def test_rules_file_are_copied(self): process_commands( ['installudevrules', '--location', self.INSTALLATION_FOLDER]) # Assert files wre copied for _, _, files in walk(self.INSTALLATION_FOLDER, topdown=False): for file_name in files: src = path.join(self.SOURCE_FOLDER, file_name) tgt = path.join(self.INSTALLATION_FOLDER, file_name) self.assertTrue(filecmp.cmp(src, tgt))
def do_command(self, args): cli_args = [] for arg in args: cli_args.append(shlex.quote(arg)) if self.interface == 'cli': proc = subprocess.Popen(['hwi ' + ' '.join(cli_args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True) result = proc.communicate() return json.loads(result[0].decode()) elif self.interface == 'bindist': proc = subprocess.Popen(['../dist/hwi ' + ' '.join(cli_args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True) result = proc.communicate() return json.loads(result[0].decode()) elif self.interface == 'stdin': input_str = '\n'.join(args) + '\n' proc = subprocess.Popen(['hwi', '--stdin'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) result = proc.communicate(input_str.encode()) return json.loads(result[0].decode()) else: return process_commands(args)
def start(self): self.emulator_stderr = open('ledger-emulator.stderr', 'a') # Start the emulator self.emulator_proc = subprocess.Popen([ 'python3', './' + os.path.basename(self.emulator_path), '--display', 'headless', './apps/btc.elf' ], cwd=os.path.dirname( self.emulator_path), stderr=self.emulator_stderr, preexec_fn=os.setsid) # Wait for simulator to be up while True: try: enum_res = process_commands(['enumerate']) found = False for dev in enum_res: if dev['type'] == 'ledger' and 'error' not in dev: found = True break if found: break except Exception as e: print(str(e)) pass time.sleep(0.5)
def start(self): # Start the emulator self.emulator_proc = subprocess.Popen([ 'python3', './' + os.path.basename(self.emulator_path), '-bn', './apps/btc.elf' ], cwd=os.path.dirname( self.emulator_path), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, preexec_fn=os.setsid) # Wait for simulator to be up while True: try: enum_res = process_commands(['enumerate']) found = False for dev in enum_res: if dev['type'] == 'ledger' and 'error' not in dev: found = True break if found: break except Exception as e: print(str(e)) pass time.sleep(0.5) self.kp_thread = ScreenTextThread() self.kp_thread.start()
def test_rules_file_are_copied(self): result = process_commands(['installudevrules', '--location', self.INSTALLATION_FOLDER]) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual(result['error'], 'Need to be root.') self.assertEqual(result['code'], -16) # Assert files wre copied for root, dirs, files in walk(self.INSTALLATION_FOLDER, topdown=False): for file_name in files: src = path.join(self.SOURCE_FOLDER, file_name) tgt = path.join(self.INSTALLATION_FOLDER, file_name) self.assertTrue(filecmp.cmp(src, tgt))
def start(self): automation_path = os.path.abspath("data/speculos-automation.json") self.emulator_stderr = open('ledger-emulator.stderr', 'a') # Start the emulator self.emulator_proc = subprocess.Popen(['python3', './' + os.path.basename(self.emulator_path), '--display', 'headless', '--automation', 'file:{}'.format(automation_path), '--log-level', 'automation:DEBUG', '--log-level', 'seproxyhal:DEBUG', './apps/btc.elf'], cwd=os.path.dirname(self.emulator_path), stderr=self.emulator_stderr, preexec_fn=os.setsid) # Wait for simulator to be up while True: try: enum_res = process_commands(['enumerate']) found = False for dev in enum_res: if dev['type'] == 'ledger' and 'error' not in dev: found = True break if found: break except Exception as e: print(str(e)) pass time.sleep(0.5)
def ledger_test_suite(device_model, rpc, userpass, interface): # Look for real ledger using HWI API(self-referential, but no other way) enum_res = process_commands(['enumerate']) path = None master_xpub = None fingerprint = None for device in enum_res: if device['type'] == 'ledger': fingerprint = device['fingerprint'] path = device['path'] master_xpub = process_commands( ['-f', fingerprint, 'getmasterxpub'])['xpub'] break assert (path is not None and master_xpub is not None and fingerprint is not None) # Ledger specific disabled command tests class TestLedgerDisabledCommands(DeviceTestCase): def test_pin(self): result = self.do_command(self.dev_args + ['promptpin']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual( result['error'], 'The Ledger Nano S and X do not need a PIN sent from the host') self.assertEqual(result['code'], -9) result = self.do_command(self.dev_args + ['sendpin', '1234']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual( result['error'], 'The Ledger Nano S and X do not need a PIN sent from the host') self.assertEqual(result['code'], -9) def test_setup(self): result = self.do_command(self.dev_args + ['-i', 'setup']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual( result['error'], 'The Ledger Nano S and X do not support software setup') self.assertEqual(result['code'], -9) def test_wipe(self): result = self.do_command(self.dev_args + ['wipe']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual( result['error'], 'The Ledger Nano S and X do not support wiping via software') self.assertEqual(result['code'], -9) def test_restore(self): result = self.do_command(self.dev_args + ['-i', 'restore']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual( result['error'], 'The Ledger Nano S and X do not support restoring via software' ) self.assertEqual(result['code'], -9) def test_backup(self): result = self.do_command(self.dev_args + ['backup']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual( result['error'], 'The Ledger Nano S and X do not support creating a backup via software' ) self.assertEqual(result['code'], -9) # Generic Device tests suite = unittest.TestSuite() suite.addTest( DeviceTestCase.parameterize(TestLedgerDisabledCommands, rpc, userpass, device_model, 'ledger', path, fingerprint, master_xpub, interface=interface)) suite.addTest( DeviceTestCase.parameterize(TestDeviceConnect, rpc, userpass, device_model, 'ledger', path, fingerprint, master_xpub, interface=interface)) suite.addTest( DeviceTestCase.parameterize(TestDeviceConnect, rpc, userpass, device_model, 'ledger', path, fingerprint, master_xpub, interface=interface)) suite.addTest( DeviceTestCase.parameterize(TestGetDescriptors, rpc, userpass, device_model, 'ledger', path, fingerprint, master_xpub, interface=interface)) suite.addTest( DeviceTestCase.parameterize(TestGetKeypool, rpc, userpass, device_model, 'ledger', path, fingerprint, master_xpub, interface=interface)) suite.addTest( DeviceTestCase.parameterize(TestSignTx, rpc, userpass, device_model, 'ledger', path, fingerprint, master_xpub, interface=interface)) suite.addTest( DeviceTestCase.parameterize(TestDisplayAddress, rpc, userpass, device_model, 'ledger', path, fingerprint, master_xpub, interface=interface)) suite.addTest( DeviceTestCase.parameterize(TestSignMessage, rpc, userpass, device_model, 'ledger', path, fingerprint, master_xpub, interface=interface)) return suite
def coldcard_test_suite(simulator, rpc, userpass, interface): # Start the Coldcard simulator simulator_proc = subprocess.Popen(['python3', os.path.basename(simulator)], cwd=os.path.dirname(simulator), stdout=subprocess.DEVNULL) # Wait for simulator to be up while True: enum_res = process_commands(['enumerate']) found = False for dev in enum_res: if dev['type'] == 'coldcard' and 'error' not in dev: found = True break if found: break time.sleep(0.5) # Cleanup def cleanup_simulator(): dev = ColdcardDevice(sn='/tmp/ckcc-simulator.sock') resp = dev.send_recv(CCProtocolPacker.logout()) atexit.register(cleanup_simulator) # Coldcard specific management command tests class TestColdcardManCommands(DeviceTestCase): def test_setup(self): result = self.do_command(self.dev_args + ['-i', 'setup']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual(result['error'], 'The Coldcard does not support software setup') self.assertEqual(result['code'], -9) def test_wipe(self): result = self.do_command(self.dev_args + ['wipe']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual(result['error'], 'The Coldcard does not support wiping via software') self.assertEqual(result['code'], -9) def test_restore(self): result = self.do_command(self.dev_args + ['-i', 'restore']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual(result['error'], 'The Coldcard does not support restoring via software') self.assertEqual(result['code'], -9) def test_backup(self): result = self.do_command(self.dev_args + ['backup']) self.assertTrue(result['success']) self.assertIn('The backup has been written to', result['message']) def test_pin(self): result = self.do_command(self.dev_args + ['promptpin']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual(result['error'], 'The Coldcard does not need a PIN sent from the host') self.assertEqual(result['code'], -9) result = self.do_command(self.dev_args + ['sendpin', '1234']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual(result['error'], 'The Coldcard does not need a PIN sent from the host') self.assertEqual(result['code'], -9) # Generic device tests suite = unittest.TestSuite() suite.addTest(DeviceTestCase.parameterize(TestColdcardManCommands, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', '', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestDeviceConnect, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestGetKeypool, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestDisplayAddress, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestSignMessage, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestSignTx, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface)) return suite
def test_display_address_descriptor(self): account_xpub = process_commands(self.dev_args + ['getxpub', 'm/84h/1h/0h'])['xpub'] p2sh_segwit_account_xpub = process_commands( self.dev_args + ['getxpub', 'm/49h/1h/0h'])['xpub'] legacy_account_xpub = process_commands( self.dev_args + ['getxpub', 'm/44h/1h/0h'])['xpub'] # Native SegWit address using xpub: process_commands(self.dev_args + [ 'displayaddress', '--desc', 'wpkh([' + self.fingerprint + '/84h/1h/0h)]' + account_xpub + '/0/0)' ]) # Native SegWit address using hex encoded pubkey: process_commands(self.dev_args + [ 'displayaddress', '--desc', 'wpkh([' + self.fingerprint + '/84h/1h/0h)]' + xpub_to_pub_hex(account_xpub) + '/0/0)' ]) # P2SH wrapped SegWit address using xpub: process_commands(self.dev_args + [ 'displayaddress', '--desc', 'sh(wpkh([' + self.fingerprint + '/49h/1h/0h)]' + p2sh_segwit_account_xpub + '/0/0))' ]) # Legacy address process_commands(self.dev_args + [ 'displayaddress', '--desc', 'pkh([' + self.fingerprint + '/44h/1h/0h)]' + legacy_account_xpub + '/0/0)' ]) # Should check xpub result = process_commands(self.dev_args + [ 'displayaddress', '--desc', 'wpkh([' + self.fingerprint + '/84h/1h/0h)]' + "not_and_xpub" + '/0/0)' ]) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual(result['code'], -7) # Should check hex pub result = process_commands(self.dev_args + [ 'displayaddress', '--desc', 'wpkh([' + self.fingerprint + '/84h/1h/0h)]' + "not_and_xpub" + '/0/0)' ]) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual(result['code'], -7) # Should check fingerprint process_commands(self.dev_args + [ 'displayaddress', '--desc', 'wpkh([00000000/84h/1h/0h)]' + account_xpub + '/0/0)' ]) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual(result['code'], -7)
def coldcard_test_suite(simulator, rpc, userpass, interface): # Start the Coldcard simulator coldcard_proc = subprocess.Popen(['python3', os.path.basename(simulator), '-m'], cwd=os.path.dirname(simulator), stdout=subprocess.DEVNULL, preexec_fn=os.setsid) # Wait for simulator to be up while True: try: enum_res = process_commands(['enumerate']) found = False for dev in enum_res: if dev['type'] == 'coldcard' and 'error' not in dev: found = True break if found: break except: pass time.sleep(0.5) # Cleanup def cleanup_simulator(): os.killpg(os.getpgid(coldcard_proc.pid), signal.SIGTERM) os.waitpid(os.getpgid(coldcard_proc.pid), 0) atexit.register(cleanup_simulator) # Coldcard specific management command tests class TestColdcardManCommands(DeviceTestCase): def test_setup(self): result = self.do_command(self.dev_args + ['-i', 'setup']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual(result['error'], 'The Coldcard does not support software setup') self.assertEqual(result['code'], -9) def test_wipe(self): result = self.do_command(self.dev_args + ['wipe']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual(result['error'], 'The Coldcard does not support wiping via software') self.assertEqual(result['code'], -9) def test_restore(self): result = self.do_command(self.dev_args + ['-i', 'restore']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual(result['error'], 'The Coldcard does not support restoring via software') self.assertEqual(result['code'], -9) def test_backup(self): result = self.do_command(self.dev_args + ['backup']) self.assertTrue(result['success']) self.assertIn('The backup has been written to', result['message']) backup_filename = result['message'].split(' ')[-1] os.remove(backup_filename) def test_pin(self): result = self.do_command(self.dev_args + ['promptpin']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual(result['error'], 'The Coldcard does not need a PIN sent from the host') self.assertEqual(result['code'], -9) result = self.do_command(self.dev_args + ['sendpin', '1234']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual(result['error'], 'The Coldcard does not need a PIN sent from the host') self.assertEqual(result['code'], -9) class TestColdcardGetXpub(DeviceTestCase): def test_getxpub(self): result = self.do_command(self.dev_args + ['--expert', 'getxpub', 'm/44h/0h/0h/3']) self.assertEqual(result['xpub'], 'tpubDFHiBJDeNvqPWNJbzzxqDVXmJZoNn2GEtoVcFhMjXipQiorGUmps3e5ieDGbRrBPTFTh9TXEKJCwbAGW9uZnfrVPbMxxbFohuFzfT6VThty') self.assertTrue(result['testnet']) self.assertFalse(result['private']) self.assertEqual(result['depth'], 4) self.assertEqual(result['parent_fingerprint'], 'bc123c3e') self.assertEqual(result['child_num'], 3) self.assertEqual(result['chaincode'], '806b26507824f73bc331494afe122f428ef30dde80b2c1ce025d2d03aff411e7') self.assertEqual(result['pubkey'], '0368000bdff5e0b71421c37b8514de8acd4d98ba9908d183d9da56d02ca4fcfd08') # Generic device tests suite = unittest.TestSuite() suite.addTest(DeviceTestCase.parameterize(TestColdcardManCommands, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', '', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestColdcardGetXpub, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestDeviceConnect, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestDeviceConnect, rpc, userpass, 'coldcard_simulator', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestGetDescriptors, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestGetKeypool, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestDisplayAddress, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestSignMessage, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestSignTx, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface)) return suite
def test_setup_wipe(self): # Device is init, setup should fail result = self.do_command(self.dev_args + [ '-i', 'setup', '--label', 'setup_test', '--backup_passphrase', 'testpass' ]) self.assertEquals(result['code'], -10) self.assertEquals( result['error'], 'Device is already initialized. Use wipe first and try again') # Wipe result = self.do_command(self.dev_args + ['wipe']) self.assertTrue(result['success']) # Check arguments result = self.do_command(self.dev_args + ['-i', 'setup', '--label', 'setup_test']) self.assertEquals(result['code'], -7) self.assertEquals( result['error'], 'The label and backup passphrase for a new Digital Bitbox wallet must be specified and cannot be empty' ) result = self.do_command( self.dev_args + ['-i', 'setup', '--backup_passphrase', 'testpass']) self.assertEquals(result['code'], -7) self.assertEquals( result['error'], 'The label and backup passphrase for a new Digital Bitbox wallet must be specified and cannot be empty' ) # Setup result = self.do_command(self.dev_args + [ '-i', 'setup', '--label', 'setup_test', '--backup_passphrase', 'testpass' ]) self.assertTrue(result['success']) # Reset back to original result = process_commands(self.dev_args + ['wipe']) self.assertTrue(result['success']) send_plain(b'{"password":"******"}', dev) send_encrypt( json.dumps({ "seed": { "source": "backup", "filename": "test_backup.pdf", "key": "key" } }), '0000', dev) # Make sure device is init, setup should fail result = self.do_command(self.dev_args + [ '-i', 'setup', '--label', 'setup_test', '--backup_passphrase', 'testpass' ]) self.assertEquals(result['code'], -10) self.assertEquals( result['error'], 'Device is already initialized. Use wipe first and try again')