From 9921fc07ddbc0c53b646409de676cf81d53ac5ea Mon Sep 17 00:00:00 2001 From: shortcutme Date: Thu, 6 Apr 2017 19:19:47 +0200 Subject: [PATCH] Update pybitcointools to 1.1.42 --- src/lib/pybitcointools/MANIFEST.in | 2 +- .../pybitcointools/{README.txt => README.md} | 0 src/lib/pybitcointools/bitcoin/__init__.py | 1 + src/lib/pybitcointools/bitcoin/bci.py | 232 +- src/lib/pybitcointools/bitcoin/composite.py | 8 +- src/lib/pybitcointools/bitcoin/english.txt | 2048 +++++++++++++++++ src/lib/pybitcointools/bitcoin/main.py | 75 +- src/lib/pybitcointools/bitcoin/mnemonic.py | 127 + src/lib/pybitcointools/bitcoin/py2specials.py | 12 +- src/lib/pybitcointools/bitcoin/py3specials.py | 12 +- src/lib/pybitcointools/bitcoin/transaction.py | 58 +- src/lib/pybitcointools/setup.py | 4 +- src/lib/pybitcointools/test.py | 54 +- 13 files changed, 2502 insertions(+), 131 deletions(-) rename src/lib/pybitcointools/{README.txt => README.md} (100%) create mode 100644 src/lib/pybitcointools/bitcoin/english.txt create mode 100644 src/lib/pybitcointools/bitcoin/mnemonic.py diff --git a/src/lib/pybitcointools/MANIFEST.in b/src/lib/pybitcointools/MANIFEST.in index 1aba38f6..70656b68 100644 --- a/src/lib/pybitcointools/MANIFEST.in +++ b/src/lib/pybitcointools/MANIFEST.in @@ -1 +1 @@ -include LICENSE +include bitcoin/english.txt diff --git a/src/lib/pybitcointools/README.txt b/src/lib/pybitcointools/README.md similarity index 100% rename from src/lib/pybitcointools/README.txt rename to src/lib/pybitcointools/README.md diff --git a/src/lib/pybitcointools/bitcoin/__init__.py b/src/lib/pybitcointools/bitcoin/__init__.py index 8b543fee..7d529abc 100644 --- a/src/lib/pybitcointools/bitcoin/__init__.py +++ b/src/lib/pybitcointools/bitcoin/__init__.py @@ -7,3 +7,4 @@ from .bci import * from .composite import * from .stealth import * from .blocks import * +from .mnemonic import * diff --git a/src/lib/pybitcointools/bitcoin/bci.py b/src/lib/pybitcointools/bitcoin/bci.py index 2ff11d93..79a2c401 100644 --- a/src/lib/pybitcointools/bitcoin/bci.py +++ b/src/lib/pybitcointools/bitcoin/bci.py @@ -23,20 +23,76 @@ def make_request(*args): raise Exception(p) +def is_testnet(inp): + '''Checks if inp is a testnet address or if UTXO is a known testnet TxID''' + if isinstance(inp, (list, tuple)) and len(inp) >= 1: + return any([is_testnet(x) for x in inp]) + elif not isinstance(inp, basestring): # sanity check + raise TypeError("Input must be str/unicode, not type %s" % str(type(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): + base_url = "http://api.blockcypher.com/v1/btc/{network}/txs/{txid}?includesHex=false" + try: + # try testnet fetchtx + make_request(base_url.format(network="test3", txid=inp.lower())) + return True + except: + # try mainnet fetchtx + make_request(base_url.format(network="main", txid=inp.lower())) + return False + sys.stderr.write("TxID %s has no match for testnet or mainnet (Bad TxID)") + 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 = [] + for arg in args: + if not arg: + pass + if isinstance(arg, basestring): + r.append(is_testnet(arg)) + elif isinstance(arg, (list, tuple)): + return set_network(*arg) + if any(r) and not all(r): + raise Exception("Mixed Testnet/Mainnet queries") + return "testnet" if any(r) else "btc" + + def parse_addr_args(*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 = 'btc' + # Valid input formats: unspent([addr1, addr2, addr3]) + # unspent([addr1, addr2, addr3], network) + # unspent(addr1, addr2, addr3) + # unspent(addr1, addr2, addr3, network) addr_args = args + network = "btc" + if len(args) == 0: + return [], 'btc' if len(args) >= 1 and args[-1] in ('testnet', 'btc'): network = args[-1] addr_args = args[:-1] if len(addr_args) == 1 and isinstance(addr_args, list): + network = set_network(*addr_args[0]) addr_args = addr_args[0] - + if addr_args and isinstance(addr_args, tuple) and isinstance(addr_args[0], list): + addr_args = addr_args[0] + network = set_network(addr_args) return network, addr_args @@ -46,14 +102,14 @@ def bci_unspent(*args): u = [] for a in addrs: try: - data = make_request('https://blockchain.info/unspent?address='+a) + 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) + jsonobj = json.loads(data.decode("utf-8")) for o in jsonobj["unspent_outputs"]: h = o['tx_hash'].decode('hex')[::-1].encode('hex') u.append({ @@ -74,9 +130,9 @@ def blockr_unspent(*args): network, addr_args = parse_addr_args(*args) if network == 'testnet': - blockr_url = 'https://tbtc.blockr.io/api/v1/address/unspent/' + blockr_url = 'http://tbtc.blockr.io/api/v1/address/unspent/' elif network == 'btc': - blockr_url = 'https://btc.blockr.io/api/v1/address/unspent/' + blockr_url = 'http://btc.blockr.io/api/v1/address/unspent/' else: raise Exception( 'Unsupported network {0} for blockr_unspent'.format(network)) @@ -88,7 +144,7 @@ def blockr_unspent(*args): else: addrs = addr_args res = make_request(blockr_url+','.join(addrs)) - data = json.loads(res)['data'] + data = json.loads(res.decode("utf-8"))['data'] o = [] if 'unspent' in data: data = [data] @@ -102,7 +158,7 @@ def blockr_unspent(*args): def helloblock_unspent(*args): - network, addrs = parse_addr_args(*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': @@ -111,7 +167,7 @@ def helloblock_unspent(*args): for addr in addrs: for offset in xrange(0, 10**9, 500): res = make_request(url % (addr, offset)) - data = json.loads(res)["data"] + data = json.loads(res.decode("utf-8"))["data"] if not len(data["unspents"]): break elif offset: @@ -152,11 +208,21 @@ def history(*args): for addr in addrs: offset = 0 while 1: - data = make_request( - 'https://blockchain.info/address/%s?format=json&offset=%s' % - (addr, offset)) + 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) + jsonobj = json.loads(data.decode("utf-8")) except: raise Exception("Failed to decode data: "+data) txs.extend(jsonobj["txs"]) @@ -167,7 +233,7 @@ def history(*args): outs = {} for tx in txs: for o in tx["out"]: - if o['addr'] in addrs: + if o.get('addr', None) in addrs: key = str(tx["tx_index"])+':'+str(o["n"]) outs[key] = { "address": o["addr"], @@ -177,11 +243,12 @@ def history(*args): } for tx in txs: for i, inp in enumerate(tx["inputs"]): - if inp["prev_out"]["addr"] 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) + 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] @@ -207,9 +274,9 @@ def eligius_pushtx(tx): def blockr_pushtx(tx, network='btc'): if network == 'testnet': - blockr_url = 'https://tbtc.blockr.io/api/v1/tx/push' + blockr_url = 'http://tbtc.blockr.io/api/v1/tx/push' elif network == 'btc': - blockr_url = 'https://btc.blockr.io/api/v1/tx/push' + blockr_url = 'http://btc.blockr.io/api/v1/tx/push' else: raise Exception( 'Unsupported network {0} for blockr_pushtx'.format(network)) @@ -237,14 +304,21 @@ def pushtx(*args, **kwargs): return f(*args) -def last_block_height(): +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) + 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') @@ -253,19 +327,27 @@ def bci_fetchtx(txhash): def blockr_fetchtx(txhash, network='btc'): if network == 'testnet': - blockr_url = 'https://tbtc.blockr.io/api/v1/tx/raw/' + blockr_url = 'http://tbtc.blockr.io/api/v1/tx/raw/' elif network == 'btc': - blockr_url = 'https://btc.blockr.io/api/v1/tx/raw/' + blockr_url = 'http://btc.blockr.io/api/v1/tx/raw/' else: raise Exception( 'Unsupported network {0} for blockr_fetchtx'.format(network)) - if not re.match('^[0-9a-fA-F]*$', txhash): - txhash = txhash.encode('hex') - jsondata = json.loads(make_request(blockr_url+txhash)) - return jsondata['data']['tx']['hex'] + 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': @@ -275,7 +357,7 @@ def helloblock_fetchtx(txhash, network='btc'): else: raise Exception( 'Unsupported network {0} for helloblock_fetchtx'.format(network)) - data = json.loads(make_request(url + txhash))["data"]["transaction"] + data = json.loads(make_request(url + txhash).decode("utf-8"))["data"]["transaction"] o = { "locktime": data["locktime"], "version": data["version"], @@ -296,8 +378,8 @@ def helloblock_fetchtx(txhash, network='btc'): "value": outp["value"], "script": outp["scriptPubKey"] }) - from bitcoin.transaction import serialize - from bitcoin.transaction import txhash as TXHASH + from .transaction import serialize + from .transaction import txhash as TXHASH tx = serialize(o) assert TXHASH(tx) == txhash return tx @@ -325,7 +407,7 @@ def firstbits(address): def get_block_at_height(height): j = json.loads(make_request("https://blockchain.info/block-height/" + - str(height)+"?format=json")) + str(height)+"?format=json").decode("utf-8")) for b in j['blocks']: if b['main_chain'] is True: return b @@ -337,10 +419,10 @@ def _get_block(inp): return get_block_at_height(inp) else: return json.loads(make_request( - 'https://blockchain.info/rawblock/'+inp)) + 'https://blockchain.info/rawblock/'+inp).decode("utf-8")) -def get_block_header_data(inp): +def bci_get_block_header_data(inp): j = _get_block(inp) return { 'version': j['ver'], @@ -354,14 +436,14 @@ def get_block_header_data(inp): def blockr_get_block_header_data(height, network='btc'): if network == 'testnet': - blockr_url = "https://tbtc.blockr.io/api/v1/block/raw/" + blockr_url = "http://tbtc.blockr.io/api/v1/block/raw/" elif network == 'btc': - blockr_url = "https://btc.blockr.io/api/v1/block/raw/" + 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))) + k = json.loads(make_request(blockr_url + str(height)).decode("utf-8")) j = k['data'] return { 'version': j['version'], @@ -373,6 +455,40 @@ def blockr_get_block_header_data(height, network='btc'): '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_txs_in_block(inp): j = _get_block(inp) hashes = [t['hash'] for t in j['tx']] @@ -380,5 +496,33 @@ def get_txs_in_block(inp): def get_block_height(txhash): - j = json.loads(make_request('https://blockchain.info/rawtx/'+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): + """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 diff --git a/src/lib/pybitcointools/bitcoin/composite.py b/src/lib/pybitcointools/bitcoin/composite.py index 0df0e079..e5d50492 100644 --- a/src/lib/pybitcointools/bitcoin/composite.py +++ b/src/lib/pybitcointools/bitcoin/composite.py @@ -6,12 +6,12 @@ from .blocks import * # Takes privkey, address, value (satoshis), fee (satoshis) -def send(frm, to, value, fee=10000): - return sendmultitx(frm, to + ":" + str(value), fee) +def send(frm, to, value, fee=10000, **kwargs): + return sendmultitx(frm, to + ":" + str(value), fee, **kwargs) # Takes privkey, "address1:value1,address2:value2" (satoshis), fee (satoshis) -def sendmultitx(frm, tovalues, fee=10000, **kwargs): +def sendmultitx(frm, *args, **kwargs): tv, fee = args[:-1], int(args[-1]) outs = [] outvalue = 0 @@ -21,7 +21,7 @@ def sendmultitx(frm, tovalues, fee=10000, **kwargs): u = unspent(privtoaddr(frm), **kwargs) u2 = select(u, int(outvalue)+int(fee)) - argz = u2 + outs + [frm, fee] + argz = u2 + outs + [privtoaddr(frm), fee] tx = mksend(*argz) tx2 = signall(tx, frm) return pushtx(tx2, **kwargs) diff --git a/src/lib/pybitcointools/bitcoin/english.txt b/src/lib/pybitcointools/bitcoin/english.txt new file mode 100644 index 00000000..942040ed --- /dev/null +++ b/src/lib/pybitcointools/bitcoin/english.txt @@ -0,0 +1,2048 @@ +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/src/lib/pybitcointools/bitcoin/main.py b/src/lib/pybitcointools/bitcoin/main.py index 2b8bdd04..8cf3a9f7 100644 --- a/src/lib/pybitcointools/bitcoin/main.py +++ b/src/lib/pybitcointools/bitcoin/main.py @@ -122,7 +122,7 @@ def jacobian_add(p, q): U1H2 = (U1 * H2) % P nx = (R ** 2 - H3 - 2 * U1H2) % P ny = (R * (U1H2 - nx) - S1 * H3) % P - nz = H * p[2] * q[2] + nz = (H * p[2] * q[2]) % P return (nx, ny, nz) @@ -179,10 +179,10 @@ def encode_pubkey(pub, formt): pub = decode_pubkey(pub) if formt == 'decimal': return pub elif formt == 'bin': return b'\x04' + encode(pub[0], 256, 32) + encode(pub[1], 256, 32) - elif formt == 'bin_compressed': + elif formt == 'bin_compressed': return from_int_to_byte(2+(pub[1] % 2)) + encode(pub[0], 256, 32) elif formt == 'hex': return '04' + encode(pub[0], 16, 64) + encode(pub[1], 16, 64) - elif formt == 'hex_compressed': + elif formt == 'hex_compressed': return '0'+str(2+(pub[1] % 2)) + encode(pub[0], 16, 64) elif formt == 'bin_electrum': return encode(pub[0], 256, 32) + encode(pub[1], 256, 32) elif formt == 'hex_electrum': return encode(pub[0], 16, 64) + encode(pub[1], 16, 64) @@ -253,6 +253,9 @@ def add_privkeys(p1, p2): f1, f2 = get_privkey_format(p1), get_privkey_format(p2) return encode_privkey((decode_privkey(p1, f1) + decode_privkey(p2, f2)) % N, f1) +def mul_privkeys(p1, p2): + f1, f2 = get_privkey_format(p1), get_privkey_format(p2) + return encode_privkey((decode_privkey(p1, f1) * decode_privkey(p2, f2)) % N, f1) def multiply(pubkey, privkey): f1, f2 = get_pubkey_format(pubkey), get_privkey_format(privkey) @@ -450,12 +453,32 @@ def pubkey_to_address(pubkey, magicbyte=0): pubtoaddr = pubkey_to_address + +def is_privkey(priv): + try: + get_privkey_format(priv) + return True + except: + return False + +def is_pubkey(pubkey): + try: + get_pubkey_format(pubkey) + return True + except: + return False + +def is_address(addr): + ADDR_RE = re.compile("^[123mn][a-km-zA-HJ-NP-Z0-9]{26,33}$") + return bool(ADDR_RE.match(addr)) + + # EDCSA def encode_sig(v, r, s): vb, rb, sb = from_int_to_byte(v), encode(r, 256), encode(s, 256) - + result = base64.b64encode(vb+b'\x00'*(32-len(rb))+rb+b'\x00'*(32-len(sb))+sb) return result if is_python2 else str(result, 'utf-8') @@ -487,35 +510,59 @@ def ecdsa_raw_sign(msghash, priv): r, y = fast_multiply(G, k) s = inv(k, N) * (z + r*decode_privkey(priv)) % N - return 27+(y % 2), r, s + v, r, s = 27+((y % 2) ^ (0 if s * 2 < N else 1)), r, s if s * 2 < N else N - s + if 'compressed' in get_privkey_format(priv): + v += 4 + return v, r, s def ecdsa_sign(msg, priv): - return encode_sig(*ecdsa_raw_sign(electrum_sig_hash(msg), priv)) + v, r, s = ecdsa_raw_sign(electrum_sig_hash(msg), priv) + sig = encode_sig(v, r, s) + assert ecdsa_verify(msg, sig, + privtopub(priv)), "Bad Sig!\t %s\nv = %d\n,r = %d\ns = %d" % (sig, v, r, s) + return sig def ecdsa_raw_verify(msghash, vrs, pub): v, r, s = vrs + if not (27 <= v <= 34): + return False w = inv(s, N) z = hash_to_int(msghash) u1, u2 = z*w % N, r*w % N x, y = fast_add(fast_multiply(G, u1), fast_multiply(decode_pubkey(pub), u2)) + return bool(r == x and (r % N) and (s % N)) - return r == x + +# For BitcoinCore, (msg = addr or msg = "") be default +def ecdsa_verify_addr(msg, sig, addr): + assert is_address(addr) + Q = ecdsa_recover(msg, sig) + magic = get_version_byte(addr) + return (addr == pubtoaddr(Q, int(magic))) or (addr == pubtoaddr(compress(Q), int(magic))) def ecdsa_verify(msg, sig, pub): + if is_address(pub): + return ecdsa_verify_addr(msg, sig, pub) return ecdsa_raw_verify(electrum_sig_hash(msg), decode_sig(sig), pub) def ecdsa_raw_recover(msghash, vrs): v, r, s = vrs - + if not (27 <= v <= 34): + raise ValueError("%d must in range 27-31" % v) x = r - beta = pow(x*x*x+A*x+B, (P+1)//4, P) + xcubedaxb = (x*x*x+A*x+B) % P + beta = pow(xcubedaxb, (P+1)//4, P) y = beta if v % 2 ^ beta % 2 else (P - beta) + # If xcubedaxb is not a quadratic residue, then r cannot be the x coord + # for a point on the curve, and so the sig is invalid + if (xcubedaxb - y*y) % P != 0 or not (r % N) or not (s % N): + return False z = hash_to_int(msghash) Gz = jacobian_multiply((Gx, Gy, 1), (N - z) % N) XY = jacobian_multiply((x, y, 1), s) @@ -523,10 +570,12 @@ def ecdsa_raw_recover(msghash, vrs): Q = jacobian_multiply(Qr, inv(r, N)) Q = from_jacobian(Q) - if ecdsa_raw_verify(msghash, vrs, Q): - return Q - return False + # if ecdsa_raw_verify(msghash, vrs, Q): + return Q + # return False def ecdsa_recover(msg, sig): - return encode_pubkey(ecdsa_raw_recover(electrum_sig_hash(msg), decode_sig(sig)), 'hex') + v,r,s = decode_sig(sig) + Q = ecdsa_raw_recover(electrum_sig_hash(msg), (v,r,s)) + return encode_pubkey(Q, 'hex_compressed') if v >= 31 else encode_pubkey(Q, 'hex') diff --git a/src/lib/pybitcointools/bitcoin/mnemonic.py b/src/lib/pybitcointools/bitcoin/mnemonic.py new file mode 100644 index 00000000..a9df3617 --- /dev/null +++ b/src/lib/pybitcointools/bitcoin/mnemonic.py @@ -0,0 +1,127 @@ +import hashlib +import os.path +import binascii +import random +from bisect import bisect_left + +wordlist_english=list(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) + 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)] + 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_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) + 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) + return csint == entropy_cs(ebytes) + +def mnemonic_to_seed(mnemonic_phrase,passphrase=b''): + try: + from hashlib import pbkdf2_hmac + def pbkdf2_hmac_sha256(password,salt,iters=2048): + return pbkdf2_hmac(hash_name='sha512',password=password,salt=salt,iterations=iters) + except: + try: + from Crypto.Protocol.KDF import PBKDF2 + from Crypto.Hash import SHA512,HMAC + + def pbkdf2_hmac_sha256(password,salt,iters=2048): + return PBKDF2(password=password,salt=salt,dkLen=64,count=iters,prf=lambda p,s: HMAC.new(p,s,SHA512).digest()) + except: + try: + + from pbkdf2 import PBKDF2 + import hmac + def pbkdf2_hmac_sha256(password,salt,iters=2048): + return PBKDF2(password,salt, iterations=iters, macmodule=hmac, digestmodule=hashlib.sha512).read(64) + except: + raise RuntimeError("No implementation of pbkdf2 was found!") + + return pbkdf2_hmac_sha256(password=mnemonic_phrase,salt=b'mnemonic'+passphrase) + +def words_mine(prefix,entbits,satisfunction,wordlist=wordlist_english,randombits=random.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)) + +if __name__=="__main__": + import json + testvectors=json.load(open('vectors.json','r')) + passed=True + for v in testvectors['english']: + ebytes=binascii.unhexlify(v[0]) + w=' '.join(entropy_to_words(ebytes)) + seed=mnemonic_to_seed(w,passphrase='TREZOR') + passed = passed and w==v[1] + passed = passed and binascii.hexlify(seed)==v[2] + print("Tests %s." % ("Passed" if passed else "Failed")) + + diff --git a/src/lib/pybitcointools/bitcoin/py2specials.py b/src/lib/pybitcointools/bitcoin/py2specials.py index 4e2e42bb..337154f3 100644 --- a/src/lib/pybitcointools/bitcoin/py2specials.py +++ b/src/lib/pybitcointools/bitcoin/py2specials.py @@ -40,10 +40,14 @@ if sys.version_info.major == 2: return encode(decode(string, frm), to, minlen) def bin_to_b58check(inp, magicbyte=0): - inp_fmtd = chr(int(magicbyte)) + inp - leadingzbytes = len(re.match('^\x00*', inp_fmtd).group(0)) - checksum = bin_dbl_sha256(inp_fmtd)[:4] - return '1' * leadingzbytes + changebase(inp_fmtd+checksum, 256, 58) + if magicbyte == 0: + inp = '\x00' + inp + while magicbyte > 0: + inp = chr(int(magicbyte % 256)) + inp + magicbyte //= 256 + leadingzbytes = len(re.match('^\x00*', inp).group(0)) + checksum = bin_dbl_sha256(inp)[:4] + return '1' * leadingzbytes + changebase(inp+checksum, 256, 58) def bytes_to_hex_string(b): return b.encode('hex') diff --git a/src/lib/pybitcointools/bitcoin/py3specials.py b/src/lib/pybitcointools/bitcoin/py3specials.py index be234722..7593b9a6 100644 --- a/src/lib/pybitcointools/bitcoin/py3specials.py +++ b/src/lib/pybitcointools/bitcoin/py3specials.py @@ -38,16 +38,20 @@ if sys.version_info.major == 3: return encode(decode(string, frm), to, minlen) def bin_to_b58check(inp, magicbyte=0): - inp_fmtd = from_int_to_byte(int(magicbyte))+inp + if magicbyte == 0: + inp = from_int_to_byte(0) + inp + while magicbyte > 0: + inp = from_int_to_byte(magicbyte % 256) + inp + magicbyte //= 256 leadingzbytes = 0 - for x in inp_fmtd: + for x in inp: if x != 0: break leadingzbytes += 1 - checksum = bin_dbl_sha256(inp_fmtd)[:4] - return '1' * leadingzbytes + changebase(inp_fmtd+checksum, 256, 58) + checksum = bin_dbl_sha256(inp)[:4] + return '1' * leadingzbytes + changebase(inp+checksum, 256, 58) def bytes_to_hex_string(b): if isinstance(b, str): diff --git a/src/lib/pybitcointools/bitcoin/transaction.py b/src/lib/pybitcointools/bitcoin/transaction.py index ec71ec9b..4a501504 100644 --- a/src/lib/pybitcointools/bitcoin/transaction.py +++ b/src/lib/pybitcointools/bitcoin/transaction.py @@ -9,7 +9,7 @@ from _functools import reduce def json_is_base(obj, base): if not is_python2 and isinstance(obj, bytes): return False - + alpha = get_code_string(base) if isinstance(obj, string_types): for i in range(len(obj)): @@ -58,7 +58,7 @@ def deserialize(tx): def read_var_int(): pos[0] += 1 - + val = from_byte_to_int(tx[pos[0]-1]) if val < 253: return val @@ -138,9 +138,9 @@ def signature_form(tx, i, script, hashcode=SIGHASH_ALL): newtx["outs"] = [] elif hashcode == SIGHASH_SINGLE: newtx["outs"] = newtx["outs"][:len(newtx["ins"])] - for out in range(len(newtx["ins"]) - 1): - out.value = 2**64 - 1 - out.script = "" + for out in newtx["outs"][:len(newtx["ins"]) - 1]: + out['value'] = 2**64 - 1 + out['script'] = "" elif hashcode == SIGHASH_ANYONECANPAY: newtx["ins"] = [newtx["ins"][i]] else: @@ -152,15 +152,14 @@ def signature_form(tx, i, script, hashcode=SIGHASH_ALL): def der_encode_sig(v, r, s): b1, b2 = safe_hexlify(encode(r, 256)), safe_hexlify(encode(s, 256)) - if r >= 2**255: + if len(b1) and b1[0] in '89abcdef': b1 = '00' + b1 - if s >= 2**255: + if len(b2) and b2[0] in '89abcdef': b2 = '00' + b2 left = '02'+encode(len(b1)//2, 16, 2)+b1 right = '02'+encode(len(b2)//2, 16, 2)+b2 return '30'+encode(len(left+right)//2, 16, 2)+left+right - def der_decode_sig(sig): leftlen = decode(sig[6:8], 16)*2 left = sig[8:8+leftlen] @@ -168,6 +167,32 @@ def der_decode_sig(sig): right = sig[12+leftlen:12+leftlen+rightlen] return (None, decode(left, 16), decode(right, 16)) +def is_bip66(sig): + """Checks hex DER sig for BIP66 consistency""" + #https://raw.githubusercontent.com/bitcoin/bips/master/bip-0066.mediawiki + #0x30 [total-len] 0x02 [R-len] [R] 0x02 [S-len] [S] [sighash] + sig = bytearray.fromhex(sig) if re.match('^[0-9a-fA-F]*$', sig) else bytearray(sig) + if (sig[0] == 0x30) and (sig[1] == len(sig)-2): # check if sighash is missing + sig.extend(b"\1") # add SIGHASH_ALL for testing + #assert (sig[-1] & 124 == 0) and (not not sig[-1]), "Bad SIGHASH value" + + if len(sig) < 9 or len(sig) > 73: return False + if (sig[0] != 0x30): return False + if (sig[1] != len(sig)-3): return False + rlen = sig[3] + if (5+rlen >= len(sig)): return False + slen = sig[5+rlen] + if (rlen + slen + 7 != len(sig)): return False + if (sig[2] != 0x02): return False + if (rlen == 0): return False + if (sig[4] & 0x80): return False + if (rlen > 1 and (sig[4] == 0x00) and not (sig[5] & 0x80)): return False + if (sig[4+rlen] != 0x02): return False + if (slen == 0): return False + if (sig[rlen+6] & 0x80): return False + if (slen > 1 and (sig[6+rlen] == 0x00) and not (sig[7+rlen] & 0x80)): + return False + return True def txhash(tx, hashcode=None): if isinstance(tx, str) and re.match('^[0-9a-fA-F]*$', tx): @@ -230,8 +255,11 @@ def script_to_address(script, vbyte=0): if vbyte in [111, 196]: # Testnet scripthash_byte = 196 - else: + elif vbyte == 0: + # Mainnet scripthash_byte = 5 + else: + scripthash_byte = vbyte # BIP0016 scripthash addresses return bin_to_b58check(script[2:-1], scripthash_byte) @@ -275,7 +303,7 @@ def serialize_script_unit(unit): if unit < 16: return from_int_to_byte(unit + 80) else: - return bytes([unit]) + return from_int_to_byte(unit) elif unit is None: return b'\x00' else: @@ -300,7 +328,7 @@ else: if json_is_base(script, 16): return safe_hexlify(serialize_script(json_changebase(script, lambda x: binascii.unhexlify(x)))) - + result = bytes() for b in map(serialize_script_unit, script): result += b if isinstance(b, bytes) else bytes(b, 'utf-8') @@ -313,7 +341,7 @@ def mk_multisig_script(*args): # [pubs],k or pub1,pub2...pub[n],k else: pubs = list(filter(lambda x: len(str(x)) >= 32, args)) k = int(args[len(pubs)]) - return serialize_script([k]+pubs+[len(pubs)]) + 'ae' + return serialize_script([k]+pubs+[len(pubs)]+[0xae]) # Signing and verifying @@ -378,8 +406,12 @@ def apply_multisignatures(*args): if isinstance(tx, str) and re.match('^[0-9a-fA-F]*$', tx): return safe_hexlify(apply_multisignatures(binascii.unhexlify(tx), i, script, sigs)) + # Not pushing empty elements on the top of the stack if passing no + # script (in case of bare multisig inputs there is no script) + script_blob = [] if script.__len__() == 0 else [script] + txobj = deserialize(tx) - txobj["ins"][i]["script"] = serialize_script([None]+sigs+[script]) + txobj["ins"][i]["script"] = serialize_script([None]+sigs+script_blob) return serialize(txobj) diff --git a/src/lib/pybitcointools/setup.py b/src/lib/pybitcointools/setup.py index 1cd6d1f8..e01a9bfc 100644 --- a/src/lib/pybitcointools/setup.py +++ b/src/lib/pybitcointools/setup.py @@ -5,7 +5,7 @@ except ImportError: from distutils.core import setup setup(name='bitcoin', - version='1.1.28', + version='1.1.42', description='Python Bitcoin Tools', author='Vitalik Buterin', author_email='vbuterin@gmail.com', @@ -13,5 +13,5 @@ setup(name='bitcoin', packages=['bitcoin'], scripts=['pybtctool'], include_package_data=True, - data_files=[("", ["LICENSE"])], + data_files=[("", ["LICENSE"]), ("bitcoin", ["bitcoin/english.txt"])], ) diff --git a/src/lib/pybitcointools/test.py b/src/lib/pybitcointools/test.py index 2cf415d8..59edaace 100644 --- a/src/lib/pybitcointools/test.py +++ b/src/lib/pybitcointools/test.py @@ -99,57 +99,19 @@ class TestElectrumWalletInternalConsistency(unittest.TestCase): ) -class TestElectrumSignVerify(unittest.TestCase): - """Requires Electrum.""" +class TestRawSignRecover(unittest.TestCase): @classmethod def setUpClass(cls): - cls.wallet = "/tmp/tempwallet_" + str(random.randrange(2**40)) - print("Starting wallet tests with: " + cls.wallet) - os.popen('echo "\n\n\n\n\n\n" | electrum -w %s create' % cls.wallet).read() - cls.seed = str(json.loads(os.popen("electrum -w %s getseed" % cls.wallet).read())['seed']) - cls.addies = json.loads(os.popen("electrum -w %s listaddresses" % cls.wallet).read()) + print("Basic signing and recovery tests") - def test_address(self): - for i in range(5): + def test_all(self): + for i in range(20): + k = sha256(str(i)) + s = ecdsa_raw_sign('35' * 32, k) self.assertEqual( - self.addies[i], - electrum_address(self.seed, i, 0), - "Address does not match! Details:\nseed %s, i: %d" % (self.seed, i) - ) - - def test_sign_verify(self): - print("Electrum-style signing and verification tests, against actual Electrum") - alphabet = "1234567890qwertyuiopasdfghjklzxcvbnm" - for i in range(8): - msg = ''.join([random.choice(alphabet) for i in range(random.randrange(20, 200))]) - addy = random.choice(self.addies) - wif = os.popen('electrum -w %s dumpprivkey %s' % (self.wallet, addy)).readlines()[-2].replace('"', '').strip() - priv = b58check_to_hex(wif) - pub = privtopub(priv) - - sig = os.popen('electrum -w %s signmessage %s %s' % (self.wallet, addy, msg)).readlines()[-1].strip() - self.assertTrue( - ecdsa_verify(msg, sig, pub), - "Verification error. Details:\nmsg: %s\nsig: %s\npriv: %s\naddy: %s\npub: %s" % ( - msg, sig, priv, addy, pub - ) - ) - - rec = ecdsa_recover(msg, sig) - self.assertEqual( - pub, - rec, - "Recovery error. Details:\nmsg: %s\nsig: %s\npriv: %s\naddy: %s\noriginal pub: %s, %s\nrecovered pub: %s" % ( - msg, sig, priv, addy, pub, decode_pubkey(pub, 'hex')[1], rec - ) - ) - - mysig = ecdsa_sign(msg, priv) - self.assertEqual( - os.popen('electrum -w %s verifymessage %s %s %s' % (self.wallet, addy, mysig, msg)).read().strip(), - "true", - "Electrum verify message does not match" + ecdsa_raw_recover('35' * 32, s), + decode_pubkey(privtopub(k)) )