2
Created on Aug 14, 2013
8
from pytest.Tiab import TiabTest, FIRST_WLT_NAME, SECOND_WLT_NAME
12
from armoryengine.MultiSigUtils import readLockboxesFile
13
from CppBlockUtils import SecureBinaryData
14
from armoryengine.ArmoryUtils import convertKeyDataToAddress, \
15
hash256, binary_to_hex, hex_to_binary, CLI_OPTIONS, \
16
WalletLockError, InterruptTestError, MULTISIG_FILE_NAME
17
from armoryengine.PyBtcWallet import PyBtcWallet
18
from armoryengine.BDM import TheBDM
21
sys.argv.append('--nologging')
24
WALLET_ROOT_ADDR = '5da74ed60a43a7ff11f0ba56cb0192b03518cc56'
25
NEW_UNUSED_ADDR = 'fb80e6fd042fa24178b897a6a70e1ae7eb56a20a'
27
class PyBtcWalletTest(TiabTest):
30
self.shortlabel = 'TestWallet1'
31
self.wltID ='3VB8XSoY'
33
self.fileA = os.path.join(self.armoryHomeDir, 'armory_%s_.wallet' % self.wltID)
34
self.fileB = os.path.join(self.armoryHomeDir, 'armory_%s_backup.wallet' % self.wltID)
35
self.fileAupd = os.path.join(self.armoryHomeDir, 'armory_%s_backup_unsuccessful.wallet' % self.wltID)
36
self.fileBupd = os.path.join(self.armoryHomeDir, 'armory_%s_update_unsuccessful.wallet' % self.wltID)
38
self.removeFileList([self.fileA, self.fileB, self.fileAupd, self.fileBupd])
40
# We need a controlled test, so we script the all the normally-random stuff
41
self.privKey = SecureBinaryData('\xaa'*32)
42
self.privKey2 = SecureBinaryData('\x33'*32)
43
self.chainstr = SecureBinaryData('\xee'*32)
44
theIV = SecureBinaryData(hex_to_binary('77'*16))
45
self.passphrase = SecureBinaryData('A self.passphrase')
46
self.passphrase2 = SecureBinaryData('A new self.passphrase')
48
self.wlt = PyBtcWallet().createNewWallet(withEncrypt=False, \
49
plainRootKey=self.privKey, \
50
chaincode=self.chainstr, \
52
shortLabel=self.shortlabel,
53
armoryHomeDir = self.armoryHomeDir)
56
self.removeFileList([self.fileA, self.fileB, self.fileAupd, self.fileBupd])
59
# *********************************************************************
60
# Testing deterministic, encrypted wallet features'
61
# *********************************************************************
62
def removeFileList(self, fileList):
67
def testBackupWallet(self):
68
backupTestPath = os.path.join(self.armoryHomeDir, 'armory_%s_.wallet.backup.test' % self.wltID)
69
# Remove backupTestPath in case it exists
70
backupFileList = [backupTestPath, self.fileB]
71
self.removeFileList(backupFileList)
72
# Remove the backup test path that is to be created after tear down.
73
self.addCleanup(self.removeFileList, backupFileList)
74
self.wlt.backupWalletFile(backupTestPath)
75
self.assertTrue(os.path.exists(backupTestPath))
76
self.wlt.backupWalletFile()
77
self.assertTrue(os.path.exists(self.fileB))
79
def testIsWltSigningAnyLockbox(self):
80
lockboxList = readLockboxesFile(os.path.join(self.armoryHomeDir, MULTISIG_FILE_NAME))
81
self.assertFalse(self.wlt.isWltSigningAnyLockbox(lockboxList))
83
lboxWltAFile = os.path.join(self.armoryHomeDir,'armory_%s_.wallet' % FIRST_WLT_NAME)
84
lboxWltA = PyBtcWallet().readWalletFile(lboxWltAFile, doScanNow=True)
85
self.assertTrue(lboxWltA.isWltSigningAnyLockbox(lockboxList))
87
lboxWltBFile = os.path.join(self.armoryHomeDir,'armory_%s_.wallet' % SECOND_WLT_NAME)
88
lboxWltB = PyBtcWallet().readWalletFile(lboxWltBFile, doScanNow=True)
89
self.assertTrue(lboxWltB.isWltSigningAnyLockbox(lockboxList))
91
# Remove wallet files, need fresh dir for this test
92
def testPyBtcWallet(self):
94
self.wlt.addrPoolSize = 5
95
# No block chain loaded so this should return -1
96
# self.assertEqual(self.wlt.detectHighestUsedIndex(True), -1)
97
self.assertEqual(self.wlt.kdfKey, None)
98
self.assertEqual(binary_to_hex(self.wlt.addrMap['ROOT'].addrStr20), WALLET_ROOT_ADDR )
100
#############################################################################
101
# (1) Getting a new address:
102
newAddr = self.wlt.getNextUnusedAddress()
103
self.wlt.pprint(indent=' '*5)
104
self.assertEqual(binary_to_hex(newAddr.addrStr20), NEW_UNUSED_ADDR)
106
# (1) Re-reading wallet from file, compare the two wallets
107
wlt2 = PyBtcWallet().readWalletFile(self.wlt.walletPath)
108
self.assertTrue(self.wlt.isEqualTo(wlt2))
110
#############################################################################
111
# Test locking an unencrypted wallet does not lock
112
self.assertFalse(self.wlt.useEncryption)
114
self.assertFalse(self.wlt.isLocked)
115
# (2)Testing unencrypted wallet import-address'
116
originalLength = len(self.wlt.linearAddr160List)
117
self.wlt.importExternalAddressData(privKey=self.privKey2)
118
self.assertEqual(len(self.wlt.linearAddr160List), originalLength+1)
120
# (2) Re-reading wallet from file, compare the two wallets
121
wlt2 = PyBtcWallet().readWalletFile(self.wlt.walletPath)
122
self.assertTrue(self.wlt.isEqualTo(wlt2))
124
# (2a)Testing deleteImportedAddress
125
# Wallet size before delete:', os.path.getsize(self.wlt.walletPath)
126
# Addresses before delete:', len(self.wlt.linearAddr160List)
127
toDelete160 = convertKeyDataToAddress(self.privKey2)
128
self.wlt.deleteImportedAddress(toDelete160)
129
self.assertEqual(len(self.wlt.linearAddr160List), originalLength)
132
# (2a) Reimporting address for remaining tests
133
# Wallet size before reimport:', os.path.getsize(self.wlt.walletPath)
134
self.wlt.importExternalAddressData(privKey=self.privKey2)
135
self.assertEqual(len(self.wlt.linearAddr160List), originalLength+1)
138
# (2b)Testing ENCRYPTED wallet import-address
139
privKey3 = SecureBinaryData('\xbb'*32)
140
privKey4 = SecureBinaryData('\x44'*32)
141
self.chainstr2 = SecureBinaryData('\xdd'*32)
142
theIV2 = SecureBinaryData(hex_to_binary('66'*16))
143
self.passphrase2= SecureBinaryData('hello')
144
wltE = PyBtcWallet().createNewWallet(withEncrypt=True, \
145
plainRootKey=privKey3, \
146
securePassphrase=self.passphrase2, \
147
chaincode=self.chainstr2, \
149
shortLabel=self.shortlabel,
150
armoryHomeDir = self.armoryHomeDir)
152
# We should have thrown an error about importing into a locked wallet...
153
self.assertRaises(WalletLockError, wltE.importExternalAddressData, privKey=self.privKey2)
157
wltE.unlock(securePassphrase=self.passphrase2)
158
wltE.importExternalAddressData(privKey=self.privKey2)
160
# (2b) Re-reading wallet from file, compare the two wallets
161
wlt2 = PyBtcWallet().readWalletFile(wltE.walletPath)
162
self.assertTrue(wltE.isEqualTo(wlt2))
164
# (2b) Unlocking wlt2 after re-reading locked-import-wallet
165
wlt2.unlock(securePassphrase=self.passphrase2)
166
self.assertFalse(wlt2.isLocked)
168
#############################################################################
169
# Now play with encrypted wallets
170
# *********************************************************************'
171
# (3)Testing conversion to encrypted wallet
173
kdfParams = self.wlt.computeSystemSpecificKdfParams(0.1)
174
self.wlt.changeKdfParams(*kdfParams)
176
self.assertEqual(self.wlt.kdf.getSalt(), kdfParams[2])
177
self.wlt.changeWalletEncryption( securePassphrase=self.passphrase )
178
self.assertEqual(self.wlt.kdf.getSalt(), kdfParams[2])
180
# (3) Re-reading wallet from file, compare the two wallets'
181
wlt2 = PyBtcWallet().readWalletFile(self.wlt.getWalletPath())
182
self.assertTrue(self.wlt.isEqualTo(wlt2))
183
# NOTE: this isEqual operation compares the serializations
184
# of the wallet addresses, which only contains the
185
# encrypted versions of the private keys. However,
186
# self.wlt is unlocked and contains the plaintext keys, too
187
# while wlt2 does not.
189
for key in self.wlt.addrMap:
190
self.assertTrue(self.wlt.addrMap[key].isLocked)
191
self.assertEqual(self.wlt.addrMap[key].binPrivKey32_Plain.toHexStr(), '')
193
#############################################################################
194
# (4)Testing changing self.passphrase on encrypted wallet',
196
self.wlt.unlock( securePassphrase=self.passphrase )
197
for key in self.wlt.addrMap:
198
self.assertFalse(self.wlt.addrMap[key].isLocked)
199
self.assertNotEqual(self.wlt.addrMap[key].binPrivKey32_Plain.toHexStr(), '')
200
# ...to same self.passphrase'
201
origKdfKey = self.wlt.kdfKey
202
self.wlt.changeWalletEncryption( securePassphrase=self.passphrase )
203
self.assertEqual(origKdfKey, self.wlt.kdfKey)
205
# (4)And now testing new self.passphrase...'
206
self.wlt.changeWalletEncryption( securePassphrase=self.passphrase2 )
207
self.assertNotEqual(origKdfKey, self.wlt.kdfKey)
209
# (4) Re-reading wallet from file, compare the two wallets'
210
wlt2 = PyBtcWallet().readWalletFile(self.wlt.getWalletPath())
211
self.assertTrue(self.wlt.isEqualTo(wlt2))
213
#############################################################################
214
# (5)Testing changing KDF on encrypted wallet'
216
self.wlt.unlock( securePassphrase=self.passphrase2 )
218
MEMORY_REQT_BYTES = 1024
221
self.wlt.changeKdfParams(MEMORY_REQT_BYTES, NUM_ITER, hex_to_binary(SALT_ALL_0), self.passphrase2)
222
self.assertEqual(self.wlt.kdf.getMemoryReqtBytes(), MEMORY_REQT_BYTES)
223
self.assertEqual(self.wlt.kdf.getNumIterations(), NUM_ITER)
224
self.assertEqual(self.wlt.kdf.getSalt().toHexStr(), SALT_ALL_0)
226
self.wlt.changeWalletEncryption( securePassphrase=self.passphrase2 )
227
# I don't know why this shouldn't be ''
228
# Commenting out because it's a broken assertion
229
# self.assertNotEqual(origKdfKey.toHexStr(), '')
231
# (5) Get new address from locked wallet'
235
self.wlt.getNextUnusedAddress()
236
self.assertEqual(len(self.wlt.addrMap), originalLength+13)
238
# (5) Re-reading wallet from file, compare the two wallets'
239
wlt2 = PyBtcWallet().readWalletFile(self.wlt.getWalletPath())
240
self.assertTrue(self.wlt.isEqualTo(wlt2))
242
#############################################################################
243
# !!! #forkOnlineWallet()
244
# (6)Testing forking encrypted wallet for online mode'
245
self.wlt.forkOnlineWallet('OnlineVersionOfEncryptedWallet.bin')
246
wlt2.readWalletFile('OnlineVersionOfEncryptedWallet.bin')
247
for key in wlt2.addrMap:
248
self.assertTrue(self.wlt.addrMap[key].isLocked)
249
self.assertEqual(self.wlt.addrMap[key].binPrivKey32_Plain.toHexStr(), '')
250
# (6)Getting a new addresses from both wallets'
251
for i in range(self.wlt.addrPoolSize*2):
252
self.wlt.getNextUnusedAddress()
253
wlt2.getNextUnusedAddress()
255
newaddr1 = self.wlt.getNextUnusedAddress()
256
newaddr2 = wlt2.getNextUnusedAddress()
257
self.assertTrue(newaddr1.getAddr160() == newaddr2.getAddr160())
258
self.assertEqual(len(wlt2.addrMap), 3*originalLength+14)
260
# (6) Re-reading wallet from file, compare the two wallets
261
wlt3 = PyBtcWallet().readWalletFile('OnlineVersionOfEncryptedWallet.bin')
262
self.assertTrue(wlt3.isEqualTo(wlt2))
263
#############################################################################
264
# (7)Testing removing wallet encryption'
265
# Wallet is locked? ', self.wlt.isLocked
266
self.wlt.unlock(securePassphrase=self.passphrase2)
267
self.wlt.changeWalletEncryption( None )
268
for key in self.wlt.addrMap:
269
self.assertFalse(self.wlt.addrMap[key].isLocked)
270
self.assertNotEqual(self.wlt.addrMap[key].binPrivKey32_Plain.toHexStr(), '')
272
# (7) Re-reading wallet from file, compare the two wallets'
273
wlt2 = PyBtcWallet().readWalletFile(self.wlt.getWalletPath())
274
self.assertTrue(self.wlt.isEqualTo(wlt2))
276
#############################################################################
278
# *********************************************************************'
279
# (8)Doing interrupt tests to test wallet-file-update recovery'
282
d = hash256(f.read())
284
return binary_to_hex(d[:8])
286
def verifyFileStatus(fileAExists = True, fileBExists = True, \
287
fileAupdExists = True, fileBupdExists = True):
288
self.assertEqual(os.path.exists(self.fileA), fileAExists)
289
self.assertEqual(os.path.exists(self.fileB), fileBExists)
290
self.assertEqual(os.path.exists(self.fileAupd), fileAupdExists)
291
self.assertEqual(os.path.exists(self.fileBupd), fileBupdExists)
293
correctMainHash = hashfile(self.fileA)
295
self.wlt.interruptTest1 = True
296
self.wlt.getNextUnusedAddress()
297
except InterruptTestError:
300
self.wlt.interruptTest1 = False
302
# (8a)Interrupted getNextUnusedAddress on primary file update'
303
verifyFileStatus(True, True, False, True)
304
# (8a)Do consistency check on the wallet'
305
self.wlt.doWalletFileConsistencyCheck()
306
verifyFileStatus(True, True, False, False)
307
self.assertEqual(correctMainHash, hashfile(self.fileA))
310
self.wlt.interruptTest2 = True
311
self.wlt.getNextUnusedAddress()
312
except InterruptTestError:
315
self.wlt.interruptTest2 = False
317
# (8b)Interrupted getNextUnusedAddress on between primary/backup update'
318
verifyFileStatus(True, True, True, True)
319
# (8b)Do consistency check on the wallet'
320
self.wlt.doWalletFileConsistencyCheck()
321
verifyFileStatus(True, True, False, False)
322
self.assertEqual(hashfile(self.fileA), hashfile(self.fileB))
323
# (8c) Try interrupting at state 3'
324
verifyFileStatus(True, True, False, False)
327
self.wlt.interruptTest3 = True
328
self.wlt.getNextUnusedAddress()
329
except InterruptTestError:
332
self.wlt.interruptTest3 = False
334
# (8c)Interrupted getNextUnusedAddress on backup file update'
335
verifyFileStatus(True, True, True, False)
336
# (8c)Do consistency check on the wallet'
337
self.wlt.doWalletFileConsistencyCheck()
338
verifyFileStatus(True, True, False, False)
339
self.assertEqual(hashfile(self.fileA), hashfile(self.fileB))
341
#############################################################################
343
# *********************************************************************'
344
# (9)Checksum-based byte-error correction tests!'
345
# (9)Start with a good primary and backup file...'
347
# (9a)Open primary wallet, change second byte in KDF'
348
wltfile = open(self.wlt.walletPath,'r+b')
350
wltfile.write('\xff')
352
# (9a)Byte changed, file hashes:'
353
verifyFileStatus(True, True, False, False)
355
# (9a)Try to read wallet from file, should correct KDF error, write fix'
356
wlt2 = PyBtcWallet().readWalletFile(self.wlt.walletPath)
357
verifyFileStatus(True, True, False, False)
358
self.assertNotEqual(hashfile(self.fileA), hashfile(self.fileB))
361
# *********************************************************************'
362
# (9b)Change a byte in each checksummed field in root addr'
363
wltfile = open(self.wlt.walletPath,'r+b')
364
wltfile.seek(838); wltfile.write('\xff')
365
wltfile.seek(885); wltfile.write('\xff')
366
wltfile.seek(929); wltfile.write('\xff')
367
wltfile.seek(954); wltfile.write('\xff')
368
wltfile.seek(1000); wltfile.write('\xff')
370
# (9b) New file hashes...'
371
verifyFileStatus(True, True, False, False)
373
# (9b)Try to read wallet from file, should correct address errors'
374
wlt2 = PyBtcWallet().readWalletFile(self.wlt.walletPath)
375
verifyFileStatus(True, True, False, False)
376
self.assertNotEqual(hashfile(self.fileA), hashfile(self.fileB))
379
# *********************************************************************'
380
# (9c)Change a byte in each checksummed field, of first non-root addr'
381
wltfile = open(self.wlt.walletPath,'r+b')
382
wltfile.seek(1261+21+838); wltfile.write('\xff')
383
wltfile.seek(1261+21+885); wltfile.write('\xff')
384
wltfile.seek(1261+21+929); wltfile.write('\xff')
385
wltfile.seek(1261+21+954); wltfile.write('\xff')
386
wltfile.seek(1261+21+1000); wltfile.write('\xff')
388
# (9c) New file hashes...'
389
verifyFileStatus(True, True, False, False)
391
# (9c)Try to read wallet from file, should correct address errors'
392
wlt2 = PyBtcWallet().readWalletFile(self.wlt.walletPath)
393
verifyFileStatus(True, True, False, False)
394
self.assertNotEqual(hashfile(self.fileA), hashfile(self.fileB))
397
# *********************************************************************'
398
# (9d)Now butcher the CHECKSUM, see if correction works'
399
wltfile = open(self.wlt.walletPath,'r+b')
400
wltfile.seek(977); wltfile.write('\xff')
402
# (9d) New file hashes...'
403
verifyFileStatus(True, True, False, False)
405
# (9d)Try to read wallet from file, should correct address errors'
406
wlt2 = PyBtcWallet().readWalletFile(self.wlt.walletPath)
407
verifyFileStatus(True, True, False, False)
408
self.assertNotEqual(hashfile(self.fileA), hashfile(self.fileB))
412
# (9z) Test comment I/O'
413
comment1 = 'This is my normal unit-testing address.'
414
comment2 = 'This is fake tx... no tx has this hash.'
415
comment3 = comment1 + ' Corrected!'
416
hash1 = '\x1f'*20 # address160
417
hash2 = '\x2f'*32 # tx hash
418
self.wlt.setComment(hash1, comment1)
419
self.wlt.setComment(hash2, comment2)
420
self.wlt.setComment(hash1, comment3)
422
wlt2 = PyBtcWallet().readWalletFile(self.wlt.walletPath)
423
c3 = wlt2.getComment(hash1)
424
c2 = wlt2.getComment(hash2)
425
self.assertEqual(c3, comment3)
426
self.assertEqual(c2, comment2)
428
# Running tests with "python <module name>" will NOT work for any Armory tests
429
# You must run tests with "python -m unittest <module name>" or run all tests with "python -m unittest discover"
430
# if __name__ == "__main__":
b'\\ No newline at end of file'