forked from harrigan/bitcointools
-
Notifications
You must be signed in to change notification settings - Fork 11
/
wallet.py
300 lines (272 loc) · 10.6 KB
/
wallet.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
#!/usr/bin/env python3
#
# Copyright (c) 2010 Gavin Andresen
# Copyright (c) 2018 John Newbery
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Code for parsing the wallet.dat file"""
from base58 import public_key_to_bc_address
import logging
import sys
from bsddb3.db import ( # pip3 install bsddb3. Requires berkeley-db to be installed.
DB,
DBEnv,
DBError,
DBNoSuchFileError,
DB_BTREE,
DB_CREATE,
DB_INIT_LOCK,
DB_INIT_LOG,
DB_INIT_MPOOL,
DB_INIT_TXN,
DB_RDONLY,
DB_RECOVER,
DB_THREAD,
)
from datastructures import (
Account,
AccountingEntry,
BlockLocator,
HDChain,
KeyMeta,
KeyPool,
PrivateKey,
WalletTransaction,
)
from serialize import BCBytesStream, SerializationError
from util import determine_datadir
VERSION_HD_CHAIN_SPLIT = 2
VERSION_WITH_HDDATA = 10
def create_env(db_dir=None):
if db_dir is None:
db_dir = determine_datadir()
db_env = DBEnv(0)
db_env.open(db_dir, DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN | DB_THREAD | DB_RECOVER)
return db_env
class Wallet():
"""Represents contents of wallet.dat file.
accounting_entries: TODO
accounts: TODO
default_key: TODO
hd_chain: TODO
key_meta: TODO
keys: TODO
minimum_version: TODO
names: TODO
orderposnext: TODO
owner_keys: TODO
pool: TODO
purposes: TODO
records: TODO
version: TODO
wallet_transactions: TODO
wkeys: TODO."""
def __init__(self, wallet_dir, name):
self.accounting_entries = []
self.accounts = {}
self.default_key = b''
self.hd_chain = None
self.key_meta = []
self.keys = {}
self.minimum_version = 0
self.names = {}
self.orderposnext = 0
self.owner_keys = {}
self.pool = {}
self.purposes = {}
self.records = []
self.version = 0
self.wallet_transactions = []
self.wkeys = {}
try:
self.db_env = create_env(wallet_dir)
except DBNoSuchFileError:
logging.error("Couldn't open " + wallet_dir)
sys.exit(1)
self.open_wallet(name)
self.parse_wallet()
def __repr__(self):
ret = "version: {}\n".format(self.version)
ret += "minimum_version: {}\n".format(self.minimum_version)
ret += "orderposnext: {}\n".format(self.orderposnext)
if self.best_block:
ret += "best_block: {}".format(self.best_block.__repr__())
if self.best_block_no_merkle:
ret += "best_block_no_merkle: {}".format(self.best_block_no_merkle.__repr__())
if self.default_key != b'':
ret += "default_key: {}\n".format(self.default_key)
if self.hd_chain:
ret += "hd_chain:\n {}\n".format(self.hd_chain)
if self.keys:
ret += "keys:\n"
ret += "\n".join([" pub_key: {}, address: {}, priv_key: {}".format(pub_key.hex(), public_key_to_bc_address(pub_key), priv_key.__repr__()) for pub_key, priv_key in self.keys.items()])
ret += "\n"
if self.wkeys:
ret += "wkeys:\n"
ret += "\n".join([" {}".format(wkey) for wkey in self.wkeys])
ret += "\n"
if self.names:
ret += "names:\n"
ret += "\n".join([" address: {}, name: {}".format(address, name) for name, address in self.names.items()])
ret += "\n"
if self.purposes:
ret += "purposes:\n"
ret += "\n".join([" address: {}, purpose: {}".format(purpose, address) for purpose, address in self.purposes.items()])
ret += "\n"
if self.pool:
ret += "key_pool:\n"
ret += "\n".join([" {}. {}".format(n, key_pool) for n, key_pool in self.pool.items()])
ret += "\n"
if self.accounts:
ret += "accounts:\n"
ret += "\n".join([" {}: {}".format(account_name, account) for account_name, account in self.accounts.items()])
ret += "\n"
if self.accounting_entries:
ret += "accounting entries:\n"
ret += "\n".join([" {}".format(account_entry) for account_entry in self.accounting_entries])
ret += "\n"
if self.key_meta:
ret += "key metadata:\n"
ret += "\n".join([" {}".format(key_meta) for key_meta in self.key_meta])
ret += "\n"
return ret
def open_wallet(self, name, writable=False):
self.db = DB(self.db_env)
flags = DB_THREAD | (DB_CREATE if writable else DB_RDONLY)
try:
r = self.db.open(name, "main", DB_BTREE, flags)
except DBError:
r = True
if r is not None:
logging.error("Couldn't open wallet.dat/main. Try quitting Bitcoin and running this again.")
sys.exit(1)
def parse_wallet(self):
"""Parse a berkeley db .dat wallet file.
A bekeley db .dat wallet file is a key-value store, with the following possible keys:
- acc: account information. key is "acc"+string account name. Value is an Account object.
acc entries are not read on startup, only when the deprecated `setaccount` and
`getaccountaddress` RPCs are called.
- acentry: TODO
- bestblock: TODO
- bestblock_nomerkle: TODO
- ckey: TODO
- cscript: TODO
- defaultkey: TODO
- destdata: TODO
- hdchain: TODO
- key: TODO
- keymeta: TODO
- minversion: TODO
- mkey: TODO
- name: TODO
- orderposnext: TODO
- pool: TODO
- purpose: TODO
- tx: TODO
- version: TODO
- watchmeta: TODO
- watchs: TODO
- wkey: TODO"""
for (key, value) in self.db.items():
d = {}
kds = BCBytesStream(key)
vds = BCBytesStream(value)
t = kds.deser_string()
d["__key__"] = key
d["__value__"] = value
d["__type__"] = t
try:
if t == "acc":
account = Account()
account.deserialize(vds)
self.accounts[kds.deser_string()] = account
elif t == "acentry":
account_entry = AccountingEntry()
account_entry.deserialize(vds)
account_entry.account = kds.deser_string()
account_entry.index = kds.deser_uint64()
self.accounting_entries.append(account_entry)
elif t == "bestblock":
best_block = BlockLocator()
best_block.deserialize(vds)
self.best_block = best_block
elif t == "bestblock_nomerkle":
best_block_no_merkle = BlockLocator()
best_block_no_merkle.deserialize(vds)
self.best_block_no_merkle = best_block_no_merkle
elif t == "ckey":
pass # TODO: parse ckey entries
elif t == "cscript":
pass # TODO: parse cscript entries
elif t == "defaultkey":
self.default_key = vds.read(vds.deser_compact_size())
elif t == "hdchain":
hd_chain = HDChain()
hd_chain.deserialize(vds)
self.hd_chain = hd_chain
elif t == "key":
public_key = kds.read(kds.deser_compact_size())
private_key = PrivateKey()
private_key.deserialize(vds)
self.keys[public_key] = private_key
self.owner_keys[public_key_to_bc_address(public_key)] = private_key
elif t == "keymeta":
key_metadata = KeyMeta()
key_metadata.deserialize(vds)
self.key_meta.append(key_metadata)
elif t == "minversion":
self.minimum_version = vds.deser_uint32()
elif t == "mkey":
pass # TODO: parse mkey entries
elif t == "name":
self.names[vds.deser_string()] = kds.deser_string()
elif t == "orderposnext":
self.orderposnext = vds.deser_int64()
elif t == "pool":
keypool = KeyPool()
keypool.deserialize(vds)
self.pool[kds.deser_int64()] = keypool
elif t == "purpose":
self.purposes[kds.deser_string()] = vds.deser_string()
elif t == "tx":
tx_id = kds.read(32)
tx = WalletTransaction()
tx.deserialize(vds)
tx.tx_id = tx_id
self.wallet_transactions.append(tx)
elif t == "version":
self.version = vds.deser_uint32()
elif t == "watchmeta":
pass # TODO: parse watchmeta entries
elif t == "watchs":
pass # TODO: parse watchs entries
elif t == "wkey":
public_key = kds.read(kds.deser_compact_size())
private_key = vds.read(vds.deser_compact_size())
created = vds.deser_int64()
expires = vds.deser_int64()
comment = vds.deser_string()
self.wkeys.append({'pubkey': public_key, 'priv_key': private_key, 'created': created, 'expiers': expires, 'comment': comment})
else:
print("ERROR parsing wallet.dat, type %s" % t)
self.records.append(d)
except Exception as e:
print("ERROR parsing wallet.dat, type %s" % t)
print("key data in hex: {}".format(key.hex()))
print("value data in hex: {}".format(value.hex()))
raise
self.wallet_transactions.sort(key=lambda i: i.time_received)
self.key_meta.sort(key=lambda i: i.hd_key_path)
def close(self):
"""Close the database and database environment."""
self.db.close()
self.db_env.close()
def dump_wallet(wallet, print_wallet, print_wallet_transactions):
if print_wallet:
print(wallet)
for d in wallet.records:
print("Unknown key type: {}".format(d["__type__"]))
print("value data in hex: {}".format(d["__value__"]))
if print_wallet_transactions:
print("wallet transactions:")
print(" \n".join([" {}".format(tx) for tx in wallet.wallet_transactions]))