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:
parent
f5c8138df6
commit
c0879f8625
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
14
core/src/apps/cardano/helpers/paths.py
Normal file
14
core/src/apps/cardano/helpers/paths.py
Normal 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
|
@ -1,4 +0,0 @@
|
||||
from apps.common import HARDENED
|
||||
|
||||
BYRON = 44 | HARDENED
|
||||
SHELLEY = 1852 | HARDENED
|
@ -1,6 +0,0 @@
|
||||
from apps.common import HARDENED
|
||||
|
||||
from . import purposes
|
||||
|
||||
BYRON = [purposes.BYRON, 1815 | HARDENED]
|
||||
SHELLEY = [purposes.SHELLEY, 1815 | HARDENED]
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user