1# This file is part of the TREZOR project. 2# 3# Copyright (C) 2012-2016 Marek Palatinus <slush@satoshilabs.com> 4# Copyright (C) 2012-2016 Pavol Rusnak <stick@satoshilabs.com> 5# Copyright (C) 2016 Jochen Hoenicke <hoenicke@gmail.com> 6# 7# This library is free software: you can redistribute it and/or modify 8# it under the terms of the GNU Lesser General Public License as published by 9# the Free Software Foundation, either version 3 of the License, or 10# (at your option) any later version. 11# 12# This library is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU Lesser General Public License for more details. 16# 17# You should have received a copy of the GNU Lesser General Public License 18# along with this library. If not, see <http://www.gnu.org/licenses/>. 19 20import binascii 21from decimal import Decimal 22import requests 23import json 24import struct 25 26from . import types_pb2 as proto_types 27 28cache_dir = None 29 30 31def pack_varint(n): 32 if n < 253: 33 return struct.pack("<B", n) 34 elif n <= 0xFFFF: 35 return struct.pack("<BH", 253, n) 36 elif n <= 0xFFFFFFFF: 37 return struct.pack("<BL", 254, n) 38 else: 39 return struct.pack("<BQ", 255, n) 40 41 42class TxApi(object): 43 44 def __init__(self, network, url): 45 self.network = network 46 self.url = url 47 48 def fetch_json(self, url, resource, resourceid): 49 global cache_dir 50 if cache_dir: 51 cache_file = '%s/%s_%s_%s.json' % (cache_dir, self.network, resource, resourceid) 52 try: # looking into cache first 53 with open(cache_file) as f: 54 j = json.load(f) 55 return j 56 except: 57 pass 58 try: 59 r = requests.get('%s/%s/%s' % (self.url, resource, resourceid), headers={'User-agent': 'Mozilla/5.0'}) 60 j = r.json() 61 except: 62 raise Exception('URL error: %s' % url) 63 if cache_file: 64 try: # saving into cache 65 json.dump(j, open(cache_file, 'w')) 66 except: 67 pass 68 return j 69 70 def get_tx(self, txhash): 71 raise NotImplementedError 72 73 74class TxApiInsight(TxApi): 75 76 def __init__(self, network, url, zcash=None): 77 super(TxApiInsight, self).__init__(network, url) 78 self.zcash = zcash 79 80 def get_tx(self, txhash): 81 82 data = self.fetch_json(self.url, 'tx', txhash) 83 84 t = proto_types.TransactionType() 85 t.version = data['version'] 86 t.lock_time = data['locktime'] 87 88 for vin in data['vin']: 89 i = t.inputs.add() 90 if 'coinbase' in vin.keys(): 91 i.prev_hash = b"\0"*32 92 i.prev_index = 0xffffffff # signed int -1 93 i.script_sig = binascii.unhexlify(vin['coinbase']) 94 i.sequence = vin['sequence'] 95 96 else: 97 i.prev_hash = binascii.unhexlify(vin['txid']) 98 i.prev_index = vin['vout'] 99 i.script_sig = binascii.unhexlify(vin['scriptSig']['hex']) 100 i.sequence = vin['sequence'] 101 102 for vout in data['vout']: 103 o = t.bin_outputs.add() 104 o.amount = int(Decimal(str(vout['value'])) * 100000000) 105 o.script_pubkey = binascii.unhexlify(vout['scriptPubKey']['hex']) 106 107 if self.zcash: 108 if t.version == 2: 109 joinsplit_cnt = len(data['vjoinsplit']) 110 if joinsplit_cnt == 0: 111 t.extra_data =b'\x00' 112 else: 113 if joinsplit_cnt >= 253: 114 # we assume cnt < 253, so we can treat varIntLen(cnt) as 1 115 raise ValueError('Too many joinsplits') 116 extra_data_len = 1 + joinsplit_cnt * 1802 + 32 + 64 117 raw = self.fetch_json(self.url, 'rawtx', txhash) 118 raw = binascii.unhexlify(raw['rawtx']) 119 t.extra_data = raw[-extra_data_len:] 120 121 if "_dash" in self.network: 122 dip2_type = data.get("type", 0) 123 124 if t.version == 3 and dip2_type != 0: 125 # It's a DIP2 special TX with payload 126 127 if "extraPayloadSize" not in data or "extraPayload" not in data: 128 raise ValueError("Payload data missing in DIP2 transaction") 129 130 if data["extraPayloadSize"] * 2 != len(data["extraPayload"]): 131 raise ValueError("length mismatch") 132 t.extra_data = pack_varint(data["extraPayloadSize"]) + binascii.unhexlify( 133 data["extraPayload"] 134 ) 135 136 # Trezor (and therefore KeepKey) firmware doesn't understand the 137 # split of version and type, so let's mimic the old serialization 138 # format 139 t.version |= dip2_type << 16 140 141 return t 142 143 def get_raw_tx(self, txhash): 144 data = self.fetch_json(self.url, 'rawtx', txhash)['rawtx'] 145 return data 146 147 148TxApiBitcoin = TxApiInsight(network='insight_bitcoin', url='https://btc.coinquery.com/api') 149TxApiTestnet = TxApiInsight(network='insight_testnet', url='https://test-insight.bitpay.com/api') 150TxApiZcashTestnet = TxApiInsight(network='insight_zcashtestnet', url='https://explorer.testnet.z.cash/api', zcash=True) 151TxApiBitcoinGold = TxApiInsight(network='insight_bitcoingold', url='https://btg.coinquery.com/api') 152TxApiGroestlcoin = TxApiInsight(network='insight_groestlcoin', url='https://groestlsight.groestlcoin.org/api') 153TxApiGroestlcoinTestnet = TxApiInsight(network='insight_groestlcoin_testnet', url='https://groestlsight-test.groestlcoin.org/api') 154TxApiDash = TxApiInsight(network='insight_dash', url='https://dash.coinquery.com/api') 155