1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-22 22:38:08 +00:00

feat(core/cardano): use path schemas

This commit is contained in:
matejcik 2020-08-14 15:40:00 +02:00 committed by matejcik
parent f5c8138df6
commit c0879f8625
9 changed files with 52 additions and 103 deletions

View File

@ -2,11 +2,11 @@ from trezor import wire
from trezor.crypto import base58, hashlib
from trezor.messages import CardanoAddressParametersType, CardanoAddressType
from apps.common import HARDENED
from apps.common.seed import remove_ed25519_prefix
from .byron_address import derive_byron_address, validate_output_byron_address
from .helpers import INVALID_ADDRESS, NETWORK_MISMATCH, bech32, network_ids, purposes
from .helpers import INVALID_ADDRESS, NETWORK_MISMATCH, bech32, network_ids
from .helpers.paths import SCHEMA_STAKING
from .helpers.utils import variable_length_encode
from .seed import is_byron_path, is_shelley_path
@ -32,27 +32,6 @@ MIN_ADDRESS_BYTES_LENGTH = 29
MAX_ADDRESS_BYTES_LENGTH = 65
def validate_full_path(path: List[int]) -> bool:
"""
Validates derivation path to fit {44', 1852'}/1815'/a'/{0,1,2}/i,
where `a` is an account number and i an address index.
The max value for `a` is 20, 1 000 000 for `i`.
"""
if len(path) != 5:
return False
if path[0] not in (purposes.BYRON, purposes.SHELLEY):
return False
if path[1] != 1815 | HARDENED:
return False
if path[2] < HARDENED or path[2] > 20 | HARDENED:
return False
if path[3] not in (0, 1, 2):
return False
if path[4] > 1000000:
return False
return True
def validate_output_address(address: str, protocol_magic: int, network_id: int) -> None:
if address is None or len(address) == 0:
raise INVALID_ADDRESS
@ -262,31 +241,10 @@ def _validate_base_address_staking_info(
"Base address needs either a staking path or a staking key hash!"
)
if staking_key_hash is None and not is_staking_path(staking_path):
if staking_key_hash is None and not SCHEMA_STAKING.match(staking_path):
raise wire.DataError("Invalid staking path!")
def is_staking_path(path: List[int]) -> bool:
"""
Validates path to match 1852'/1815'/a'/2/0. Path must
be a valid Cardano path. It must have a Shelley purpose
(Byron paths are not valid staking paths), it must have
2 as chain type and currently there is only one staking
path for each account so a 0 is required for address index.
"""
if not validate_full_path(path):
return False
if path[0] != purposes.SHELLEY:
return False
if path[3] != 2:
return False
if path[4] != 0:
return False
return True
def _derive_pointer_address(
keychain: seed.Keychain,
path: List[int],
@ -332,7 +290,7 @@ def _derive_reward_address(
path: List[int],
network_id: int,
) -> bytes:
if not is_staking_path(path):
if not SCHEMA_STAKING.match(path):
raise wire.DataError("Invalid path for reward address!")
header = _create_address_header(CardanoAddressType.REWARD, network_id)

View File

@ -4,9 +4,10 @@ from trezor.messages.CardanoAddress import CardanoAddress
from apps.common import paths
from apps.common.layout import address_n_to_str, show_qr
from . import CURVE, seed
from .address import derive_human_readable_address, validate_full_path
from . import seed
from .address import derive_human_readable_address
from .helpers import protocol_magics, staking_use_cases
from .helpers.paths import SCHEMA_ADDRESS
from .helpers.utils import to_account_path
from .layout import (
show_address,
@ -26,7 +27,11 @@ async def get_address(
address_parameters = msg.address_parameters
await paths.validate_path(
ctx, validate_full_path, keychain, address_parameters.address_n, CURVE
ctx,
keychain,
address_parameters.address_n,
# path must match the ADDRESS schema
SCHEMA_ADDRESS.match(address_parameters.address_n),
)
validate_network_info(msg.network_id, msg.protocol_magic)

View File

@ -4,11 +4,11 @@ from trezor import log, wire
from trezor.messages.CardanoPublicKey import CardanoPublicKey
from trezor.messages.HDNodeType import HDNodeType
from apps.common import HARDENED, layout, paths
from apps.common import layout, paths
from apps.common.seed import remove_ed25519_prefix
from . import CURVE, seed
from .helpers import purposes
from . import seed
from .helpers.paths import SCHEMA_PUBKEY
if False:
from typing import List
@ -21,10 +21,10 @@ async def get_public_key(
):
await paths.validate_path(
ctx,
_validate_path_for_get_public_key,
keychain,
msg.address_n,
CURVE,
# path must match the PUBKEY schema
SCHEMA_PUBKEY.match(msg.address_n),
)
try:
@ -57,26 +57,3 @@ def _get_public_key(
)
return CardanoPublicKey(node=node_type, xpub=xpub_key)
def _validate_path_for_get_public_key(path: List[int]) -> bool:
"""
Modified version of paths.validate_path_for_get_public_key.
Checks if path has at least three hardened items, Byron or Shelley purpose
and slip44 id 1815. The path is allowed to have more than three items,
but all the following items have to be non-hardened.
"""
length = len(path)
if length < 3 or length > 5:
return False
if path[0] not in (purposes.BYRON, purposes.SHELLEY):
return False
if path[1] != 1815 | HARDENED:
return False
if path[2] < HARDENED or path[2] > 20 | HARDENED:
return False
if length > 3 and paths.is_hardened(path[3]):
return False
if length > 4 and paths.is_hardened(path[4]):
return False
return True

View File

@ -0,0 +1,14 @@
from apps.common import HARDENED
from apps.common.paths import PathSchema
SLIP44_ID = 1815
BYRON_ROOT = [44 | HARDENED, SLIP44_ID | HARDENED]
SHELLEY_ROOT = [1852 | HARDENED, SLIP44_ID | HARDENED]
# fmt: off
SCHEMA_PUBKEY = PathSchema("m/[44,1852]'/coin_type'/account'/*", SLIP44_ID)
SCHEMA_ADDRESS = PathSchema("m/[44,1852]'/coin_type'/account'/[0,1,2]/address_index", SLIP44_ID)
# staking is only allowed on Shelley paths with suffix /2/0
SCHEMA_STAKING = PathSchema("m/1852'/coin_type'/account'/2/0", SLIP44_ID)
# fmt: on

View File

@ -1,4 +0,0 @@
from apps.common import HARDENED
BYRON = 44 | HARDENED
SHELLEY = 1852 | HARDENED

View File

@ -1,6 +0,0 @@
from apps.common import HARDENED
from . import purposes
BYRON = [purposes.BYRON, 1815 | HARDENED]
SHELLEY = [purposes.SHELLEY, 1815 | HARDENED]

View File

@ -1,7 +1,8 @@
from trezor.messages import CardanoAddressType
from ..address import get_public_key_hash, validate_full_path
from ..address import get_public_key_hash
from ..seed import is_shelley_path
from .paths import SCHEMA_ADDRESS
from .utils import to_account_path
if False:
@ -28,7 +29,7 @@ def get(
) -> int:
address_type = address_parameters.address_type
if address_type == CardanoAddressType.BASE:
if not validate_full_path(address_parameters.address_n):
if not SCHEMA_ADDRESS.match(address_parameters.address_n):
return MISMATCH
if not is_shelley_path(address_parameters.address_n):
return MISMATCH

View File

@ -6,7 +6,7 @@ from apps.common import mnemonic
from apps.common.passphrase import get as get_passphrase
from apps.common.seed import get_seed
from .helpers import seed_namespaces
from .helpers import paths
if False:
from apps.common.paths import Bip32Path
@ -17,15 +17,15 @@ class Keychain:
"""Cardano keychain hard-coded to Byron and Shelley seed namespaces."""
def __init__(self, root: bip32.HDNode) -> None:
self.byron_root = derive_path_cardano(root, seed_namespaces.BYRON)
self.shelley_root = derive_path_cardano(root, seed_namespaces.SHELLEY)
self.byron_root = derive_path_cardano(root, paths.BYRON_ROOT)
self.shelley_root = derive_path_cardano(root, paths.SHELLEY_ROOT)
root.__del__()
def verify_path(self, path: Bip32Path) -> None:
if not is_byron_path(path) and not is_shelley_path(path):
raise wire.DataError("Forbidden key path")
def _get_path_root(self, path: list):
def _get_path_root(self, path: Bip32Path) -> bip32.HDNode:
if is_byron_path(path):
return self.byron_root
elif is_shelley_path(path):
@ -33,13 +33,16 @@ class Keychain:
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)
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(seed_namespaces.BYRON) == len(seed_namespaces.SHELLEY)
suffix = node_path[len(seed_namespaces.SHELLEY) :]
assert len(paths.BYRON_ROOT) == len(paths.SHELLEY_ROOT)
suffix = node_path[len(paths.SHELLEY_ROOT) :]
# derive child node from the root
return derive_path_cardano(path_root, suffix)
@ -50,11 +53,11 @@ class Keychain:
def is_byron_path(path: Bip32Path):
return path[: len(seed_namespaces.BYRON)] == seed_namespaces.BYRON
return path[: len(paths.BYRON_ROOT)] == paths.BYRON_ROOT
def is_shelley_path(path: Bip32Path):
return path[: len(seed_namespaces.SHELLEY)] == seed_namespaces.SHELLEY
return path[: len(paths.SHELLEY_ROOT)] == paths.SHELLEY_ROOT
def derive_path_cardano(root: bip32.HDNode, path: Bip32Path) -> bip32.HDNode:

View File

@ -11,14 +11,12 @@ from apps.common import cbor
from apps.common.paths import validate_path
from apps.common.seed import remove_ed25519_prefix
from . import CURVE, seed
from . import seed
from .address import (
derive_address_bytes,
derive_human_readable_address,
get_address_bytes_unsafe,
get_public_key_hash,
is_staking_path,
validate_full_path,
validate_output_address,
)
from .byron_address import get_address_attributes
@ -30,6 +28,7 @@ from .helpers import (
protocol_magics,
staking_use_cases,
)
from .helpers.paths import SCHEMA_ADDRESS, SCHEMA_STAKING
from .helpers.utils import to_account_path
from .layout import (
confirm_certificate,
@ -75,7 +74,9 @@ async def sign_tx(
validate_network_info(msg.network_id, msg.protocol_magic)
for i in msg.inputs:
await validate_path(ctx, validate_full_path, keychain, i.address_n, CURVE)
await validate_path(
ctx, keychain, i.address_n, SCHEMA_ADDRESS.match(i.address_n)
)
_validate_outputs(keychain, msg.outputs, msg.protocol_magic, msg.network_id)
_validate_certificates(msg.certificates)
@ -140,7 +141,7 @@ def _validate_outputs(
def _validate_certificates(certificates: List[CardanoTxCertificateType]) -> None:
for certificate in certificates:
if not is_staking_path(certificate.path):
if not SCHEMA_STAKING.match(certificate.path):
raise INVALID_CERTIFICATE
if certificate.type == CardanoCertificateType.STAKE_DELEGATION:
@ -150,7 +151,7 @@ def _validate_certificates(certificates: List[CardanoTxCertificateType]) -> None
def _validate_withdrawals(withdrawals: List[CardanoTxWithdrawalType]) -> None:
for withdrawal in withdrawals:
if not is_staking_path(withdrawal.path):
if not SCHEMA_STAKING.match(withdrawal.path):
raise INVALID_WITHDRAWAL
if not 0 <= withdrawal.amount < LOVELACE_MAX_SUPPLY: