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