2018-11-07 14:14:39 +01:00
|
|
|
import json
|
|
|
|
import requests
|
|
|
|
import time
|
|
|
|
|
|
|
|
misperrors = {'error': 'Error'}
|
|
|
|
mispattributes = {'input': ['btc'], 'output': ['text']}
|
2024-08-12 11:23:10 +02:00
|
|
|
moduleinfo = {
|
|
|
|
'version': '0.1',
|
|
|
|
'author': 'Sascha Rommelfangen',
|
|
|
|
'description': 'An expansion hover module to get a blockchain balance from a BTC address in MISP.',
|
|
|
|
'module-type': ['hover'],
|
|
|
|
'name': 'BTC Steroids',
|
|
|
|
'logo': 'bitcoin.png',
|
|
|
|
'requirements': [],
|
|
|
|
'features': '',
|
|
|
|
'references': [],
|
|
|
|
'input': 'btc address attribute.',
|
|
|
|
'output': 'Text to describe the blockchain balance and the transactions related to the btc address in input.',
|
|
|
|
}
|
2018-11-07 14:14:39 +01:00
|
|
|
|
|
|
|
moduleconfig = []
|
|
|
|
|
2018-12-11 15:29:09 +01:00
|
|
|
blockchain_firstseen = 'https://blockchain.info/q/addressfirstseen/'
|
|
|
|
blockchain_balance = 'https://blockchain.info/q/addressbalance/'
|
|
|
|
blockchain_totalreceived = 'https://blockchain.info/q/getreceivedbyaddress/'
|
|
|
|
blockchain_all = 'https://blockchain.info/rawaddr/{}?filter=5{}'
|
|
|
|
converter = 'https://min-api.cryptocompare.com/data/pricehistorical?fsym=BTC&tsyms=USD,EUR&ts={}'
|
2018-11-07 14:14:39 +01:00
|
|
|
converter_rls = 'https://min-api.cryptocompare.com/stats/rate/limit'
|
|
|
|
result_text = ""
|
|
|
|
g_rate_limit = 300
|
|
|
|
start_time = 0
|
|
|
|
conversion_rates = {}
|
|
|
|
|
2018-12-11 15:29:09 +01:00
|
|
|
|
2018-11-07 14:14:39 +01:00
|
|
|
def get_consumption(output=False):
|
2018-11-13 15:30:06 +01:00
|
|
|
try:
|
|
|
|
req = requests.get(converter_rls)
|
|
|
|
jreq = req.json()
|
|
|
|
minute = str(jreq['Data']['calls_left']['minute'])
|
2018-12-11 15:29:09 +01:00
|
|
|
hour = str(jreq['Data']['calls_left']['hour'])
|
|
|
|
except Exception:
|
2018-11-13 15:30:06 +01:00
|
|
|
minute = str(-1)
|
|
|
|
hour = str(-1)
|
2018-11-07 14:14:39 +01:00
|
|
|
# Debug out for the console
|
|
|
|
print("Calls left this minute / hour: " + minute + " / " + hour)
|
|
|
|
return minute, hour
|
|
|
|
|
|
|
|
|
|
|
|
def convert(btc, timestamp):
|
|
|
|
global g_rate_limit
|
|
|
|
global start_time
|
|
|
|
global now
|
|
|
|
global conversion_rates
|
|
|
|
date = time.strftime('%Y-%m-%d', time.localtime(timestamp))
|
|
|
|
# Lookup conversion rates in the cache:
|
|
|
|
if date in conversion_rates:
|
|
|
|
(usd, eur) = conversion_rates[date]
|
|
|
|
else:
|
|
|
|
# If not cached, we have to get the converion rates
|
|
|
|
# We have to be careful with rate limiting on the server side
|
|
|
|
if g_rate_limit == 300:
|
|
|
|
minute, hour = get_consumption()
|
|
|
|
g_rate_limit -= 1
|
|
|
|
now = time.time()
|
2018-12-11 15:29:09 +01:00
|
|
|
# delta = now - start_time
|
|
|
|
# print(g_rate_limit)
|
2018-11-07 14:14:39 +01:00
|
|
|
if g_rate_limit <= 10:
|
|
|
|
minute, hour = get_consumption(output=True)
|
|
|
|
if int(minute) <= 10:
|
2018-12-11 15:29:09 +01:00
|
|
|
# print(minute)
|
|
|
|
# get_consumption(output=True)
|
2018-11-07 14:14:39 +01:00
|
|
|
time.sleep(3)
|
|
|
|
else:
|
|
|
|
mprint(minute)
|
|
|
|
start_time = time.time()
|
|
|
|
g_rate_limit = int(minute)
|
|
|
|
try:
|
2018-12-11 15:29:09 +01:00
|
|
|
req = requests.get(converter.format(timestamp))
|
2018-11-07 14:14:39 +01:00
|
|
|
jreq = req.json()
|
|
|
|
usd = jreq['BTC']['USD']
|
|
|
|
eur = jreq['BTC']['EUR']
|
|
|
|
# Since we have the rates, store them in the cache
|
|
|
|
conversion_rates[date] = (usd, eur)
|
|
|
|
except Exception as ex:
|
|
|
|
mprint(ex)
|
|
|
|
get_consumption(output=True)
|
|
|
|
# Actually convert and return the values
|
|
|
|
u = usd * btc
|
|
|
|
e = eur * btc
|
2018-12-11 15:29:09 +01:00
|
|
|
return u, e
|
2018-11-07 14:14:39 +01:00
|
|
|
|
|
|
|
|
|
|
|
def mprint(input):
|
|
|
|
# Prepare the final print
|
|
|
|
global result_text
|
|
|
|
result_text = result_text + "\n" + str(input)
|
|
|
|
|
|
|
|
|
|
|
|
def handler(q=False):
|
|
|
|
global result_text
|
|
|
|
global conversion_rates
|
2019-03-14 14:39:58 +01:00
|
|
|
result_text = ""
|
2018-12-11 15:29:09 +01:00
|
|
|
# start_time = time.time()
|
|
|
|
# now = time.time()
|
2018-11-07 14:14:39 +01:00
|
|
|
if q is False:
|
|
|
|
return False
|
|
|
|
request = json.loads(q)
|
|
|
|
click = False
|
|
|
|
# This means the magnifying glass has been clicked
|
|
|
|
if request.get('persistent') == 1:
|
|
|
|
click = True
|
|
|
|
# Otherwise the attribute was only hovered over
|
|
|
|
if request.get('btc'):
|
|
|
|
btc = request['btc']
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
mprint("\nAddress:\t" + btc)
|
|
|
|
try:
|
2018-12-11 15:29:09 +01:00
|
|
|
req = requests.get(blockchain_all.format(btc, "&limit=50"))
|
2018-11-07 14:14:39 +01:00
|
|
|
jreq = req.json()
|
2018-12-11 15:29:09 +01:00
|
|
|
except Exception:
|
|
|
|
# print(e)
|
2018-11-07 14:57:19 +01:00
|
|
|
print(req.text)
|
2019-03-14 14:39:58 +01:00
|
|
|
result_text = "Not a valid BTC address"
|
|
|
|
r = {
|
|
|
|
'results': [
|
|
|
|
{
|
|
|
|
'types': ['text'],
|
|
|
|
'values':[
|
|
|
|
str(result_text)
|
|
|
|
]
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
return r
|
2018-11-07 14:14:39 +01:00
|
|
|
|
|
|
|
n_tx = jreq['n_tx']
|
|
|
|
balance = float(jreq['final_balance'] / 100000000)
|
|
|
|
rcvd = float(jreq['total_received'] / 100000000)
|
|
|
|
sent = float(jreq['total_sent'] / 100000000)
|
|
|
|
output = 'Balance:\t{0:.10f} BTC (+{1:.10f} BTC / -{2:.10f} BTC)'
|
|
|
|
mprint(output.format(balance, rcvd, sent))
|
|
|
|
if click is False:
|
|
|
|
mprint("Transactions:\t" + str(n_tx) + "\t (previewing up to 5 most recent)")
|
|
|
|
else:
|
|
|
|
mprint("Transactions:\t" + str(n_tx))
|
2018-11-26 15:56:11 +01:00
|
|
|
if n_tx > 0:
|
|
|
|
mprint("======================================================================================")
|
2018-11-07 14:14:39 +01:00
|
|
|
i = 0
|
|
|
|
while i < n_tx:
|
|
|
|
if click is False:
|
2019-01-24 09:51:46 +01:00
|
|
|
try:
|
|
|
|
req = requests.get(blockchain_all.format(btc, "&limit=5&offset={}".format(i)))
|
|
|
|
except Exception as e:
|
|
|
|
# Lazy retry - cries for a function
|
|
|
|
print(e)
|
|
|
|
time.sleep(3)
|
|
|
|
req = requests.get(blockchain_all.format(btc, "&limit=5&offset={}".format(i)))
|
2018-11-07 14:14:39 +01:00
|
|
|
if n_tx > 5:
|
|
|
|
n_tx = 5
|
|
|
|
else:
|
2019-01-24 09:51:46 +01:00
|
|
|
try:
|
|
|
|
req = requests.get(blockchain_all.format(btc, "&limit=50&offset={}".format(i)))
|
|
|
|
except Exception as e:
|
|
|
|
# Lazy retry - cries for a function
|
|
|
|
print(e)
|
|
|
|
time.sleep(3)
|
|
|
|
req = requests.get(blockchain_all.format(btc, "&limit=50&offset={}".format(i)))
|
2018-11-07 14:14:39 +01:00
|
|
|
jreq = req.json()
|
|
|
|
if jreq['txs']:
|
|
|
|
for transactions in jreq['txs']:
|
|
|
|
sum = 0
|
|
|
|
sum_counter = 0
|
|
|
|
for tx in transactions['inputs']:
|
|
|
|
script_old = tx['script']
|
2019-01-24 09:51:46 +01:00
|
|
|
try:
|
|
|
|
addr_in = tx['prev_out']['addr']
|
|
|
|
except KeyError:
|
|
|
|
addr_in = None
|
|
|
|
try:
|
|
|
|
prev_out = tx['prev_out']['value']
|
|
|
|
except KeyError:
|
|
|
|
prev_out = None
|
|
|
|
if prev_out != 0 and addr_in == btc:
|
2018-11-07 14:14:39 +01:00
|
|
|
datetime = time.strftime("%d %b %Y %H:%M:%S %Z", time.localtime(int(transactions['time'])))
|
2018-12-11 15:29:09 +01:00
|
|
|
value = float(tx['prev_out']['value'] / 100000000)
|
|
|
|
u, e = convert(value, transactions['time'])
|
2018-11-07 14:14:39 +01:00
|
|
|
mprint("#" + str(n_tx - i) + "\t" + str(datetime) + "\t-{0:10.8f} BTC {1:10.2f} USD\t{2:10.2f} EUR".format(value, u, e).rstrip('0'))
|
|
|
|
if script_old != tx['script']:
|
|
|
|
i += 1
|
|
|
|
else:
|
|
|
|
sum_counter += 1
|
|
|
|
sum += value
|
|
|
|
if sum_counter > 1:
|
2018-12-11 15:29:09 +01:00
|
|
|
u, e = convert(sum, transactions['time'])
|
2018-11-07 14:14:39 +01:00
|
|
|
mprint("\t\t\t\t\t----------------------------------------------")
|
|
|
|
mprint("#" + str(n_tx - i) + "\t\t\t\t Sum:\t-{0:10.8f} BTC {1:10.2f} USD\t{2:10.2f} EUR\n".format(sum, u, e).rstrip('0'))
|
|
|
|
for tx in transactions['out']:
|
2019-01-24 09:51:46 +01:00
|
|
|
try:
|
|
|
|
addr_out = tx['addr']
|
|
|
|
except KeyError:
|
|
|
|
addr_out = None
|
|
|
|
try:
|
|
|
|
prev_out = tx['prev_out']['value']
|
|
|
|
except KeyError:
|
|
|
|
prev_out = None
|
|
|
|
if prev_out != 0 and addr_out == btc:
|
2018-11-07 14:14:39 +01:00
|
|
|
datetime = time.strftime("%d %b %Y %H:%M:%S %Z", time.localtime(int(transactions['time'])))
|
2018-12-11 15:29:09 +01:00
|
|
|
value = float(tx['value'] / 100000000)
|
|
|
|
u, e = convert(value, transactions['time'])
|
2018-11-07 14:14:39 +01:00
|
|
|
mprint("#" + str(n_tx - i) + "\t" + str(datetime) + "\t {0:10.8f} BTC {1:10.2f} USD\t{2:10.2f} EUR".format(value, u, e).rstrip('0'))
|
2019-01-25 10:45:02 +01:00
|
|
|
# i += 1
|
2018-11-07 14:14:39 +01:00
|
|
|
i += 1
|
|
|
|
|
2018-12-11 15:29:09 +01:00
|
|
|
r = {
|
2018-11-07 14:14:39 +01:00
|
|
|
'results': [
|
|
|
|
{
|
|
|
|
'types': ['text'],
|
|
|
|
'values':[
|
|
|
|
str(result_text)
|
|
|
|
]
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
# Debug output on the console
|
|
|
|
print(result_text)
|
|
|
|
# Unset the result for the next request
|
|
|
|
result_text = ""
|
|
|
|
return r
|
|
|
|
|
|
|
|
|
|
|
|
def introspection():
|
|
|
|
return mispattributes
|
|
|
|
|
|
|
|
|
|
|
|
def version():
|
|
|
|
moduleinfo['config'] = moduleconfig
|
|
|
|
return moduleinfo
|