diff --git a/common/tests/fixtures/cardano/get_public_key.json b/common/tests/fixtures/cardano/get_public_key.json index f115ba5cc..620e97e20 100644 --- a/common/tests/fixtures/cardano/get_public_key.json +++ b/common/tests/fixtures/cardano/get_public_key.json @@ -57,6 +57,78 @@ "public_key": "be81ace1f63f4f0cae74dd274a72d7818f238bc764ab3e0dc0beb1945b756dca", "chain_code": "29034f036a162ac4f9f9f397b2d1f289754bb6633915f26b199e156f81d05c88" } + }, + { + "parameters": { + "path": "m/1854'/1815'/0'" + }, + "result": { + "public_key": "99694f772a1cffa68ad4a8d911e649cfe990aee720d26893211a8818c4566a42", + "chain_code": "9c3372fcecd832bfe2418319e473b981cdd641e0e12bd878b5e94d93fa0b1552" + } + }, + { + "parameters": { + "path": "m/1854'/1815'/1'" + }, + "result": { + "public_key": "1b66eb903dc15131c0f39c1ac41ca659115ab790c3e2aef129e3095e406abffe", + "chain_code": "0fcb6522d3aa8ad78370c48e62c97eb1315f4234ded8d8fdd9b941c4e5fda0c0" + } + }, + { + "parameters": { + "path": "m/1854'/1815'/2'" + }, + "result": { + "public_key": "29f3535b40978eda8038de3fe85cf08efecff5e565a3dc745660a43a172aaa0a", + "chain_code": "b82dbc91a252a93ec6d3f9e418a4e779c848162777367f729468ec23f409304e" + } + }, + { + "parameters": { + "path": "m/1854'/1815'/3'" + }, + "result": { + "public_key": "bdc40afecb357a08538cd98878b11d0afa7a0a36a8c5f71be1b591ae09088149", + "chain_code": "c4e0b579c132de4204d19f12260ac6bede8ae67f0fe15b9098d4f32a42314924" + } + }, + { + "parameters": { + "path": "m/1855'/1815'/0'" + }, + "result": { + "public_key": "b75258e4f61eb7b313d8554c2fe10673cf214ca2d762bfd53ec3b7846e2ee872", + "chain_code": "9ad37dc23fe1cda7b0ac5574c6f16171cdb2bb723496954770a2bf0e08334e8f" + } + }, + { + "parameters": { + "path": "m/1855'/1815'/1'" + }, + "result": { + "public_key": "a54627d6d16724032172541d4261e7aa87c06395724f1d18975a21d56650bda9", + "chain_code": "bccfd881a3bb7eefbfb885a80909c30892a9a2151f7530c10c68ec6e7a89b28a" + } + }, + { + "parameters": { + "path": "m/1855'/1815'/2'" + }, + "result": { + "public_key": "5307953b17d4195e0b87d61731339dba2c7d543f621d87c1bd73466328372345", + "chain_code": "0e3314c9499b6efec4fa48cd5068dd549ed5ee524c300e754ebc0a63c3bc06ff" + } + }, + { + "parameters": { + "path": "m/1855'/1815'/3'" + }, + "result": { + "public_key": "b404bb5bf674a38ebd6141416df8e1aea9ed04c2ebea659c29145d24db7e67db", + "chain_code": "2dc07d8eda29c2a806295ee42dc1f3bf63814d0c9ee978a24827575dcb5a53bb" + } } ] } diff --git a/core/src/apps/cardano/get_public_key.py b/core/src/apps/cardano/get_public_key.py index 3246c41af..cd4351445 100644 --- a/core/src/apps/cardano/get_public_key.py +++ b/core/src/apps/cardano/get_public_key.py @@ -7,7 +7,7 @@ from trezor.ui.layouts import show_pubkey from apps.common import paths from . import seed -from .helpers.paths import SCHEMA_PUBKEY +from .helpers.paths import SCHEMA_MINT, SCHEMA_PUBKEY from .helpers.utils import derive_public_key if False: @@ -23,7 +23,7 @@ async def get_public_key( keychain, msg.address_n, # path must match the PUBKEY schema - SCHEMA_PUBKEY.match(msg.address_n), + SCHEMA_PUBKEY.match(msg.address_n) or SCHEMA_MINT.match(msg.address_n), ) try: diff --git a/core/src/apps/cardano/helpers/paths.py b/core/src/apps/cardano/helpers/paths.py index 98b4f2b68..184f90fbb 100644 --- a/core/src/apps/cardano/helpers/paths.py +++ b/core/src/apps/cardano/helpers/paths.py @@ -6,13 +6,17 @@ SLIP44_ID = 1815 BYRON_ROOT = [44 | HARDENED, SLIP44_ID | HARDENED] SHELLEY_ROOT = [1852 | HARDENED, SLIP44_ID | HARDENED] +MULTISIG_ROOT = [1854 | HARDENED, SLIP44_ID | HARDENED] +MINTING_ROOT = [1855 | HARDENED, SLIP44_ID | HARDENED] # fmt: off -SCHEMA_PUBKEY = PathSchema.parse("m/[44,1852]'/coin_type'/account'/*", SLIP44_ID) +SCHEMA_PUBKEY = PathSchema.parse("m/[44,1852,1854]'/coin_type'/account'/*", SLIP44_ID) +# minting has a specific schema for key derivation - see CIP-1855 +SCHEMA_MINT = PathSchema.parse("m/1855'/coin_type'/[0-%s]'" % (HARDENED - 1), SLIP44_ID) SCHEMA_PAYMENT = PathSchema.parse("m/[44,1852]'/coin_type'/account'/[0,1]/address_index", SLIP44_ID) # staking is only allowed on Shelley paths with suffix /2/0 -SCHEMA_STAKING = PathSchema.parse("m/1852'/coin_type'/account'/2/0", SLIP44_ID) -SCHEMA_STAKING_ANY_ACCOUNT = PathSchema.parse("m/1852'/coin_type'/[0-%s]'/2/0" % (HARDENED - 1), SLIP44_ID) +SCHEMA_STAKING = PathSchema.parse("m/[1852]'/coin_type'/account'/2/0", SLIP44_ID) +SCHEMA_STAKING_ANY_ACCOUNT = PathSchema.parse("m/[1852]'/coin_type'/[0-%s]'/2/0" % (HARDENED - 1), SLIP44_ID) # fmt: on ACCOUNT_PATH_LENGTH = const(3) diff --git a/core/src/apps/cardano/seed.py b/core/src/apps/cardano/seed.py index 16bcb4301..c5290dba7 100644 --- a/core/src/apps/cardano/seed.py +++ b/core/src/apps/cardano/seed.py @@ -18,15 +18,20 @@ if False: class Keychain: - """Cardano keychain hard-coded to Byron and Shelley seed namespaces.""" + """ + Cardano keychain hard-coded to 44 (Byron), 1852 (Shelley), 1854 (multi-sig) and 1855 (token minting) + seed namespaces. + """ def __init__(self, root: bip32.HDNode) -> None: self.byron_root = derive_path_cardano(root, paths.BYRON_ROOT) self.shelley_root = derive_path_cardano(root, paths.SHELLEY_ROOT) + self.multisig_root = derive_path_cardano(root, paths.MULTISIG_ROOT) + self.minting_root = derive_path_cardano(root, paths.MINTING_ROOT) root.__del__() def verify_path(self, path: Bip32Path) -> None: - if not is_byron_path(path) and not is_shelley_path(path): + if not self.is_in_keychain(path): raise wire.DataError("Forbidden key path") def _get_path_root(self, path: Bip32Path) -> bip32.HDNode: @@ -34,18 +39,31 @@ class Keychain: return self.byron_root elif is_shelley_path(path): return self.shelley_root + elif is_multisig_path(path): + return self.multisig_root + elif is_minting_path(path): + return self.minting_root else: raise wire.DataError("Forbidden key path") def is_in_keychain(self, path: Bip32Path) -> bool: - return is_byron_path(path) or is_shelley_path(path) + return ( + is_byron_path(path) + or is_shelley_path(path) + or is_multisig_path(path) + or is_minting_path(path) + ) def derive(self, node_path: Bip32Path) -> bip32.HDNode: self.verify_path(node_path) path_root = self._get_path_root(node_path) # this is true now, so for simplicity we don't branch on path type - assert len(paths.BYRON_ROOT) == len(paths.SHELLEY_ROOT) + assert ( + len(paths.BYRON_ROOT) == len(paths.SHELLEY_ROOT) + and len(paths.MULTISIG_ROOT) == len(paths.SHELLEY_ROOT) + and len(paths.MINTING_ROOT) == len(paths.SHELLEY_ROOT) + ) suffix = node_path[len(paths.SHELLEY_ROOT) :] # derive child node from the root @@ -64,6 +82,14 @@ def is_shelley_path(path: Bip32Path) -> bool: return path[: len(paths.SHELLEY_ROOT)] == paths.SHELLEY_ROOT +def is_multisig_path(path: Bip32Path) -> bool: + return path[: len(paths.MULTISIG_ROOT)] == paths.MULTISIG_ROOT + + +def is_minting_path(path: Bip32Path) -> bool: + return path[: len(paths.MINTING_ROOT)] == paths.MINTING_ROOT + + def derive_path_cardano(root: bip32.HDNode, path: Bip32Path) -> bip32.HDNode: node = root.clone() for i in path: