-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathcreatewallets.py
338 lines (305 loc) · 15.4 KB
/
createwallets.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
# -*- coding: iso-8859-15 -*-
#
# Bulk Paper Wallets
#
# Generate Bitcoin Paper Wallets in Bulk and fund them. Wallets will be saved as PDF files.
# © 2017 - 2018 December - 1200 Web Development <http://1200wd.com/>
#
# Published under GNU GENERAL PUBLIC LICENSE see LICENSE file for more details.
# WARNING: This software is still under development, only use if you understand the code and known what you are doing.
# So use at your own risk!
#
import sys
import os
import argparse
import binascii
import qrcode
import pdfkit
import csv
from jinja2 import Template
from bitcoinlib.wallets import HDWallet, wallet_exists, wallet_delete, wallets_list
from bitcoinlib.keys import HDKey
from bitcoinlib.mnemonic import Mnemonic
from bitcoinlib.networks import Network
from bitcoinlib.services.services import Service
try:
input = raw_input
except NameError:
pass
DEFAULT_NETWORK = 'bitcoin'
DEFAULT_WALLET_NAME = "Bulk Paper Wallet"
INSTALL_DIR = os.path.dirname(os.path.abspath(__file__))
WALLET_DIR = os.path.join(INSTALL_DIR, 'wallets')
if not os.path.exists(WALLET_DIR):
os.makedirs(WALLET_DIR)
OUTPUT_ACCOUNT_ID = 1
pdfkit_options = {
'page-size': 'A4',
'margin-top': '0.25in',
'margin-right': '0.25in',
'margin-bottom': '0.25in',
'margin-left': '0.25in',
'encoding': "UTF-8",
}
class BulkPaperWallet(HDWallet):
def create_paper_wallets(self, output_keys, style_file, template_file, image_size_factor=1):
count = 0
for wallet_key in output_keys:
address_img = qrcode.make(wallet_key.address)
filename_pre = "%s/%d-" % (WALLET_DIR, wallet_key.key_id)
address_img.save(filename_pre+'address.png', 'PNG')
private_wif = wallet_key.key().key.wif()
priv_img = qrcode.make(private_wif)
priv_img.save(filename_pre+'privatekey.png', 'PNG')
passphrase = Mnemonic().to_mnemonic(wallet_key._hdkey_object.private_byte)
f = open('templates/'+template_file, 'r')
template = Template(f.read())
wallet_name = self.name
wallet_str = template.render(
install_dir=INSTALL_DIR,
filename_pre=filename_pre,
wallet_name=wallet_name,
private_key=private_wif,
passphrase=passphrase,
address=wallet_key.address,
currency_name=self.network.currency_name,
currency_name_plural=self.network.currency_name_plural,
image_size_factor=image_size_factor)
print("Generate wallet %d" % wallet_key.key_id)
pdfkit.from_string(wallet_str, filename_pre+'wallet.pdf', options=pdfkit_options, css=style_file)
count += 1
print("A total of %d paper wallets have been created" % count)
@classmethod
def create(cls, name, keys='', owner='', network=None, account_id=0, purpose=0, scheme='bip32', parent_id=None,
sort_keys=True, password='', witness_type='legacy', encoding=None, multisig=None,
cosigner_id=None, key_path=None, databasefile=None):
return super(BulkPaperWallet, cls).create(
name=name, keys=keys, network=network, account_id=account_id,
purpose=purpose, scheme=scheme, parent_id=parent_id, sort_keys=sort_keys, password=password,
witness_type=witness_type, encoding=encoding, multisig=multisig, cosigner_id=cosigner_id, key_path=key_path,
databasefile=databasefile)
def parse_args():
parser = argparse.ArgumentParser(description='Create Bulk Paper Wallets')
parser.add_argument('--wallet-name', '-w', default=DEFAULT_WALLET_NAME,
help="Name of wallet to create or open. Used to store your all your wallet keys "
"and will be printed on each paper wallet")
parser.add_argument('--network', '-n', help="Specify 'bitcoin', 'litecoin', 'testnet' or other supported network",
default=DEFAULT_NETWORK)
group1 = parser.add_mutually_exclusive_group()
group1.add_argument('--outputs', '-o', nargs="*", type=float,
help="List of output values. For example '-o 1 2 3' creates 3 wallets with a value of "
"1, 2 and 3 bitcoin successively")
group1.add_argument('--outputs-import', '-f',
help="Filename of comma seperated value list of output values and optional wallet names. "
"Example: 1.51, John")
parser.add_argument('--outputs-repeat', '-r', type=int,
help="Repeat the outputs OUTPUTS_REPEAT times. For example 'createwallet.py -o 5 -r 10' "
"will create 10 wallets with 5 bitcoin")
parser.add_argument('--wallet-remove',
help="Name of wallet to remove, all keys and related information will be deleted")
parser.add_argument('--print', '-p', action='store_true',
help="Print wallets, skip check for funds on input address")
parser.add_argument('--passphrase-strength', type=int, default=128,
help="Number of bits for passphrase key")
parser.add_argument('--list-wallets', '-l', action='store_true',
help="List all known wallets in bitcoinlib database")
parser.add_argument('--wallet-info', '-i', action='store_true',
help="Show wallet information")
parser.add_argument('--recover-wallet-passphrase',
help="Passphrase - sequence of words - to recover and regenerate a previous wallet")
parser.add_argument('--test-pdf', action='store_true',
help="Generate a single preview PDF paper wallet. Contains dummy keys")
parser.add_argument('--style', '-s', default='style.css',
help="Specify style sheet file")
parser.add_argument('--template', '-t', default='default.html',
help="Specify wallet template html file")
parser.add_argument('--image-size', type=int, default=1,
help="Image size factor in paper wallets")
parser.add_argument('--witness-type', '-y', default='legacy',
help='Wallet witness type, can be legacy, p2sh-segwit or segwit. Default is legacy. ')
parser.add_argument('--fee-per-kb', '-k', type=int,
help="Fee in Satoshi's per kilobyte")
pa = parser.parse_args()
if pa.outputs_repeat and pa.outputs is None:
parser.error("--output_repeat requires --outputs")
if not pa.wallet_remove and not pa.list_wallets and not pa.wallet_info and not pa.recover_wallet_passphrase \
and not pa.test_pdf and not (pa.outputs or pa.outputs_import):
parser.error("Either --outputs or --outputs-import should be specified")
return pa
if __name__ == '__main__':
# --- Parse commandline arguments ---
args = parse_args()
wallet_name = args.wallet_name
network = args.network
network_obj = Network(network)
style_file = args.style
template_file = args.template
# List wallets, then exit
if args.list_wallets:
print("\nBitcoinlib wallets:")
for w in wallets_list():
print(w['name'])
print("\n")
sys.exit()
if args.wallet_info:
print("Wallet info for %s" % args.wallet_name)
if wallet_exists(args.wallet_name):
wallet = BulkPaperWallet(args.wallet_name)
# wallet.utxos_update()
wallet.info()
else:
raise ValueError("Wallet '%s' not found" % args.wallet_name)
sys.exit()
# Delete specified wallet, then exit
if args.wallet_remove:
if not wallet_exists(args.wallet_remove):
print("Wallet '%s' not found" % args.wallet_remove)
sys.exit()
inp = input("\nWallet '%s' with all keys and will be removed, without private key it cannot be restored."
"\nPlease retype exact name of wallet to proceed: " % args.wallet_remove)
if inp == args.wallet_remove:
if wallet_delete(args.wallet_remove, force=True):
print("\nWallet %s has been removed" % args.wallet_remove)
else:
print("\nError when deleting wallet")
sys.exit()
# Generate a test wallet preview PDF
if args.test_pdf:
if wallet_exists('BPW_pdf_test_tmp'):
wallet = BulkPaperWallet('BPW_pdf_test_tmp')
else:
wallet_obj = BulkPaperWallet
wallet = wallet_obj.create(name='BPW_pdf_test_tmp', network=network)
test_key = wallet.get_key()
wallet.create_paper_wallets([test_key], style_file, template_file, args.image_size)
wallet_delete('BPW_pdf_test_tmp')
sys.exit()
# --- Create or open wallet ---
if wallet_exists(wallet_name):
if args.recover_wallet_passphrase:
print("\nWallet %s already exists. Please specify (not existing) wallet name for wallet to recover" %
wallet_name)
sys.exit()
wallet = BulkPaperWallet(wallet_name)
if wallet.network.name != args.network:
print("\nNetwork setting (%s) ignored. Using network from defined wallet instead: %s" %
(args.network, wallet.network.name))
network = wallet.network.name
network_obj = Network(network)
print("\nOpen wallet '%s' (%s network)" % (wallet_name, network))
else:
print("\nCREATE wallet '%s' (%s network)" % (wallet_name, network))
if not args.recover_wallet_passphrase:
words = Mnemonic('english').generate(args.passphrase_strength)
print("\nYour mnemonic private key sentence is: %s" % words)
print("\nPlease write down on paper and backup. With this key you can restore all paper wallets if "
"something goes wrong during this process. You can / have to throw away this private key after "
"the paper wallets are distributed.")
inp = input("\nType 'yes' if you understood and wrote down your key: ")
if inp not in ['yes', 'Yes', 'YES']:
print("Exiting...")
sys.exit()
else:
words = args.recover_wallet_passphrase
seed = binascii.hexlify(Mnemonic().to_seed(words))
hdkey = HDKey().from_seed(seed, network=network)
wallet = BulkPaperWallet.create(name=wallet_name, network=network, keys=hdkey, witness_type=args.witness_type)
wallet.new_key("Input")
wallet.new_account("Outputs", account_id=OUTPUT_ACCOUNT_ID)
if args.recover_wallet_passphrase:
print("Wallet recovered, now updating keys and balances...")
stuff_updated = True
while stuff_updated:
for kn in range(0, 10):
wallet.new_key(account_id=OUTPUT_ACCOUNT_ID)
wallet.new_key_change(account_id=OUTPUT_ACCOUNT_ID)
stuff_updated = wallet.utxos_update()
wallet.info()
sys.exit()
# --- Create array with outputs ---
outputs = []
if args.outputs_import:
print("Import outputs from .csv file")
with open(args.outputs_import, mode='r') as f:
reader = csv.reader(f)
for row in reader:
assert len(row) == 2, "Row length is not 2 or row %s, row should have format: amount, name" % row
f.seek(0)
outputs = [{
'amount': float(row[0]),
'name': row[1]} for row in reader]
print("Successfully imported %d outputs" % len(outputs))
else:
output_list = [{'amount': o, 'name': ''} for o in args.outputs]
repeat_n = 1
if args.outputs_repeat:
repeat_n = args.outputs_repeat
for r in range(0, repeat_n):
outputs += output_list
outputs_arr = []
output_keys = []
total_amount = 0
denominator = float(network_obj.denominator)
for o in outputs:
nk = wallet.new_key(account_id=OUTPUT_ACCOUNT_ID, name=o['name'].lstrip())
output_keys.append(nk)
amount = int(o['amount'] * (1/denominator))
outputs_arr.append((nk.address, amount))
total_amount += amount
# --- Estimate transaction fees ---
srv = Service(network=network)
if args.fee_per_kb:
fee_per_kb = args.fee_per_kb
else:
fee_per_kb = srv.estimatefee()
if not srv.results:
raise IOError("No response from services, could not determine estimated transaction fees. "
"You can use --fee-per-kb option to determine fee manually and avoid this error.")
tr_size = 100 + (1 * 150) + (len(outputs_arr) * 50)
estimated_fee = int((tr_size / 1024) * fee_per_kb)
if estimated_fee < 0:
raise IOError("No valid response from any service provider, could not determine estimated transaction fees. "
"You can use --fee-per-kb option to determine fee manually and avoid this error.")
print("Estimated fee is for this transaction is %s (%d satoshis/kb)" %
(network_obj.print_value(estimated_fee), fee_per_kb))
print("Total value of outputs is %s" % network_obj.print_value(total_amount))
total_transaction = total_amount + estimated_fee
# --- Check for UTXO's and create transaction and Paper wallets
input_key = wallet.keys(name="Input", is_active=None)
if not input_key:
print("No valid input key found. Is this wallet created with BulkPaperWallets?")
sys.exit()
input_key = input_key[0]
wallet.utxos_update(account_id=0)
print("\nTotal wallet balance: %s" % wallet.balance(as_string=True))
enough_balance = bool(input_key.balance >= total_transaction)
if not enough_balance and not args.print:
remaining_balance = total_transaction - input_key.balance
file_inputcode = os.path.join(WALLET_DIR, str(wallet.wallet_id) + '-input-address-qrcode.png')
networklink = network
if networklink == 'testnet':
networklink = 'bitcoin'
paymentlink = '%s:%s?amount=%.8f' % (networklink, input_key.address, remaining_balance*denominator)
ki_img = qrcode.make(paymentlink)
ki_img.save(file_inputcode, 'PNG')
print("\nNot enough funds in wallet to create transaction.\nPlease transfer %s to "
"address %s and restart this program with EXACTLY the same options.\nYou can find a QR code in %s" %
(network_obj.print_value(remaining_balance), input_key.address, file_inputcode))
else:
if enough_balance:
print("\nEnough input(s) to spent found, ready to create wallets and transaction")
else:
print("\nNot enough balance found. Create wallets anyway because --print option was specified.")
if not args.template and not args.style:
print("\nHave you created test-wallet PDFs to check page formatting with the '--test-pdf' option? "
"You can change font and image size with the --template and --style options")
inp = input("\nType 'y' to continue: ")
if inp not in ['y', 'Y']:
print("Exiting...")
sys.exit()
wallet.create_paper_wallets(output_keys, style_file, template_file, args.image_size)
if not args.print:
t = wallet.send(outputs_arr, account_id=0, fee=estimated_fee, min_confirms=0)
print("\nTransaction pushed to the network, transaction information / result:")
t.info()
print("\nPaper wallets are created and can be found in the %s directory" % WALLET_DIR)