def test_getxpub(self): with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data/bip32_vectors.json'), encoding='utf-8') as f: vectors = json.load(f) for vec in vectors: with self.subTest(vector=vec): # Setup with xprv device.wipe(self.client) load_device_by_xprv(client=self.client, xprv=vec['xprv'], pin='', passphrase_protection=False, label='test', language='english') # Test getmasterxpub gmxp_res = process_commands([ '-t', 'trezor', '-d', 'udp:127.0.0.1:21324', 'getmasterxpub' ]) self.assertEqual(gmxp_res['xpub'], vec['master_xpub']) # Test the path derivs for path_vec in vec['vectors']: gxp_res = process_commands([ '-t', 'trezor', '-d', 'udp:127.0.0.1:21324', 'getxpub', path_vec['path'] ]) self.assertEqual(gxp_res['xpub'], path_vec['xpub'])
def test_display_address_bad_args(self): result = process_commands( self.dev_args + ['displayaddress', '--sh_wpkh', '--wpkh', 'm/49h/1h/0h/0/0']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual(result['code'], -7)
def test_setup(self): result = process_commands(self.dev_args + ['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 = process_commands(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_no_type(self): gmxp_res = process_commands(['getmasterxpub']) self.assertIn('error', gmxp_res) self.assertEqual( gmxp_res['error'], 'You must specify a device type or fingerprint for all commands except enumerate' ) self.assertIn('code', gmxp_res) self.assertEqual(gmxp_res['code'], -1)
def test_enumerate(self): enum_res = process_commands(['enumerate']) found = False for device in enum_res: self.assertNotIn('error', device) if device['type'] == self.type and device[ 'path'] == self.path and device[ 'fingerprint'] == self.fingerprint: found = True self.assertTrue(found)
def ledger_test_suite(rpc, userpass): # 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) # Generic Device tests suite = unittest.TestSuite() suite.addTest(DeviceTestCase.parameterize(TestDeviceConnect, rpc, userpass, 'ledger', path, fingerprint, master_xpub)) suite.addTest(DeviceTestCase.parameterize(TestGetKeypool, rpc, userpass, 'ledger', path, fingerprint, master_xpub)) suite.addTest(DeviceTestCase.parameterize(TestSignTx, rpc, userpass, 'ledger', path, fingerprint, master_xpub)) suite.addTest(DeviceTestCase.parameterize(TestDisplayAddress, rpc, userpass, 'ledger', path, fingerprint, master_xpub)) return suite
def test_sign_msg(self): process_commands( self.dev_args + ['signmessage', 'Message signing test', 'm/44h/1h/0h/0/0'])
def test_display_address(self): process_commands(self.dev_args + ['displayaddress', 'm/44h/1h/0h/0/0']) process_commands(self.dev_args + ['displayaddress', '--sh_wpkh', 'm/49h/1h/0h/0/0']) process_commands(self.dev_args + ['displayaddress', '--wpkh', 'm/84h/1h/0h/0/0'])
def _test_signtx(self, input_type, multisig): # Import some keys to the watch only wallet and send coins to them keypool_desc = process_commands( self.dev_args + ['getkeypool', '--keypool', '--sh_wpkh', '30', '40']) import_result = self.wrpc.importmulti(keypool_desc) self.assertTrue(import_result[0]['success']) keypool_desc = process_commands( self.dev_args + ['getkeypool', '--keypool', '--sh_wpkh', '--internal', '30', '40']) import_result = self.wrpc.importmulti(keypool_desc) self.assertTrue(import_result[0]['success']) sh_wpkh_addr = self.wrpc.getnewaddress('', 'p2sh-segwit') wpkh_addr = self.wrpc.getnewaddress('', 'bech32') pkh_addr = self.wrpc.getnewaddress('', 'legacy') self.wrpc.importaddress(wpkh_addr) self.wrpc.importaddress(pkh_addr) # pubkeys to construct 2-of-3 multisig descriptors for import sh_wpkh_info = self.wrpc.getaddressinfo(sh_wpkh_addr) wpkh_info = self.wrpc.getaddressinfo(wpkh_addr) pkh_info = self.wrpc.getaddressinfo(pkh_addr) # Get origin info/key pair so wallet doesn't forget how to # sign with keys post-import pubkeys = [sh_wpkh_info['desc'][8:-2],\ wpkh_info['desc'][5:-1],\ pkh_info['desc'][4:-1]] sh_multi_desc = { 'desc': 'sh(multi(2,' + pubkeys[0] + ',' + pubkeys[1] + ',' + pubkeys[2] + '))', "timestamp": "now", "label": "shmulti" } sh_wsh_multi_desc = { 'desc': 'sh(wsh(multi(2,' + pubkeys[0] + ',' + pubkeys[1] + ',' + pubkeys[2] + ')))', "timestamp": "now", "label": "shwshmulti" } # re-order pubkeys to allow import without "already have private keys" error wsh_multi_desc = { 'desc': 'wsh(multi(2,' + pubkeys[2] + ',' + pubkeys[1] + ',' + pubkeys[0] + '))', "timestamp": "now", "label": "wshmulti" } multi_result = self.wrpc.importmulti( [sh_multi_desc, sh_wsh_multi_desc, wsh_multi_desc]) self.assertTrue(multi_result[0]['success']) self.assertTrue(multi_result[1]['success']) self.assertTrue(multi_result[2]['success']) sh_multi_addr = self.wrpc.getaddressesbylabel("shmulti").popitem()[0] sh_wsh_multi_addr = self.wrpc.getaddressesbylabel( "shwshmulti").popitem()[0] wsh_multi_addr = self.wrpc.getaddressesbylabel("wshmulti").popitem()[0] send_amount = 2 number_inputs = 0 # Single-sig if input_type == 'segwit' or input_type == 'all': self.wpk_rpc.sendtoaddress(sh_wpkh_addr, send_amount) self.wpk_rpc.sendtoaddress(wpkh_addr, send_amount) number_inputs += 2 if input_type == 'legacy' or input_type == 'all': self.wpk_rpc.sendtoaddress(pkh_addr, send_amount) number_inputs += 1 # Now do segwit/legacy multisig if multisig: if input_type == 'legacy' or input_type == 'all': self.wpk_rpc.sendtoaddress(sh_multi_addr, send_amount) number_inputs += 1 if input_type == 'segwit' or input_type == 'all': self.wpk_rpc.sendtoaddress(wsh_multi_addr, send_amount) self.wpk_rpc.sendtoaddress(sh_wsh_multi_addr, send_amount) number_inputs += 2 self.wpk_rpc.generatetoaddress(6, self.wpk_rpc.getnewaddress()) # Spend different amounts, requiring 1 to 3 inputs for i in range(number_inputs): # Create a psbt spending the above psbt = self.wrpc.walletcreatefundedpsbt( [], [{ self.wpk_rpc.getnewaddress(): (i + 1) * send_amount }], 0, { 'includeWatching': True, 'subtractFeeFromOutputs': [0] }, True) sign_res = process_commands(self.dev_args + ['signtx', psbt['psbt']]) finalize_res = self.wrpc.finalizepsbt(sign_res['psbt']) self.assertTrue(finalize_res['complete']) self.wrpc.sendrawtransaction(finalize_res['hex'])
def test_getkeypool(self): non_keypool_desc = process_commands(self.dev_args + ['getkeypool', '0', '20']) import_result = self.wpk_rpc.importmulti(non_keypool_desc) self.assertTrue(import_result[0]['success']) keypool_desc = process_commands(self.dev_args + ['getkeypool', '--keypool', '0', '20']) import_result = self.wpk_rpc.importmulti(keypool_desc) self.assertFalse(import_result[0]['success']) import_result = self.wrpc.importmulti(keypool_desc) self.assertTrue(import_result[0]['success']) for i in range(0, 21): addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress()) self.assertEqual(addr_info['hdkeypath'], "m/44'/1'/0'/0/{}".format(i)) keypool_desc = process_commands( self.dev_args + ['getkeypool', '--keypool', '--internal', '0', '20']) import_result = self.wrpc.importmulti(keypool_desc) self.assertTrue(import_result[0]['success']) for i in range(0, 21): addr_info = self.wrpc.getaddressinfo( self.wrpc.getrawchangeaddress()) self.assertEqual(addr_info['hdkeypath'], "m/44'/1'/0'/1/{}".format(i)) keypool_desc = process_commands( self.dev_args + ['getkeypool', '--keypool', '--sh_wpkh', '0', '20']) import_result = self.wrpc.importmulti(keypool_desc) self.assertTrue(import_result[0]['success']) for i in range(0, 21): addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress()) self.assertEqual(addr_info['hdkeypath'], "m/49'/1'/0'/0/{}".format(i)) keypool_desc = process_commands( self.dev_args + ['getkeypool', '--keypool', '--sh_wpkh', '--internal', '0', '20']) import_result = self.wrpc.importmulti(keypool_desc) self.assertTrue(import_result[0]['success']) for i in range(0, 21): addr_info = self.wrpc.getaddressinfo( self.wrpc.getrawchangeaddress()) self.assertEqual(addr_info['hdkeypath'], "m/49'/1'/0'/1/{}".format(i)) keypool_desc = process_commands( self.dev_args + ['getkeypool', '--keypool', '--wpkh', '0', '20']) import_result = self.wrpc.importmulti(keypool_desc) self.assertTrue(import_result[0]['success']) for i in range(0, 21): addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress()) self.assertEqual(addr_info['hdkeypath'], "m/84'/1'/0'/0/{}".format(i)) keypool_desc = process_commands( self.dev_args + ['getkeypool', '--keypool', '--wpkh', '--internal', '0', '20']) import_result = self.wrpc.importmulti(keypool_desc) self.assertTrue(import_result[0]['success']) for i in range(0, 21): addr_info = self.wrpc.getaddressinfo( self.wrpc.getrawchangeaddress()) self.assertEqual(addr_info['hdkeypath'], "m/84'/1'/0'/1/{}".format(i)) keypool_desc = process_commands(self.dev_args + [ 'getkeypool', '--keypool', '--sh_wpkh', '--account', '3', '0', '20' ]) import_result = self.wrpc.importmulti(keypool_desc) self.assertTrue(import_result[0]['success']) for i in range(0, 21): addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress()) self.assertEqual(addr_info['hdkeypath'], "m/49'/1'/3'/0/{}".format(i)) keypool_desc = process_commands(self.dev_args + [ 'getkeypool', '--keypool', '--sh_wpkh', '--internal', '--account', '3', '0', '20' ]) import_result = self.wrpc.importmulti(keypool_desc) self.assertTrue(import_result[0]['success']) for i in range(0, 21): addr_info = self.wrpc.getaddressinfo( self.wrpc.getrawchangeaddress()) self.assertEqual(addr_info['hdkeypath'], "m/49'/1'/3'/1/{}".format(i)) keypool_desc = process_commands( self.dev_args + ['getkeypool', '--keypool', '--wpkh', '--account', '3', '0', '20']) import_result = self.wrpc.importmulti(keypool_desc) self.assertTrue(import_result[0]['success']) for i in range(0, 21): addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress()) self.assertEqual(addr_info['hdkeypath'], "m/84'/1'/3'/0/{}".format(i)) keypool_desc = process_commands(self.dev_args + [ 'getkeypool', '--keypool', '--wpkh', '--internal', '--account', '3', '0', '20' ]) import_result = self.wrpc.importmulti(keypool_desc) self.assertTrue(import_result[0]['success']) for i in range(0, 21): addr_info = self.wrpc.getaddressinfo( self.wrpc.getrawchangeaddress()) self.assertEqual(addr_info['hdkeypath'], "m/84'/1'/3'/1/{}".format(i)) keypool_desc = process_commands( self.dev_args + ['getkeypool', '--keypool', '--path', 'm/0h/0h/4h/*', '0', '20']) import_result = self.wrpc.importmulti(keypool_desc) self.assertTrue(import_result[0]['success']) for i in range(0, 21): addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress()) self.assertEqual(addr_info['hdkeypath'], "m/0'/0'/4'/{}".format(i)) keypool_desc = process_commands( self.dev_args + ['getkeypool', '--keypool', '--path', '/0h/0h/4h/*', '0', '20']) self.assertEqual(keypool_desc['error'], 'Path must start with m/') self.assertEqual(keypool_desc['code'], -7) keypool_desc = process_commands( self.dev_args + ['getkeypool', '--keypool', '--path', 'm/0h/0h/4h/', '0', '20']) self.assertEqual(keypool_desc['error'], 'Path must end with /*') self.assertEqual(keypool_desc['code'], -7)
def test_type_only_autodetech(self): gmxp_res = process_commands(['-t', self.type, 'getmasterxpub']) self.assertEqual(gmxp_res['xpub'], self.master_xpub)
def test_fingerprint_autodetect(self): gmxp_res = process_commands(['-f', self.fingerprint, 'getmasterxpub']) self.assertEqual(gmxp_res['xpub'], self.master_xpub)
def test_path_type(self): gmxp_res = process_commands( ['-t', self.type, '-d', self.path, 'getmasterxpub']) self.assertEqual(gmxp_res['xpub'], self.master_xpub)
def test_getkeypool_bad_args(self): result = process_commands( self.dev_args + ['getkeypool', '--sh_wpkh', '--wpkh', '0', '20']) self.assertIn('error', result) self.assertIn('code', result) self.assertEqual(result['code'], -7)
def hwi_command(args): result = process_commands(args) if 'error' in result: raise ValueError(result['error']) return result
def coldcard_test_suite(simulator, rpc, userpass): # 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']) if len(enum_res) > 0 and 'error' not in enum_res[0]: 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 setup and wipe tests class TestColdcardSetupWipe(DeviceTestCase): def test_setup(self): result = process_commands(self.dev_args + ['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 = process_commands(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) # Generic device tests suite = unittest.TestSuite() suite.addTest( DeviceTestCase.parameterize(TestColdcardSetupWipe, rpc, userpass, 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', '')) suite.addTest( DeviceTestCase.parameterize( TestDeviceConnect, rpc, userpass, 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd' )) suite.addTest( DeviceTestCase.parameterize( TestGetKeypool, rpc, userpass, 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd' )) suite.addTest( DeviceTestCase.parameterize( TestDisplayAddress, rpc, userpass, 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd' )) suite.addTest( DeviceTestCase.parameterize( TestSignMessage, rpc, userpass, 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd' )) suite.addTest( DeviceTestCase.parameterize( TestSignTx, rpc, userpass, 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd' )) return suite