Tests and fixes for remainig Keepkey commands.

Tests for setup, wipe, restore, backup, promptpin, sendpin
This commit is contained in:
Andrew Chow 2019-02-04 17:47:47 -05:00
parent cf152b709a
commit d627634ce6
3 changed files with 112 additions and 8 deletions

View File

@ -1,7 +1,7 @@
# KeepKey interaction script
from ..hwwclient import HardwareWalletClient
from ..errors import BadArgumentError, DeviceAlreadyUnlockedError, DeviceConnectionError, UnavailableActionError, DeviceNotReadyError
from ..errors import BadArgumentError, DeviceAlreadyInitError, DeviceAlreadyUnlockedError, DeviceConnectionError, UnavailableActionError, DeviceNotReadyError
from keepkeylib.transport_hid import HidTransport
from keepkeylib.transport_udp import UDPTransport
from keepkeylib.client import BaseClient, DebugWireMixin, DebugLinkMixin, ProtocolMixin, TextUIMixin
@ -111,8 +111,13 @@ def parse_multisig(script):
# Doesn't init device
class NoInitMixin(ProtocolMixin):
def __init__(self, *args, **kwargs):
super(ProtocolMixin, self).__init__(*args, **kwargs)
self.tx_api = None
super().__init__(*args, **kwargs)
def init_device(self):
pass
def actual_init_device(self):
return super().init_device()
class KeepKey(NoInitMixin, TextUIMixin, BaseClient):
pass
@ -160,7 +165,7 @@ class KeepkeyClient(HardwareWalletClient):
os.environ['PASSPHRASE'] = password
def _check_unlocked(self):
self.client.init_device()
self.client.actual_init_device()
if self.client.features.pin_protection and not self.client.features.pin_cached:
raise DeviceNotReadyError('Keepkey is locked. Unlock by using \'promptpin\' and then \'sendpin\'.')
@ -360,6 +365,7 @@ class KeepkeyClient(HardwareWalletClient):
# Setup a new device
@keepkey_exception
def setup_device(self, label='', passphrase=''):
self.client.actual_init_device()
if self.client.features.initialized:
raise DeviceAlreadyInitError('Device is already initialized. Use wipe first and try again')
self.client.reset_device(False, 256, bool(self.password), True, label, 'english')
@ -390,7 +396,7 @@ class KeepkeyClient(HardwareWalletClient):
# Prompt for a pin on device
@keepkey_exception
def prompt_pin(self):
self.client.init_device()
self.client.actual_init_device()
if not self.client.features.pin_protection:
raise DeviceAlreadyUnlockedError('This device does not need a PIN')
if self.client.features.pin_cached:
@ -442,7 +448,7 @@ def enumerate(password=''):
client = None
try:
client = KeepkeyClient(path, password)
client.client.init_device()
client.client.actual_init_device()
if client.client.features.initialized:
master_xpub = client.get_pubkey_at_path('m/0h')['xpub']
d_data['fingerprint'] = get_xpub_fingerprint_hex(master_xpub)

View File

@ -38,7 +38,7 @@ suite = unittest.TestSuite()
suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(TestSegwitAddress))
suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(TestPSBT))
if not args.no_trezor or not args.no_coldcard or args.ledger or not args.no_bitbox:
if not args.no_trezor or not args.no_coldcard or args.ledger or not args.no_bitbox or not args.no_keepkey:
# Start bitcoind
rpc, userpass = start_bitcoind(args.bitcoind)

View File

