diff --git a/core/tests/test_apps.common.seed.py b/core/tests/test_apps.common.seed.py index a342380a7e..37e8e2bae3 100644 --- a/core/tests/test_apps.common.seed.py +++ b/core/tests/test_apps.common.seed.py @@ -1,6 +1,8 @@ from common import * + +from storage import cache from apps.common import HARDENED, coins -from apps.common.seed import Keychain, Slip21Node, _path_hardened +from apps.common.seed import Keychain, Slip21Node, _path_hardened, get_keychain, with_slip44_keychain from apps.wallet.sign_tx import scripts, addresses from trezor import wire from trezor.crypto import bip39 @@ -112,6 +114,63 @@ class TestKeychain(unittest.TestCase): with self.assertRaises(wire.DataError): keychain.derive([b"SLIP-9999", b"Authentication key"]).key() + def test_get_keychain(self): + seed = bip39.seed(' '.join(['all'] * 12), '') + cache.start_session() + cache.set(cache.APP_COMMON_SEED, seed) + + namespaces = [("secp256k1", [44 | HARDENED])] + keychain = await_result(get_keychain(wire.DUMMY_CONTEXT, namespaces)) + + # valid path: + self.assertIsNotNone(keychain.derive([44 | HARDENED, 1 | HARDENED])) + + # invalid path: + with self.assertRaises(wire.DataError): + keychain.derive([44]) + + def test_with_slip44(self): + seed = bip39.seed(' '.join(['all'] * 12), '') + cache.start_session() + cache.set(cache.APP_COMMON_SEED, seed) + + slip44_id = 42 + valid_path = [44 | HARDENED, slip44_id | HARDENED] + invalid_path = [44 | HARDENED, 99 | HARDENED] + testnet_path = [44 | HARDENED, 1 | HARDENED] + + def check_valid_paths(keychain, *paths): + for path in paths: + self.assertIsNotNone(keychain.derive(path)) + + def check_invalid_paths(keychain, *paths): + for path in paths: + self.assertRaises(wire.DataError, keychain.derive, path) + + @with_slip44_keychain(slip44_id) + async def func_id_only(ctx, msg, keychain): + check_valid_paths(keychain, valid_path) + check_invalid_paths(keychain, testnet_path, invalid_path) + + @with_slip44_keychain(slip44_id, allow_testnet=True) + async def func_allow_testnet(ctx, msg, keychain): + check_valid_paths(keychain, valid_path, testnet_path) + check_invalid_paths(keychain, invalid_path) + + @with_slip44_keychain(slip44_id, curve="ed25519") + async def func_with_curve(ctx, msg, keychain): + check_valid_paths(keychain, valid_path) + check_invalid_paths(keychain, testnet_path, invalid_path) + + i, _ = keychain.match_path(valid_path) + ns_curve, ns = keychain.namespaces[i] + self.assertEqual(ns_curve, "ed25519") + + await_result(func_id_only(wire.DUMMY_CONTEXT, None)) + await_result(func_allow_testnet(wire.DUMMY_CONTEXT, None)) + await_result(func_with_curve(wire.DUMMY_CONTEXT, None)) + + if __name__ == '__main__': unittest.main() diff --git a/core/tests/test_apps.ethereum.keychain.py b/core/tests/test_apps.ethereum.keychain.py new file mode 100644 index 0000000000..7bfdc1239d --- /dev/null +++ b/core/tests/test_apps.ethereum.keychain.py @@ -0,0 +1,216 @@ +from common import * +from storage import cache +from trezor import wire +from trezor.crypto import bip39 +from apps.common.paths import HARDENED + +if not utils.BITCOIN_ONLY: + from apps.ethereum.keychain import ( + from_address_n, + with_keychain_from_path, + with_keychain_from_chain_id, + ) + from apps.ethereum.networks import by_chain_id, by_slip44 + + from trezor.messages.EthereumGetAddress import EthereumGetAddress + from trezor.messages.EthereumSignTx import EthereumSignTx + + +@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") +class TestEthereumKeychain(unittest.TestCase): + def _check_keychain(self, keychain, slip44_id): + # valid address should succeed + valid_addresses = ( + [44 | HARDENED, slip44_id | HARDENED], + [44 | HARDENED, slip44_id | HARDENED, 0 | HARDENED], + [44 | HARDENED, slip44_id | HARDENED, 19 | HARDENED], + [44 | HARDENED, slip44_id | HARDENED, 0 | HARDENED, 0, 0], + ) + for addr in valid_addresses: + keychain.derive(addr) + # invalid address should fail + invalid_addresses = ( + [44 | HARDENED], + [44 | HARDENED, 0 | HARDENED], + [42 | HARDENED, slip44_id | HARDENED], + [0 | HARDENED, slip44_id | HARDENED, 0 | HARDENED], + ) + for addr in invalid_addresses: + self.assertRaises( + wire.DataError, keychain.derive, addr, + ) + + def setUp(self): + cache.start_session() + seed = bip39.seed(" ".join(["all"] * 12), "") + cache.set(cache.APP_COMMON_SEED, seed) + + def test_from_address_n(self): + # valid keychain m/44'/60'/0' + keychain = await_result( + from_address_n( + wire.DUMMY_CONTEXT, [44 | HARDENED, 60 | HARDENED, 0 | HARDENED] + ) + ) + self._check_keychain(keychain, 60) + + def test_from_address_n_unknown(self): + # try Bitcoin slip44 id m/44'/0'/0' + with self.assertRaises(wire.DataError): + await_result( + from_address_n( + wire.DUMMY_CONTEXT, [44 | HARDENED, 0 | HARDENED, 0 | HARDENED] + ) + ) + + def test_bad_address_n(self): + # keychain generated from valid slip44 id but invalid address m/0'/60'/0' + keychain = await_result( + from_address_n( + wire.DUMMY_CONTEXT, [0 | HARDENED, 60 | HARDENED, 0 | HARDENED] + ) + ) + self._check_keychain(keychain, 60) + + def test_with_keychain_from_path(self): + @with_keychain_from_path + async def handler(ctx, msg, keychain): + self._check_keychain(keychain, msg.address_n[1] & ~HARDENED) + + await_result( + handler( + wire.DUMMY_CONTEXT, + EthereumGetAddress( + address_n=[44 | HARDENED, 60 | HARDENED, 0 | HARDENED] + ), + ) + ) + await_result( + handler( + wire.DUMMY_CONTEXT, + EthereumGetAddress( + address_n=[44 | HARDENED, 108 | HARDENED, 0 | HARDENED] + ), + ) + ) + + with self.assertRaises(wire.DataError): + await_result( + handler( + wire.DUMMY_CONTEXT, + EthereumGetAddress( + address_n=[44 | HARDENED, 0 | HARDENED, 0 | HARDENED] + ), + ) + ) + + def test_with_keychain_from_chain_id(self): + @with_keychain_from_chain_id + async def handler_chain_id(ctx, msg, keychain): + network = by_chain_id(msg.chain_id) + # standard tests + self._check_keychain(keychain, network.slip44) + # provided address should succeed too + keychain.derive(msg.address_n) + + await_result( # Ethereum + handler_chain_id( + wire.DUMMY_CONTEXT, + EthereumSignTx( + address_n=[44 | HARDENED, 60 | HARDENED, 0 | HARDENED], + chain_id=1, + ), + ) + ) + + await_result( # Ethereum Classic + handler_chain_id( + wire.DUMMY_CONTEXT, + EthereumSignTx( + address_n=[44 | HARDENED, 61 | HARDENED, 0 | HARDENED], + chain_id=61, + ), + ) + ) + + with self.assertRaises(wire.DataError): + await_result( # unknown chain_id + handler_chain_id( + wire.DUMMY_CONTEXT, + EthereumSignTx( + address_n=[44 | HARDENED, 60 | HARDENED, 0 | HARDENED], + chain_id=123456789, + ), + ) + ) + + with self.assertRaises(wire.DataError): + await_result( # chain_id and network mismatch + handler_chain_id( + wire.DUMMY_CONTEXT, + EthereumSignTx( + address_n=[44 | HARDENED, 60 | HARDENED, 0 | HARDENED], + chain_id=2, + ), + ) + ) + + def test_missing_chain_id(self): + @with_keychain_from_chain_id + async def handler_chain_id(ctx, msg, keychain): + network = by_slip44(msg.address_n[1] & ~HARDENED) + # standard tests + self._check_keychain(keychain, network.slip44) + # provided address should succeed too + keychain.derive(msg.address_n) + + await_result( # Ethereum + handler_chain_id( + wire.DUMMY_CONTEXT, + EthereumSignTx( + address_n=[44 | HARDENED, 60 | HARDENED, 0 | HARDENED], + chain_id=None, + ), + ) + ) + + await_result( # Ethereum Classic + handler_chain_id( + wire.DUMMY_CONTEXT, + EthereumSignTx( + address_n=[44 | HARDENED, 61 | HARDENED, 0 | HARDENED], + ), + ) + ) + + with self.assertRaises(wire.DataError): + await_result( # unknown slip44 id + handler_chain_id( + wire.DUMMY_CONTEXT, + EthereumSignTx( + address_n=[44 | HARDENED, 0 | HARDENED, 0 | HARDENED], + ), + ) + ) + + def test_wanchain(self): + @with_keychain_from_chain_id + async def handler_wanchain(ctx, msg, keychain): + self._check_keychain(keychain, 5718350) + # provided address should succeed too + keychain.derive(msg.address_n) + + await_result( + handler_wanchain( + wire.DUMMY_CONTEXT, + EthereumSignTx( + address_n=[44 | HARDENED, 5718350 | HARDENED, 0 | HARDENED], + chain_id=3, + tx_type=6, + ), + ) + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/core/tests/test_apps.wallet.keychain.py b/core/tests/test_apps.wallet.keychain.py new file mode 100644 index 0000000000..10d1174254 --- /dev/null +++ b/core/tests/test_apps.wallet.keychain.py @@ -0,0 +1,108 @@ +from common import * +from storage import cache +from trezor import wire +from trezor.crypto import bip39 +from apps.common.paths import HARDENED + +from apps.wallet.keychain import get_keychain_for_coin + + +class TestBitcoinKeychain(unittest.TestCase): + def setUp(self): + cache.start_session() + seed = bip39.seed(" ".join(["all"] * 12), "") + cache.set(cache.APP_COMMON_SEED, seed) + + def test_bitcoin(self): + keychain, coin = await_result( + get_keychain_for_coin(wire.DUMMY_CONTEXT, "Bitcoin") + ) + self.assertEqual(coin.coin_name, "Bitcoin") + + valid_addresses = ( + [44 | HARDENED, 0 | HARDENED], + [45 | HARDENED, 123456], + [48 | HARDENED, 0 | HARDENED], + [49 | HARDENED, 0 | HARDENED], + [84 | HARDENED, 0 | HARDENED], + ) + invalid_addresses = ( + [43 | HARDENED, 0 | HARDENED], + [44 | HARDENED, 1 | HARDENED], + ) + + for addr in valid_addresses: + keychain.derive(addr) + + for addr in invalid_addresses: + self.assertRaises(wire.DataError, keychain.derive, addr) + + def test_testnet(self): + keychain, coin = await_result( + get_keychain_for_coin(wire.DUMMY_CONTEXT, "Testnet") + ) + self.assertEqual(coin.coin_name, "Testnet") + + valid_addresses = ( + [44 | HARDENED, 1 | HARDENED], + [45 | HARDENED, 123456], + [48 | HARDENED, 1 | HARDENED], + [49 | HARDENED, 1 | HARDENED], + [84 | HARDENED, 1 | HARDENED], + ) + invalid_addresses = ( + [43 | HARDENED, 1 | HARDENED], + [44 | HARDENED, 0 | HARDENED], + ) + + for addr in valid_addresses: + keychain.derive(addr) + + for addr in invalid_addresses: + self.assertRaises(wire.DataError, keychain.derive, addr) + + def test_unspecified(self): + keychain, coin = await_result(get_keychain_for_coin(wire.DUMMY_CONTEXT, None)) + self.assertEqual(coin.coin_name, "Bitcoin") + keychain.derive([44 | HARDENED, 0 | HARDENED]) + + def test_unknown(self): + with self.assertRaises(wire.DataError): + await_result(get_keychain_for_coin(wire.DUMMY_CONTEXT, "MadeUpCoin2020")) + + +@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") +class TestAltcoinKeychains(unittest.TestCase): + def setUp(self): + cache.start_session() + seed = bip39.seed(" ".join(["all"] * 12), "") + cache.set(cache.APP_COMMON_SEED, seed) + + def test_bcash(self): + keychain, coin = await_result( + get_keychain_for_coin(wire.DUMMY_CONTEXT, "Bcash") + ) + self.assertEqual(coin.coin_name, "Bcash") + + self.assertFalse(coin.segwit) + valid_addresses = ( + [44 | HARDENED, 145 | HARDENED], + [45 | HARDENED, 123456], + [48 | HARDENED, 145 | HARDENED], + ) + invalid_addresses = ( + [43 | HARDENED, 145 | HARDENED], + [44 | HARDENED, 0 | HARDENED], + [49 | HARDENED, 145 | HARDENED], + [84 | HARDENED, 145 | HARDENED], + ) + + for addr in valid_addresses: + keychain.derive(addr) + + for addr in invalid_addresses: + self.assertRaises(wire.DataError, keychain.derive, addr) + + +if __name__ == "__main__": + unittest.main()