1#!/usr/bin/env python3
2
3# This software is Copyright (c) 2018, Dhiru Kholia <dhiru at openwall.com> and
4# it is hereby released to the general public under the following terms:
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted.
8#
9# Special thanks goes to https://github.com/NODESPLIT/tz-brute and Michael Senn
10# (@MikeSenn on Telegram) for helping me bootstrap this project.
11
12import re
13import os
14import sys
15import json
16import hashlib
17import binascii
18
19PY3 = sys.version_info[0] == 3
20
21if not PY3:
22    print("This program requires Python 3.6+ to run.")
23    sys.exit(0)
24
25### Borrowed code starts, The MIT License (MIT), Copyright (c) 2013 Vitalik Buterin, https://github.com/vbuterin/pybitcointools ###
26
27def bytes_to_hex_string(b):
28    if isinstance(b, str):
29        return b
30
31    return ''.join('{:02x}'.format(y) for y in b)
32
33def safe_from_hex(s):
34    return bytes.fromhex(s)
35
36def from_int_representation_to_bytes(a):
37    return bytes(str(a), 'utf-8')
38
39def from_int_to_byte(a):
40    return bytes([a])
41
42def from_byte_to_int(a):
43    return a
44
45def from_string_to_bytes(a):
46    return a if isinstance(a, bytes) else bytes(a, 'utf-8')
47
48def safe_hexlify(a):
49    return str(binascii.hexlify(a), 'utf-8')
50
51
52string_types = (str)
53string_or_bytes_types = (str, bytes)
54int_types = (int, float)
55# Base switching
56code_strings = {
57    2: '01',
58    10: '0123456789',
59    16: '0123456789abcdef',
60    32: 'abcdefghijklmnopqrstuvwxyz234567',
61    58: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz',
62    256: ''.join([chr(x) for x in range(256)])
63}
64
65def encode(val, base, minlen=0):
66    base, minlen = int(base), int(minlen)
67    code_string = get_code_string(base)
68    result_bytes = bytes()
69    while val > 0:
70        curcode = code_string[val % base]
71        result_bytes = bytes([ord(curcode)]) + result_bytes
72        val //= base
73
74    pad_size = minlen - len(result_bytes)
75
76    padding_element = b'\x00' if base == 256 else b'1' \
77        if base == 58 else b'0'
78    if (pad_size > 0):
79        result_bytes = padding_element*pad_size + result_bytes
80
81    result_string = ''.join([chr(y) for y in result_bytes])
82    result = result_bytes if base == 256 else result_string
83
84    return result
85
86def decode(string, base):
87    if base == 256 and isinstance(string, str):
88        string = bytes(bytearray.fromhex(string))
89    base = int(base)
90    code_string = get_code_string(base)
91    result = 0
92    if base == 256:
93        def extract(d, cs):
94            return d
95    else:
96        def extract(d, cs):
97            return cs.find(d if isinstance(d, str) else chr(d))
98
99    if base == 16:
100        string = string.lower()
101    while len(string) > 0:
102        result *= base
103        result += extract(string[0], code_string)
104        string = string[1:]
105    return result
106
107
108
109def bin_dbl_sha256(s):
110    bytes_to_hash = from_string_to_bytes(s)
111    return hashlib.sha256(hashlib.sha256(bytes_to_hash).digest()).digest()
112
113def lpad(msg, symbol, length):
114    if len(msg) >= length:
115        return msg
116    return symbol * (length - len(msg)) + msg
117
118def get_code_string(base):
119    if base in code_strings:
120        return code_strings[base]
121    else:
122        raise ValueError("Invalid base!")
123
124def changebase(string, frm, to, minlen=0):
125    if frm == to:
126        return lpad(string, get_code_string(frm)[0], minlen)
127    return encode(decode(string, frm), to, minlen)
128
129def b58check_to_bin(inp):
130    leadingzbytes = len(re.match('^1*', inp).group(0))
131    data = b'\x00' * leadingzbytes + changebase(inp, 58, 256)
132    assert bin_dbl_sha256(data[:-4])[:4] == data[-4:]
133    return data[1:-4]
134
135### Borrowed code ends ####
136
137if __name__ == "__main__":
138    if len(sys.argv) == 2:  # internal https://faucet.tzalpha.net/ files testing mode
139        filename = sys.argv[1]
140        data = open(filename).read()
141        data = json.loads(data)
142        mnemonic, email, address = (" ".join(data["mnemonic"]), data["email"], data["pkh"])
143        raw_address = binascii.hexlify(b58check_to_bin(address)).decode("ascii")
144        print("%s:$tezos$1*%s*%s*%s*%s*%s" % ("dummy", 2048, mnemonic, email, address, raw_address))
145        sys.exit(0)
146    if len(sys.argv) < 4:
147        sys.stderr.write("Usage: %s \'mnemonic data (15 words)\' \'email\' \'public key\'\n" %
148                         sys.argv[0])
149        sys.stderr.write("""\nExample: %s 'put guide flat machine express cave hello connect stay local spike ski romance express brass' 'jbzbdybr.vpbdbxnn@tezos.example.org' 'tz1eTjPtwYjdcBMStwVdEcwY2YE3th1bXyMR'\n""" % sys.argv[0])
150        sys.exit(-1)
151
152    mnemonic, email, address = sys.argv[1:4]
153    if len(email) > 51:
154        sys.stderr.write("[WARNING] Very large salt (email address) found, which is unsupported by tezos-opencl format!\n")
155
156    raw_address = binascii.hexlify(b58check_to_bin(address)).decode("ascii")
157
158    print("%s:$tezos$1*%s*%s*%s*%s*%s" % ("dummy", 2048, mnemonic, email, address, raw_address))
159