@ -13,9 +13,20 @@ import unittest
from keepkeylib.transport_udp import UDPTransport
from keepkeylib.client import KeepKeyDebugClient
from test_device import DeviceEmulator, DeviceTestCase, start_bitcoind, TestDeviceConnect, TestDisplayAddress, TestGetKeypool, TestSignTx
from keepkeylib import messages_pb2 as messages
from test_device import DeviceEmulator, DeviceTestCase, start_bitcoind, TestDeviceConnect, TestDisplayAddress, TestGetKeypool, TestSignMessage, TestSignTx
from hwilib.cli import process_commands
from hwilib.devices.keepkey import KeepkeyClient
from types import MethodType
def pin_matrix(self, code=None):
if self.pin:
pin = self.debug.encode_pin(self.pin)
else:
pin = self.debug.read_pin_encoded()
return messages.PinMatrixAck(pin=pin)
class KeepkeyEmulator(DeviceEmulator):
def __init__(self, emulator_path):
@ -105,6 +116,91 @@ class TestKeepkeyGetxpub(KeepkeyTestCase):
gxp_res = process_commands(['-t', 'keepkey', '-d', 'udp:127.0.0.1:21324', 'getxpub', path_vec['path']])
self.assertEqual(gxp_res['xpub'], path_vec['xpub'])
# Trezor specific management (setup, wipe, restore, backup, promptpin, sendpin) command tests
class TestKeepkeyManCommands(KeepkeyTestCase):
def setUp(self):
self.client = self.emulator.start()
self.dev_args = ['-t', 'keepkey', '-d', 'udp:127.0.0.1:21324']
def tearDown(self):
self.emulator.stop()
def test_setup_wipe(self):
# Device is init, setup should fail
result = process_commands(self.dev_args + ['setup'])
self.assertEquals(result['code'], -10)
self.assertEquals(result['error'], 'Device is already initialized. Use wipe first and try again')
# Wipe
result = process_commands(self.dev_args + ['wipe'])
self.assertTrue(result['success'])
# Setup
k_client = KeepkeyClient('udp:127.0.0.1:21324', 'test')
k_client.client.callback_PinMatrixRequest = MethodType(pin_matrix, k_client.client)
k_client.client.pin = '1234'
result = k_client.setup_device()
self.assertTrue(result['success'])
# Make sure device is init, setup should fail
result = process_commands(self.dev_args + ['setup'])
self.assertEquals(result['code'], -10)
self.assertEquals(result['error'], 'Device is already initialized. Use wipe first and try again')
def test_backup(self):
result = process_commands(self.dev_args + ['backup'])
self.assertIn('error', result)
self.assertIn('code', result)
self.assertEqual(result['error'], 'The Keepkey does not support creating a backup via software')
self.assertEqual(result['code'], -9)
def test_pins(self):
# There's no PIN
result = process_commands(self.dev_args + ['--debug', 'promptpin'])
self.assertEqual(result['error'], 'This device does not need a PIN')
self.assertEqual(result['code'], -11)
result = process_commands(self.dev_args + ['sendpin', '1234'])
self.assertEqual(result['error'], 'This device does not need a PIN')
self.assertEqual(result['code'], -11)
# Set a PIN
self.client.wipe_device()
self.client.load_device_by_mnemonic(mnemonic='alcohol woman abuse must during monitor noble actual mixed trade anger aisle', pin='1234', passphrase_protection=False, label='test', language='english')
self.client.call(messages.ClearSession())
result = process_commands(self.dev_args + ['promptpin'])
self.assertTrue(result['success'])
# Invalid pin
result = process_commands(self.dev_args + ['sendpin', 'notnum'])
self.assertEqual(result['error'], 'Non-numeric PIN provided')
self.assertEqual(result['code'], -7)
result = process_commands(self.dev_args + ['sendpin', '00000'])
self.assertFalse(result['success'])
# Make sure we get a needs pin message
result = process_commands(self.dev_args + ['getxpub', 'm/0h'])
self.assertEqual(result['code'], -12)
self.assertEqual(result['error'], 'Keepkey is locked. Unlock by using \'promptpin\' and then \'sendpin\'.')
# Prompt pin
self.client.call(messages.ClearSession())
result = process_commands(self.dev_args + ['promptpin'])
self.assertTrue(result['success'])
# Send the PIN
pin = self.client.debug.encode_pin('1234')
result = process_commands(self.dev_args + ['sendpin', pin])
self.assertTrue(result['success'])
# Sending PIN after unlock
result = process_commands(self.dev_args + ['promptpin'])
self.assertEqual(result['error'], 'The PIN has already been sent to this device')
self.assertEqual(result['code'], -11)
result = process_commands(self.dev_args + ['sendpin', '1234'])
self.assertEqual(result['error'], 'The PIN has already been sent to this device')
self.assertEqual(result['code'], -11)
def keepkey_test_suite(emulator, rpc, userpass):
# Redirect stderr to /dev/null as it's super spammy
sys.stderr = open(os.devnull, 'w')
@ -122,7 +218,9 @@ def keepkey_test_suite(emulator, rpc, userpass):
suite.addTest(DeviceTestCase.parameterize(TestGetKeypool, rpc, userpass, type, path, fingerprint, master_xpub, emulator=dev_emulator))
suite.addTest(DeviceTestCase.parameterize(TestSignTx, rpc, userpass, type, path, fingerprint, master_xpub, emulator=dev_emulator))
suite.addTest(DeviceTestCase.parameterize(TestDisplayAddress, rpc, userpass, type, path, fingerprint, master_xpub, emulator=dev_emulator))
suite.addTest(DeviceTestCase.parameterize(TestSignMessage, rpc, userpass, type, path, fingerprint, master_xpub, emulator=dev_emulator))
suite.addTest(KeepkeyTestCase.parameterize(TestKeepkeyGetxpub, emulator=dev_emulator))
suite.addTest(KeepkeyTestCase.parameterize(TestKeepkeyManCommands, emulator=dev_emulator))
return suite
if __name__ == '__main__':