-
Notifications
You must be signed in to change notification settings - Fork 143
/
pyethsaletool.py
executable file
·359 lines (318 loc) · 12 KB
/
pyethsaletool.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
#!/usr/bin/python
import python_sha3
import aes
import os
import sys
import json
import getpass
import pbkdf2 as PBKDF2
from bitcoin import *
import urllib2
from optparse import OptionParser
# Arguments
exodus = '36PrZ1KHYMpqSyAQXSG8VwbUiq2EogxLo2'
minimum = 1000000
maximum = 150000000000
# Option parsing
parser = OptionParser()
parser.add_option('-p', '--password',
default=None, dest='pw')
parser.add_option('-s', '--seed',
default=None, dest='seed')
parser.add_option('-w', '--wallet',
default='ethwallet.json', dest='wallet')
parser.add_option('-e', '--email',
default=None, dest='email')
parser.add_option('-o', '--overwrite',
default=False, dest='overwrite')
(options, args) = parser.parse_args()
# Function wrappers
def sha3(x):
return python_sha3.sha3_256(x).digest()
def pbkdf2(x):
return PBKDF2._pbkdf2(x, x, 2000)[:16]
# Makes a request to a given URL (first arg) and optional params (second arg)
def make_request(url, data, headers):
req = urllib2.Request(url, data, headers)
return urllib2.urlopen(req).read().strip()
# Prefer openssl because it's more well-tested and reviewed; otherwise,
# use pybitcointools' internal ecdsa implementation
try:
import openssl
except:
openssl = None
def openssl_tx_sign(tx, priv):
if len(priv) == 64:
priv = priv.decode('hex')
if openssl:
k = openssl.CKey()
k.generate(priv)
u = k.sign(bitcoin.bin_txhash(tx))
return u.encode('hex')
else:
return ecdsa_tx_sign(tx, priv)
def secure_sign(tx, i, priv):
i = int(i)
if not re.match('^[0-9a-fA-F]*$', tx):
return sign(tx.encode('hex'), i, priv).decode('hex')
if len(priv) <= 33:
priv = priv.encode('hex')
pub = privkey_to_pubkey(priv)
address = pubkey_to_address(pub)
signing_tx = signature_form(tx, i, mk_pubkey_script(address))
sig = openssl_tx_sign(signing_tx, priv)
txobj = deserialize(tx)
txobj["ins"][i]["script"] = serialize_script([sig, pub])
return serialize(txobj)
def secure_privtopub(priv):
if len(priv) == 64:
return secure_privtopub(priv.decode('hex')).encode('hex')
if openssl:
k = openssl.CKey()
k.generate(priv)
return k.get_pubkey()
else:
return privtopub(priv)
def tryopen(f):
try:
assert f
t = open(f).read()
try:
return json.loads(t)
except:
raise Exception("Corrupted file: "+f)
except:
return None
def eth_privtoaddr(priv):
pub = encode_pubkey(secure_privtopub(priv), 'bin_electrum')
return sha3(pub)[12:].encode('hex')
def getseed(encseed, pw, ethaddr):
try:
seed = aes.decryptData(pw, encseed.decode('hex'))
ethpriv = sha3(seed)
assert eth_privtoaddr(ethpriv) == ethaddr
except:
raise Exception("Decryption failed. Bad password?")
return seed
def genwallet(seed, pw, email):
encseed = aes.encryptData(pw, seed)
ethpriv = sha3(seed)
btcpriv = sha3(seed + '\x01')
ethaddr = sha3(secure_privtopub(ethpriv)[1:])[12:].encode('hex')
btcaddr = privtoaddr(btcpriv)
return {
"encseed": encseed.encode('hex'),
"ethaddr": ethaddr,
"btcaddr": btcaddr,
"email": email
}
def finalize(wallet, utxos, pw, addr=None):
seed = getseed(wallet["encseed"], pw, wallet["ethaddr"])
balance = sum([o["value"] for o in utxos])
change = 0
if addr:
sys.stderr.write("Warning: purchasing into a custom address. The wallet file generated by this script will NOT be able to access your ETH\n")
outputethaddr = addr
else:
outputethaddr = wallet["ethaddr"]
if balance == 0:
raise Exception("No funds in address")
elif balance < minimum:
raise Exception("Insufficient funds. Need at least %s BTC" %
str(minimum * 0.00000001))
elif balance > maximum:
change = balance - maximum
balance = maximum
sys.stderr.write("Too much BTC. Returning excess to intermediate address as change\n")
outs = [
exodus+':'+str(balance - 40000),
hex_to_b58check(outputethaddr)+':10000',
str(wallet["btcaddr"])+':'+str(change)
]
else:
outs = [
exodus+':'+str(balance - 30000),
hex_to_b58check(outputethaddr)+':10000',
]
tx = mktx(utxos, outs)
btcpriv = sha3(seed+'\x01')
for i in range(len(utxos)):
tx = secure_sign(tx, i, btcpriv)
return tx
def list_purchases(addr):
outs = unspent(hex_to_b58check(addr))
txs = {}
for o in outs:
if o['output'][65:] == '1':
h = o['output'][:64]
try:
txs[h] = fetchtx(h)
except:
txs[h] = blockr_fetchtx(h)
o = []
for h in txs:
txhex = txs[h]
txouts = deserialize(txhex)['outs']
if len(txouts) >= 2 and txouts[0]['value'] >= minimum - 30000:
addr = script_to_address(txouts[0]['script'])
if addr == exodus:
v = txouts[0]['value'] + 30000
o.append({"tx": h, "value": v})
return o
def ask_for_password(twice=False):
if options.pw:
return pbkdf2(options.pw)
pw = getpass.getpass()
if twice:
pw2 = getpass.getpass()
if pw != pw2:
raise Exception("Passwords do not match")
return pbkdf2(pw)
def ask_for_seed():
if options.seed:
return options.seed
else:
# uses pybitcointools' 3-source random generator
return random_key()
def checkwrite(f, thunk):
exit = False
try:
open(f)
# File already exists
if not options.overwrite:
s = "File %s already exists. Overwrite? (y/n) "
are_you_sure = raw_input(s % f)
if are_you_sure not in ['y', 'yes']:
exit = True
except:
# File does not already exist, we're fine
pass
if exit:
sys.exit()
else:
open(f, 'w').write(thunk())
w = tryopen(options.wallet)
# Generate new wallet
if not len(args):
args.append('help')
if args[0] == 'genwallet':
pw = ask_for_password(True)
email = options.email or raw_input("Please enter email: ")
newwal = genwallet(ask_for_seed(), pw, email)
checkwrite(options.wallet, lambda: json.dumps(newwal))
print("Your intermediate Bitcoin address is: %s" % newwal['btcaddr'])
print(" ")
print("Be absolutely sure to keep the wallet safe and backed up, and do not lose your password")
print(" ")
print("###########################################################################")
print("Note that the Ether sale is over, so any purchases made using this method are essentially donations to EthSuisse. This code is only kept online for the benefit of other projects and for individuals wishing to learn how the encryption on their wallet works")
print("###########################################################################")
# Get wallet Bitcoin address
elif args[0] == 'getbtcaddress':
if not w:
print("Must specify wallet with -w")
print(w["btcaddr"])
# Get wallet Ethereum address
elif args[0] == 'getethaddress':
if not w:
print("Must specify wallet with -w")
print(w["ethaddr"])
# Get wallet Bitcoin privkey
elif args[0] == 'getbtcprivkey':
if not w:
print("Must specify wallet with -w")
pw = ask_for_password()
print(encode_privkey(sha3(getseed(w['encseed'], pw,
w['ethaddr'])+'\x01'), 'wif'))
# Get wallet seed
elif args[0] == 'getseed':
if not w:
print("Must specify wallet with -w")
pw = ask_for_password()
print(getseed(w['encseed'], pw, w['ethaddr']))
# Get wallet Ethereum privkey
elif args[0] == 'getethprivkey':
if not w:
print("Must specify wallet with -w")
pw = ask_for_password()
print(encode_privkey(sha3(getseed(w['encseed'], pw, w['ethaddr'])), 'hex'))
# Recover wallet seed
elif args[0] == 'recover':
if not w:
print("Must have wallet file")
else:
pw = ask_for_password()
print("Your seed is: %s" % getseed(w['encseed'], pw, w['ethaddr']))
# Finalize a wallet
elif args[0] == 'finalize':
if not w:
raise Exception("Must specify valid wallet file!")
try:
u = unspent(w["btcaddr"])
except:
try:
u = blockr_unspent(w["btcaddr"])
except:
raise Exception("Blockchain.info and Blockr.io both down. Cannot get transaction outputs to finalize. Remember that your funds stored in the intermediate address can always be recovered by running './pyethsaletool.py getbtcprivkey' and importing the output into a Bitcoin wallet like blockchain.info")
pw = ask_for_password()
confirm = raw_input("Please confirm that you have read and understand the terms and conditions and purchase agreement (Y/N): ")
if confirm.strip() not in ['y', 'yes', 'Y', 'YES']:
print("Aborting. Docs can be found here: ")
print(" ")
print("https://www.ethereum.org/pdfs/TermsAndConditionsOfTheEthereumGenesisSale.pdf")
print("https://www.ethereum.org/pdfs/EtherProductPurchaseAgreement.pdf")
sys.exit()
if len(args) == 1:
tx = finalize(w, u, pw)
else:
# Finalize into custom address
tx = finalize(w, u, pw, args[1])
print("Pushing: %s" % tx)
try:
print(pushtx(tx))
except:
try:
print(eligius_pushtx(tx))
except:
raise Exception("Blockchain.info and Eligius both down. Cannot send transaction. Remember that your funds stored in the intermediate address can always be recovered by running './pyethsaletool.py getbtcprivkey' and importing the output into a Bitcoin wallet like blockchain.info")
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json, text/plain, */*'
}
try:
make_request('https://sale.ethereum.org/sendmail',
json.dumps({"tx": tx,
"email": w["email"],
"emailjson": w}),
headers=headers)
except:
print("Tried to send email backup automatically, but failed. "
"Please back up your wallet")
elif args[0] == "list":
if len(args) >= 2:
addr = args[1]
elif w:
addr = w["ethaddr"]
else:
raise Exception("Need to specify an address or wallet")
out = list_purchases(addr)
for o in out:
print("Tx: %s" % o["tx"])
print("Satoshis: %d" % o["value"])
print("Estimated ETH (min): %f" % (o["value"] * 1337 / 10**8))
print("Estimated ETH (max): %f" % (o["value"] * 2000 / 10**8))
# sha3 calculator
elif args[0] == 'sha3':
print(sha3(sys.argv[2]).encode('hex'))
# Help
else:
print('Use "python pyethsaletool.py genwallet" to generate a wallet')
print('Use "python pyethsaletool.py getbtcaddress" to output the intermediate Bitcoin address you need to send funds to')
print('Use "python pyethsaletool.py getbtcprivkey" to output the private key to your intermediate Bitcoin address')
print('Use "python pyethsaletool.py getethaddress" to output the Ethereum address')
print('Use "python pyethsaletool.py getethprivkey" to output the Ethereum private key')
print('Use "python pyethsaletool.py finalize" to finalize the funding process once you have deposited to the intermediate address')
print('Use "python pyethsaletool.py finalize 00c40fe2095423509b9fd9b754323158af2310f3" (or some other ethereum address) to purchase directly into some other Ethereum address')
print('Use "python pyethsaletool.py list" to list purchases made with your wallet')
print('Use "python pyethsaletool.py list 00c40fe2095423509b9fd9b754323158af2310f3" (or some other ethereum address) to list purchases made into that address')
print('Use -s to specify a seed, -w to specify a wallet file and -p to specify a password when creating a wallet. The -w, -b and -p options also work with other commands.')