-
Notifications
You must be signed in to change notification settings - Fork 14
/
btcToEther.py
executable file
·377 lines (328 loc) · 12.5 KB
/
btcToEther.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
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
#!/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
# Values used by pybitcointools bci.py for blockr network
TESTNET = 'testnet'
MAINNET = 'btc'
SATOSHI_PER_BTC = 10**8
BTC_PER_SATOSHI = 1e8
# Arguments
network = TESTNET
if network == TESTNET:
# a payment addr for testing is 'mvBWJFv8Uc84YEyZKBm8HZQ7qrvmBiH7zR'
magicbyte = 111
else:
magicbyte = 0
satoshiFee = 30000 # 0.3mBTC fee for the Bitcoin miner
# 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='btcToEtherWallet.json', dest='wallet')
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):
seed = aes.decryptData(pw, encseed.decode('hex'))
ethpriv = sha3(seed)
if eth_privtoaddr(ethpriv) != ethaddr:
raise Exception("Ethereum address provided to getseed does not match!")
return seed
def genwallet(seed, pw):
encseed = aes.encryptData(pw, seed)
ethpriv = sha3(seed)
btcpriv = sha3(seed + '\x01')
ethaddr = sha3(secure_privtopub(ethpriv)[1:])[12:].encode('hex')
btcaddr = privkey_to_address(btcpriv, magicbyte)
return {
"encseed": encseed.encode('hex'),
"ethaddr": ethaddr,
"btcaddr": btcaddr
}
# etherFeePercent is a string in format xx.xx eg 12.34
def makeAndSignTx(wallet, utxos, pw, ether_fee_percent, btc_amount, btc_addr, eth_addr=None):
seed = getseed(wallet["encseed"], pw, wallet["ethaddr"])
balance = sum([o["value"] for o in utxos])
change = 0
minimum = float(btc_amount) * SATOSHI_PER_BTC + satoshiFee
if eth_addr:
sys.stderr.write("Warning: purchasing into a custom ether address.\n" \
"The wallet file generated by this script will NOT be able to access your ETH\n" \
"Make sure the ether address is correct: %s" % eth_addr)
outputethaddr = eth_addr
else:
outputethaddr = wallet["ethaddr"]
if balance == 0:
raise Exception("\n\n**********************************************\n" \
"No funds. Need to send {0} BTC to your intermediate\n" \
"address {1}. (includes {2} BTC miner fee)" \
"\n**********************************************\n"
.format(
minimum / BTC_PER_SATOSHI,
wallet["btcaddr"],
satoshiFee / BTC_PER_SATOSHI))
elif balance < minimum:
raise Exception("\n\n**********************************************\n" \
"Insufficient funds. Total needed is {0} BTC (includes {1} BTC miner fee).\n" \
"Intermediate address {2} only has {3} BTC." \
"\n**********************************************\n"
.format(
minimum / BTC_PER_SATOSHI,
satoshiFee / BTC_PER_SATOSHI,
wallet["btcaddr"],
balance))
else:
outs = [
btc_addr+':'+str(balance - satoshiFee),
hex_to_b58check(outputethaddr)+':1'+ether_fee_percent.replace('.', ''),
]
tx = mktx(utxos, outs)
btcpriv = sha3(seed+'\x01')
for i in range(len(utxos)):
tx = secure_sign(tx, i, btcpriv)
return tx
# TODO global minimum removed
def list_purchases(addr):
if network == TESTNET:
outs = blockr_unspent(hex_to_b58check(addr, magicbyte), network)
else:
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, network)
o = []
for h in txs:
txhex = txs[h]
txhex = txhex.encode('ascii')
txouts = deserialize(txhex)['outs']
if len(txouts) >= 2 and txouts[0]['value'] >= minimum - satoshiFee:
addr = script_to_address(txouts[0]['script'], magicbyte)
if addr == exodus:
v = txouts[0]['value'] + satoshiFee
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)
newwal = genwallet(ask_for_seed(), pw)
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")
# 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':
pw = ask_for_password()
print(encode_privkey(sha3(getseed(w['encseed'], pw,
w['ethaddr'])+'\x01'), 'wif'))
# Get wallet seed
elif args[0] == 'getseed':
pw = ask_for_password()
print(getseed(w['encseed'], pw, w['ethaddr']))
# Get wallet Ethereum privkey
elif args[0] == 'getethprivkey':
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']))
# Create the raw transaction for reserving and claiming an ether ticket
elif args[0] == 'makeAndSignTx':
if len(args) < 4:
raise Exception("Arguments needed <etherFeePercent> <bitcoinAmount> <bitcoinAddress")
if not re.match('\d\d\.\d\d', args[1]):
raise Exception("Ether fee percentage format is xx.xx, example 09.80 for 9.8%")
try:
btc_amount = float(args[2])
except ValueError, e:
raise Exception("<bitcoinAmount> must be numeric")
try:
btc_addr = b58check_to_hex(args[3])
except AssertionError, e:
raise Exception("<bitcoinAddress> is not valid")
if not w:
raise Exception("Must specify valid wallet file!")
try:
u = unspent(w["btcaddr"])
except:
try:
u = blockr_unspent(w["btcaddr"], network)
except:
raise Exception("Blockchain.info and Blockr.io both down. Cannot get transaction outputs to makeAndSignTx. Remember that your funds stored in the intermediate address can always be recovered by running 'python btcToEther.py getbtcprivkey' and importing the output into a Bitcoin wallet like blockchain.info")
pw = ask_for_password()
confirm = raw_input("Do you want to makeAndSignTx? (y/n): ")
if confirm.strip() not in ['y', 'yes', 'Y', 'YES']:
print("Aborting as you requested")
sys.exit()
ether_fee_percent = args[1]
btc_amount = args[2]
btc_addr = args[3]
if len(args) == 4:
tx = makeAndSignTx(w, u, pw, ether_fee_percent, btc_amount, btc_addr)
else:
tx = makeAndSignTx(w, u, pw, ether_fee_percent, btc_amount, btc_addr, args[4])
# print("Pushing: %s" % tx)
# try:
# if network == TESTNET:
# blockr_pushtx(tx, network)
# else:
# 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 'python btcToEther.py getbtcprivkey' and importing the output into a Bitcoin wallet like blockchain.info")
print("\n\nHere is the Transaction Hash for reserving the ether ticket:\n%s" % txhash(tx))
print("\nAFTER the ticket is reserved, this is the transaction to broadcast:\n%s" % tx)
print("\nHuman readable tx:\n%s" % deserialize(tx))
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)
if len(out) == 0:
print("No purchases found. (You may need to wait for some bitcoin confirmations.)")
for o in out:
print("Tx: %s" % o["tx"])
# sha3 calculator
elif args[0] == 'sha3':
print(sha3(sys.argv[2]).encode('hex'))
# Help
else:
print('Use "python btcToEther.py genwallet" to generate a wallet')
print('Use "python btcToEther.py getbtcaddress" to output the intermediate Bitcoin address you need to send funds to')
print('Use "python btcToEther.py getbtcprivkey" to output the private key to your intermediate Bitcoin address')
print('Use "python btcToEther.py getethaddress" to output the Ethereum address')
print('Use "python btcToEther.py getethprivkey" to output the Ethereum private key')
print('Use "python btcToEther.py makeAndSignTx <etherFeePercent> <bitcoinAmount> <bitcoinAddress>" to create the raw transaction once you have deposited to the intermediate address')
print('Use "python btcToEther.py makeAndSignTx <etherFeePercent> <bitcoinAmount> <bitcoinAddress> <ether address>" to purchase directly into some other Ethereum address')
print('Use "python btcToEther.py list" to list purchases made with your wallet')
print('Use "python btcToEther.py list <ether 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.')