mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-06-26 18:02:35 +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.crypto import base58, hashlib
|
||||||
from trezor.messages import CardanoAddressParametersType, CardanoAddressType
|
from trezor.messages import CardanoAddressParametersType, CardanoAddressType
|
||||||
|
|
||||||
from apps.common import HARDENED
|
|
||||||
from apps.common.seed import remove_ed25519_prefix
|
from apps.common.seed import remove_ed25519_prefix
|
||||||
|
|
||||||
from .byron_address import derive_byron_address, validate_output_byron_address
|
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 .helpers.utils import variable_length_encode
|
||||||
from .seed import is_byron_path, is_shelley_path
|
from .seed import is_byron_path, is_shelley_path
|
||||||
|
|
||||||
@ -32,27 +32,6 @@ MIN_ADDRESS_BYTES_LENGTH = 29
|
|||||||
MAX_ADDRESS_BYTES_LENGTH = 65
|
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:
|
def validate_output_address(address: str, protocol_magic: int, network_id: int) -> None:
|
||||||
if address is None or len(address) == 0:
|
if address is None or len(address) == 0:
|
||||||
raise INVALID_ADDRESS
|
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!"
|
"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!")
|
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(
|
def _derive_pointer_address(
|
||||||
keychain: seed.Keychain,
|
keychain: seed.Keychain,
|
||||||
path: List[int],
|
path: List[int],
|
||||||
@ -332,7 +290,7 @@ def _derive_reward_address(
|
|||||||
path: List[int],
|
path: List[int],
|
||||||
network_id: int,
|
network_id: int,
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
if not is_staking_path(path):
|
if not SCHEMA_STAKING.match(path):
|
||||||
raise wire.DataError("Invalid path for reward address!")
|
raise wire.DataError("Invalid path for reward address!")
|
||||||
|
|
||||||
header = _create_address_header(CardanoAddressType.REWARD, network_id)
|
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 import paths
|
||||||
from apps.common.layout import address_n_to_str, show_qr
|
from apps.common.layout import address_n_to_str, show_qr
|
||||||
|
|
||||||
from . import CURVE, seed
|
from . import seed
|
||||||
from .address import derive_human_readable_address, validate_full_path
|
from .address import derive_human_readable_address
|
||||||
from .helpers import protocol_magics, staking_use_cases
|
from .helpers import protocol_magics, staking_use_cases
|
||||||
|
from .helpers.paths import SCHEMA_ADDRESS
|
||||||
from .helpers.utils import to_account_path
|
from .helpers.utils import to_account_path
|
||||||
from .layout import (
|
from .layout import (
|
||||||
show_address,
|
show_address,
|
||||||
@ -26,7 +27,11 @@ async def get_address(
|
|||||||
address_parameters = msg.address_parameters
|
address_parameters = msg.address_parameters
|
||||||
|
|
||||||
await paths.validate_path(
|
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)
|
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.CardanoPublicKey import CardanoPublicKey
|
||||||
from trezor.messages.HDNodeType import HDNodeType
|
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 apps.common.seed import remove_ed25519_prefix
|
||||||
|
|
||||||
from . import CURVE, seed
|
from . import seed
|
||||||
from .helpers import purposes
|
from .helpers.paths import SCHEMA_PUBKEY
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from typing import List
|
from typing import List
|
||||||
@ -21,10 +21,10 @@ async def get_public_key(
|
|||||||
):
|
):
|
||||||
await paths.validate_path(
|
await paths.validate_path(
|
||||||
ctx,
|
ctx,
|
||||||
_validate_path_for_get_public_key,
|
|
||||||
keychain,
|
keychain,
|
||||||
msg.address_n,
|
msg.address_n,
|
||||||
CURVE,
|
# path must match the PUBKEY schema
|
||||||
|
SCHEMA_PUBKEY.match(msg.address_n),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -57,26 +57,3 @@ def _get_public_key(
|
|||||||
)
|
)
|
||||||
|
|
||||||
return CardanoPublicKey(node=node_type, xpub=xpub_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 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 ..seed import is_shelley_path
|
||||||
|
from .paths import SCHEMA_ADDRESS
|
||||||
from .utils import to_account_path
|
from .utils import to_account_path
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
@ -28,7 +29,7 @@ def get(
|
|||||||
) -> int:
|
) -> int:
|
||||||
address_type = address_parameters.address_type
|
address_type = address_parameters.address_type
|
||||||
if address_type == CardanoAddressType.BASE:
|
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
|
return MISMATCH
|
||||||
if not is_shelley_path(address_parameters.address_n):
|
if not is_shelley_path(address_parameters.address_n):
|
||||||
return MISMATCH
|
return MISMATCH
|
||||||
|
@ -6,7 +6,7 @@ from apps.common import mnemonic
|
|||||||
from apps.common.passphrase import get as get_passphrase
|
from apps.common.passphrase import get as get_passphrase
|
||||||
from apps.common.seed import get_seed
|
from apps.common.seed import get_seed
|
||||||
|
|
||||||
from .helpers import seed_namespaces
|
from .helpers import paths
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from apps.common.paths import Bip32Path
|
from apps.common.paths import Bip32Path
|
||||||
@ -17,15 +17,15 @@ class Keychain:
|
|||||||
"""Cardano keychain hard-coded to Byron and Shelley seed namespaces."""
|
"""Cardano keychain hard-coded to Byron and Shelley seed namespaces."""
|
||||||
|
|
||||||
def __init__(self, root: bip32.HDNode) -> None:
|
def __init__(self, root: bip32.HDNode) -> None:
|
||||||
self.byron_root = derive_path_cardano(root, seed_namespaces.BYRON)
|
self.byron_root = derive_path_cardano(root, paths.BYRON_ROOT)
|
||||||
self.shelley_root = derive_path_cardano(root, seed_namespaces.SHELLEY)
|
self.shelley_root = derive_path_cardano(root, paths.SHELLEY_ROOT)
|
||||||
root.__del__()
|
root.__del__()
|
||||||
|
|
||||||
def verify_path(self, path: Bip32Path) -> None:
|
def verify_path(self, path: Bip32Path) -> None:
|
||||||
if not is_byron_path(path) and not is_shelley_path(path):
|
if not is_byron_path(path) and not is_shelley_path(path):
|
||||||
raise wire.DataError("Forbidden key 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):
|
if is_byron_path(path):
|
||||||
return self.byron_root
|
return self.byron_root
|
||||||
elif is_shelley_path(path):
|
elif is_shelley_path(path):
|
||||||
@ -33,13 +33,16 @@ class Keychain:
|
|||||||
else:
|
else:
|
||||||
raise wire.DataError("Forbidden key path")
|
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:
|
def derive(self, node_path: Bip32Path) -> bip32.HDNode:
|
||||||
self.verify_path(node_path)
|
self.verify_path(node_path)
|
||||||
path_root = self._get_path_root(node_path)
|
path_root = self._get_path_root(node_path)
|
||||||
|
|
||||||
# this is true now, so for simplicity we don't branch on path type
|
# this is true now, so for simplicity we don't branch on path type
|
||||||
assert len(seed_namespaces.BYRON) == len(seed_namespaces.SHELLEY)
|
assert len(paths.BYRON_ROOT) == len(paths.SHELLEY_ROOT)
|
||||||
suffix = node_path[len(seed_namespaces.SHELLEY) :]
|
suffix = node_path[len(paths.SHELLEY_ROOT) :]
|
||||||
|
|
||||||
# derive child node from the root
|
# derive child node from the root
|
||||||
return derive_path_cardano(path_root, suffix)
|
return derive_path_cardano(path_root, suffix)
|
||||||
@ -50,11 +53,11 @@ class Keychain:
|
|||||||
|
|
||||||
|
|
||||||
def is_byron_path(path: Bip32Path):
|
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):
|
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:
|
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.paths import validate_path
|
||||||
from apps.common.seed import remove_ed25519_prefix
|
from apps.common.seed import remove_ed25519_prefix
|
||||||
|
|
||||||
from . import CURVE, seed
|
from . import seed
|
||||||
from .address import (
|
from .address import (
|
||||||
derive_address_bytes,
|
derive_address_bytes,
|
||||||
derive_human_readable_address,
|
derive_human_readable_address,
|
||||||
get_address_bytes_unsafe,
|
get_address_bytes_unsafe,
|
||||||
get_public_key_hash,
|
get_public_key_hash,
|
||||||
is_staking_path,
|
|
||||||
validate_full_path,
|
|
||||||
validate_output_address,
|
validate_output_address,
|
||||||
)
|
)
|
||||||
from .byron_address import get_address_attributes
|
from .byron_address import get_address_attributes
|
||||||
@ -30,6 +28,7 @@ from .helpers import (
|
|||||||
protocol_magics,
|
protocol_magics,
|
||||||
staking_use_cases,
|
staking_use_cases,
|
||||||
)
|
)
|
||||||
|
from .helpers.paths import SCHEMA_ADDRESS, SCHEMA_STAKING
|
||||||
from .helpers.utils import to_account_path
|
from .helpers.utils import to_account_path
|
||||||
from .layout import (
|
from .layout import (
|
||||||
confirm_certificate,
|
confirm_certificate,
|
||||||
@ -75,7 +74,9 @@ async def sign_tx(
|
|||||||
validate_network_info(msg.network_id, msg.protocol_magic)
|
validate_network_info(msg.network_id, msg.protocol_magic)
|
||||||
|
|
||||||
for i in msg.inputs:
|
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_outputs(keychain, msg.outputs, msg.protocol_magic, msg.network_id)
|
||||||
_validate_certificates(msg.certificates)
|
_validate_certificates(msg.certificates)
|
||||||
@ -140,7 +141,7 @@ def _validate_outputs(
|
|||||||
|
|
||||||
def _validate_certificates(certificates: List[CardanoTxCertificateType]) -> None:
|
def _validate_certificates(certificates: List[CardanoTxCertificateType]) -> None:
|
||||||
for certificate in certificates:
|
for certificate in certificates:
|
||||||
if not is_staking_path(certificate.path):
|
if not SCHEMA_STAKING.match(certificate.path):
|
||||||
raise INVALID_CERTIFICATE
|
raise INVALID_CERTIFICATE
|
||||||
|
|
||||||
if certificate.type == CardanoCertificateType.STAKE_DELEGATION:
|
if certificate.type == CardanoCertificateType.STAKE_DELEGATION:
|
||||||
@ -150,7 +151,7 @@ def _validate_certificates(certificates: List[CardanoTxCertificateType]) -> None
|
|||||||
|
|
||||||
def _validate_withdrawals(withdrawals: List[CardanoTxWithdrawalType]) -> None:
|
def _validate_withdrawals(withdrawals: List[CardanoTxWithdrawalType]) -> None:
|
||||||
for withdrawal in withdrawals:
|
for withdrawal in withdrawals:
|
||||||
if not is_staking_path(withdrawal.path):
|
if not SCHEMA_STAKING.match(withdrawal.path):
|
||||||
raise INVALID_WITHDRAWAL
|
raise INVALID_WITHDRAWAL
|
||||||
|
|
||||||
if not 0 <= withdrawal.amount < LOVELACE_MAX_SUPPLY:
|
if not 0 <= withdrawal.amount < LOVELACE_MAX_SUPPLY:
|
||||||
|
Loading…
Reference in New Issue
Block a user