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

feat(core): show account number in receive/public key details for altcoins

[no changelog]
This commit is contained in:
grdddj 2023-09-22 11:49:40 +02:00 committed by Jiří Musil
parent 6b3fa353ae
commit 144ff52b7a
20 changed files with 286 additions and 94 deletions

View File

@ -26,8 +26,13 @@ async def get_address(msg: BinanceGetAddress, keychain: Keychain) -> BinanceAddr
pubkey = node.public_key()
address = address_from_public_key(pubkey, HRP)
if msg.show_display:
from . import PATTERN, SLIP44_ID
await show_address(
address, path=paths.address_n_to_str(address_n), chunkify=bool(msg.chunkify)
address,
path=paths.address_n_to_str(address_n),
account=paths.get_account_name("BNB", address_n, PATTERN, SLIP44_ID),
chunkify=bool(msg.chunkify),
)
return BinanceAddress(address=address)

View File

@ -24,7 +24,13 @@ async def get_public_key(
pubkey = node.public_key()
if msg.show_display:
from . import PATTERN, SLIP44_ID
path = paths.address_n_to_str(msg.address_n)
await show_pubkey(hexlify(pubkey).decode(), path=path)
await show_pubkey(
hexlify(pubkey).decode(),
account=paths.get_account_name("BNB", msg.address_n, PATTERN, SLIP44_ID),
path=path,
)
return BinancePublicKey(public_key=pubkey)

View File

@ -372,3 +372,46 @@ def address_n_to_str(address_n: Iterable[int]) -> str:
def unharden(item: int) -> int:
return item ^ (item & HARDENED)
def get_account_name(
coin: str, address_n: Bip32Path, pattern: str | Sequence[str], slip44_id: int
) -> str | None:
account_num = _get_account_num(address_n, pattern, slip44_id)
if account_num is None:
return None
return f"{coin} #{account_num}"
def _get_account_num(
address_n: Bip32Path, pattern: str | Sequence[str], slip44_id: int
) -> int | None:
if isinstance(pattern, str):
pattern = [pattern]
# Trying all possible patterns - at least one should match
for patt in pattern:
try:
return _get_account_num_single(address_n, patt, slip44_id)
except ValueError:
pass
# This function should not raise
return None
def _get_account_num_single(address_n: Bip32Path, pattern: str, slip44_id: int) -> int:
# Validating address_n is compatible with pattern
if not PathSchema.parse(pattern, slip44_id).match(address_n):
raise ValueError
account_pos = pattern.find("/account")
if account_pos >= 0:
i = pattern.count("/", 0, account_pos)
num = address_n[i]
if is_hardened(num):
return unharden(num) + 1
else:
return num + 1
else:
raise ValueError

View File

@ -26,6 +26,9 @@ async def get_public_key(msg: EosGetPublicKey, keychain: Keychain) -> EosPublicK
wif = public_key_to_wif(public_key)
if msg.show_display:
from . import PATTERN, SLIP44_ID
path = paths.address_n_to_str(msg.address_n)
await require_get_public_key(wif, path)
account = paths.get_account_name("EOS", msg.address_n, PATTERN, SLIP44_ID)
await require_get_public_key(wif, path, account)
return EosPublicKey(wif_public_key=wif, raw_public_key=public_key)

View File

@ -1,7 +1,9 @@
async def require_get_public_key(public_key: str, path: str) -> None:
async def require_get_public_key(
public_key: str, path: str, account: str | None
) -> None:
from trezor.ui.layouts import show_pubkey
await show_pubkey(public_key, path=path)
await show_pubkey(public_key, path=path, account=account)
async def require_sign_tx(num_actions: int) -> None:

View File

@ -32,8 +32,14 @@ async def get_address(
address = address_from_bytes(node.ethereum_pubkeyhash(), defs.network)
if msg.show_display:
slip44_id = address_n[1] # it depends on the network (ETH vs ETC...)
await show_address(
address, path=paths.address_n_to_str(address_n), chunkify=bool(msg.chunkify)
address,
path=paths.address_n_to_str(address_n),
account=paths.get_account_name(
"ETH", address_n, PATTERNS_ADDRESS, slip44_id
),
chunkify=bool(msg.chunkify),
)
return EthereumAddress(address=address)

View File

@ -22,10 +22,11 @@ async def get_address(msg: MoneroGetAddress, keychain: Keychain) -> MoneroAddres
account = msg.account # local_cache_attribute
minor = msg.minor # local_cache_attribute
payment_id = msg.payment_id # local_cache_attribute
address_n = msg.address_n # local_cache_attribute
await paths.validate_path(keychain, msg.address_n)
await paths.validate_path(keychain, address_n)
creds = misc.get_creds(keychain, msg.address_n, msg.network_type)
creds = misc.get_creds(keychain, address_n, msg.network_type)
addr = creds.address
have_subaddress = (
@ -65,10 +66,13 @@ async def get_address(msg: MoneroGetAddress, keychain: Keychain) -> MoneroAddres
)
if msg.show_display:
from . import PATTERN, SLIP44_ID
await show_address(
addr,
address_qr="monero:" + addr,
path=paths.address_n_to_str(msg.address_n),
path=paths.address_n_to_str(address_n),
account=paths.get_account_name("XMR", msg.address_n, PATTERN, SLIP44_ID),
chunkify=bool(msg.chunkify),
)

View File

@ -15,7 +15,7 @@ async def get_address(msg: NEMGetAddress, keychain: Keychain) -> NEMAddress:
from trezor.messages import NEMAddress
from trezor.ui.layouts import show_address
from apps.common.paths import address_n_to_str, validate_path
from apps.common import paths
from .helpers import check_path, get_network_str
from .validators import validate_network
@ -24,16 +24,19 @@ async def get_address(msg: NEMGetAddress, keychain: Keychain) -> NEMAddress:
network = msg.network # local_cache_attribute
validate_network(network)
await validate_path(keychain, address_n, check_path(address_n, network))
await paths.validate_path(keychain, address_n, check_path(address_n, network))
node = keychain.derive(address_n)
address = node.nem_address(network)
if msg.show_display:
from . import PATTERNS, SLIP44_ID
await show_address(
address,
case_sensitive=False,
path=address_n_to_str(address_n),
path=paths.address_n_to_str(address_n),
account=paths.get_account_name("NEM", msg.address_n, PATTERNS, SLIP44_ID),
network=get_network_str(network),
chunkify=bool(msg.chunkify),
)

View File

@ -18,16 +18,21 @@ async def get_address(msg: RippleGetAddress, keychain: Keychain) -> RippleAddres
from .helpers import address_from_public_key
await paths.validate_path(keychain, msg.address_n)
address_n = msg.address_n # local_cache_attribute
node = keychain.derive(msg.address_n)
await paths.validate_path(keychain, address_n)
node = keychain.derive(address_n)
pubkey = node.public_key()
address = address_from_public_key(pubkey)
if msg.show_display:
from . import PATTERN, SLIP44_ID
await show_address(
address,
path=paths.address_n_to_str(msg.address_n),
path=paths.address_n_to_str(address_n),
account=paths.get_account_name("XRP", msg.address_n, PATTERN, SLIP44_ID),
chunkify=bool(msg.chunkify),
)

View File

@ -17,16 +17,23 @@ async def get_address(msg: StellarGetAddress, keychain: Keychain) -> StellarAddr
from . import helpers
await paths.validate_path(keychain, msg.address_n)
address_n = msg.address_n # local_cache_attribute
node = keychain.derive(msg.address_n)
await paths.validate_path(keychain, address_n)
node = keychain.derive(address_n)
pubkey = seed.remove_ed25519_prefix(node.public_key())
address = helpers.address_from_public_key(pubkey)
if msg.show_display:
path = paths.address_n_to_str(msg.address_n)
from . import PATTERN, SLIP44_ID
await show_address(
address, case_sensitive=False, path=path, chunkify=bool(msg.chunkify)
address,
case_sensitive=False,
path=paths.address_n_to_str(address_n),
account=paths.get_account_name("XLM", msg.address_n, PATTERN, SLIP44_ID),
chunkify=bool(msg.chunkify),
)
return StellarAddress(address=address)

View File

@ -20,18 +20,23 @@ async def get_address(msg: TezosGetAddress, keychain: Keychain) -> TezosAddress:
from . import helpers
await paths.validate_path(keychain, msg.address_n)
address_n = msg.address_n # local_cache_attribute
node = keychain.derive(msg.address_n)
await paths.validate_path(keychain, address_n)
node = keychain.derive(address_n)
pk = seed.remove_ed25519_prefix(node.public_key())
pkh = hashlib.blake2b(pk, outlen=helpers.PUBLIC_KEY_HASH_SIZE).digest()
address = helpers.base58_encode_check(pkh, helpers.TEZOS_ED25519_ADDRESS_PREFIX)
if msg.show_display:
from . import PATTERNS, SLIP44_ID
await show_address(
address,
path=paths.address_n_to_str(msg.address_n),
path=paths.address_n_to_str(address_n),
account=paths.get_account_name("XTZ", address_n, PATTERNS, SLIP44_ID),
chunkify=bool(msg.chunkify),
)

View File

@ -26,6 +26,10 @@ async def get_public_key(msg: TezosGetPublicKey, keychain: Keychain) -> TezosPub
pk_prefixed = helpers.base58_encode_check(pk, helpers.TEZOS_PUBLICKEY_PREFIX)
if msg.show_display:
await show_pubkey(pk_prefixed)
from . import PATTERNS, SLIP44_ID
account = paths.get_account_name("XTZ", msg.address_n, PATTERNS, SLIP44_ID)
path = paths.address_n_to_str(msg.address_n)
await show_pubkey(pk_prefixed, account=account, path=path)
return TezosPublicKey(public_key=pk_prefixed)

View File

@ -20,26 +20,41 @@ from trezorlib.binance import get_address
from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.tools import parse_path
from ...input_flows import InputFlowShowAddressQRCode
pytestmark = [
pytest.mark.altcoin,
pytest.mark.binance,
pytest.mark.skip_t1, # T1 support is not planned
pytest.mark.setup_client(
mnemonic="offer caution gift cross surge pretty orange during eye soldier popular holiday mention east eight office fashion ill parrot vault rent devote earth cousin"
),
]
BINANCE_ADDRESS_TEST_VECTORS = [
("m/44h/714h/0h/0/0", "bnb1hgm0p7khfk85zpz5v0j8wnej3a90w709vhkdfu"),
("m/44h/714h/0h/0/1", "bnb1egswqkszzfc2uq78zjslc6u2uky4pw46x4rstd"),
]
@pytest.mark.altcoin
@pytest.mark.binance
@pytest.mark.skip_t1 # T1 support is not planned
@pytest.mark.setup_client(
mnemonic="offer caution gift cross surge pretty orange during eye soldier popular holiday mention east eight office fashion ill parrot vault rent devote earth cousin"
)
@pytest.mark.parametrize("chunkify", (True, False))
@pytest.mark.parametrize("path, expected_address", BINANCE_ADDRESS_TEST_VECTORS)
def test_binance_get_address(
client: Client, chunkify: bool, path: str, expected_address: str
def test_binance_get_address(client: Client, path: str, expected_address: str):
# data from https://github.com/binance-chain/javascript-sdk/blob/master/__tests__/crypto.test.js#L50
address = get_address(client, parse_path(path), show_display=True)
assert address == expected_address
@pytest.mark.parametrize("path, expected_address", BINANCE_ADDRESS_TEST_VECTORS)
def test_binance_get_address_chunkify_details(
client: Client, path: str, expected_address: str
):
# data from https://github.com/binance-chain/javascript-sdk/blob/master/__tests__/crypto.test.js#L50
address = get_address(
client, parse_path(path), show_display=True, chunkify=chunkify
)
assert address == expected_address
with client:
IF = InputFlowShowAddressQRCode(client)
client.set_input_flow(IF.get())
address = get_address(
client, parse_path(path), show_display=True, chunkify=True
)
assert address == expected_address

View File

@ -21,15 +21,27 @@ from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.tools import parse_path
from ...common import parametrize_using_common_fixtures
from ...input_flows import InputFlowShowAddressQRCode
pytestmark = [pytest.mark.altcoin, pytest.mark.ethereum]
@parametrize_using_common_fixtures("ethereum/getaddress.json")
@pytest.mark.parametrize("chunkify", (True, False))
def test_getaddress(client: Client, chunkify: bool, parameters, result):
def test_getaddress(client: Client, parameters, result):
address_n = parse_path(parameters["path"])
assert (
ethereum.get_address(client, address_n, show_display=True, chunkify=chunkify)
== result["address"]
ethereum.get_address(client, address_n, show_display=True) == result["address"]
)
@pytest.mark.skip_t1("No input flow for T1")
@parametrize_using_common_fixtures("ethereum/getaddress.json")
def test_getaddress_chunkify_details(client: Client, parameters, result):
with client:
IF = InputFlowShowAddressQRCode(client)
client.set_input_flow(IF.get())
address_n = parse_path(parameters["path"])
assert (
ethereum.get_address(client, address_n, show_display=True, chunkify=True)
== result["address"]
)

View File

@ -21,29 +21,45 @@ from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.tools import parse_path
from ...common import MNEMONIC12
from ...input_flows import InputFlowShowAddressQRCode
TEST_VECTORS = [
(
"m/44h/128h/0h",
b"4Ahp23WfMrMFK3wYL2hLWQFGt87ZTeRkufS6JoQZu6MEFDokAQeGWmu9MA3GFq1yVLSJQbKJqVAn9F9DLYGpRzRAEXqAXKM",
),
(
"m/44h/128h/1h",
b"44iAazhoAkv5a5RqLNVyh82a1n3ceNggmN4Ho7bUBJ14WkEVR8uFTe9f7v5rNnJ2kEbVXxfXiRzsD5Jtc6NvBi4D6WNHPie",
),
(
"m/44h/128h/2h",
b"47ejhmbZ4wHUhXaqA4b7PN667oPMkokf4ZkNdWrMSPy9TNaLVr7vLqVUQHh2MnmaAEiyrvLsX8xUf99q3j1iAeMV8YvSFcH",
),
]
pytestmark = [
pytest.mark.altcoin,
pytest.mark.monero,
pytest.mark.skip_t1,
pytest.mark.setup_client(mnemonic=MNEMONIC12),
]
@pytest.mark.altcoin
@pytest.mark.monero
@pytest.mark.skip_t1
@pytest.mark.setup_client(mnemonic=MNEMONIC12)
@pytest.mark.parametrize("chunkify", (True, False))
def test_monero_getaddress(client: Client, chunkify: bool):
assert (
monero.get_address(
client, parse_path("m/44h/128h/0h"), show_display=True, chunkify=chunkify
@pytest.mark.parametrize("path, expected_address", TEST_VECTORS)
def test_monero_getaddress(client: Client, path: str, expected_address: bytes):
address = monero.get_address(client, parse_path(path), show_display=True)
assert address == expected_address
@pytest.mark.parametrize("path, expected_address", TEST_VECTORS)
def test_monero_getaddress_chunkify_details(
client: Client, path: str, expected_address: bytes
):
with client:
IF = InputFlowShowAddressQRCode(client)
client.set_input_flow(IF.get())
address = monero.get_address(
client, parse_path(path), show_display=True, chunkify=True
)
== b"4Ahp23WfMrMFK3wYL2hLWQFGt87ZTeRkufS6JoQZu6MEFDokAQeGWmu9MA3GFq1yVLSJQbKJqVAn9F9DLYGpRzRAEXqAXKM"
)
assert (
monero.get_address(
client, parse_path("m/44h/128h/1h"), show_display=True, chunkify=chunkify
)
== b"44iAazhoAkv5a5RqLNVyh82a1n3ceNggmN4Ho7bUBJ14WkEVR8uFTe9f7v5rNnJ2kEbVXxfXiRzsD5Jtc6NvBi4D6WNHPie"
)
assert (
monero.get_address(
client, parse_path("m/44h/128h/2h"), show_display=True, chunkify=chunkify
)
== b"47ejhmbZ4wHUhXaqA4b7PN667oPMkokf4ZkNdWrMSPy9TNaLVr7vLqVUQHh2MnmaAEiyrvLsX8xUf99q3j1iAeMV8YvSFcH"
)
assert address == expected_address

View File

View File

@ -20,6 +20,8 @@ from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.ripple import get_address
from trezorlib.tools import parse_path
from ...input_flows import InputFlowShowAddressQRCode
CUSTOM_MNEMONIC = (
"armed bundle pudding lazy strategy impulse where identify "
"submit weekend physical antenna flight social acoustic absurd "
@ -32,15 +34,31 @@ pytestmark = [
pytest.mark.skip_t1, # T1 support is not planned
]
# data from https://iancoleman.io/bip39/
TEST_VECTORS = [
("m/44h/144h/0h/0/0", "rNaqKtKrMSwpwZSzRckPf7S96DkimjkF4H"),
("m/44h/144h/0h/0/1", "rBKz5MC2iXdoS3XgnNSYmF69K1Yo4NS3Ws"),
("m/44h/144h/1h/0/0", "rJX2KwzaLJDyFhhtXKi3htaLfaUH2tptEX"),
]
def test_ripple_get_address(client: Client):
# data from https://iancoleman.io/bip39/
address = get_address(client, parse_path("m/44h/144h/0h/0/0"))
assert address == "rNaqKtKrMSwpwZSzRckPf7S96DkimjkF4H"
address = get_address(client, parse_path("m/44h/144h/0h/0/1"))
assert address == "rBKz5MC2iXdoS3XgnNSYmF69K1Yo4NS3Ws"
address = get_address(client, parse_path("m/44h/144h/1h/0/0"))
assert address == "rJX2KwzaLJDyFhhtXKi3htaLfaUH2tptEX"
@pytest.mark.parametrize("path, expected_address", TEST_VECTORS)
def test_ripple_get_address(client: Client, path: str, expected_address: str):
address = get_address(client, parse_path(path), show_display=True)
assert address == expected_address
@pytest.mark.parametrize("path, expected_address", TEST_VECTORS)
def test_ripple_get_address_chunkify_details(
client: Client, path: str, expected_address: str
):
with client:
IF = InputFlowShowAddressQRCode(client)
client.set_input_flow(IF.get())
address = get_address(
client, parse_path(path), show_display=True, chunkify=True
)
assert address == expected_address
@pytest.mark.setup_client(mnemonic=CUSTOM_MNEMONIC)

View File

@ -59,6 +59,12 @@ from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.tools import parse_path
from ...common import parametrize_using_common_fixtures
from ...input_flows import InputFlowShowAddressQRCode
pytestmark = [
pytest.mark.altcoin,
pytest.mark.stellar,
]
def parameters_to_proto(parameters):
@ -80,8 +86,6 @@ def parameters_to_proto(parameters):
return tx, operations
@pytest.mark.altcoin
@pytest.mark.stellar
@parametrize_using_common_fixtures("stellar/sign_tx.json")
def test_sign_tx(client: Client, parameters, result):
tx, operations = parameters_to_proto(parameters)
@ -92,8 +96,6 @@ def test_sign_tx(client: Client, parameters, result):
assert b64encode(response.signature).decode() == result["signature"]
@pytest.mark.altcoin
@pytest.mark.stellar
@parametrize_using_common_fixtures("stellar/sign_tx.json")
@pytest.mark.skipif(not stellar.HAVE_STELLAR_SDK, reason="requires Stellar SDK")
def test_xdr(parameters, result):
@ -110,13 +112,21 @@ def test_xdr(parameters, result):
assert expected == actual
@pytest.mark.altcoin
@pytest.mark.stellar
@pytest.mark.parametrize("chunkify", (True, False))
@parametrize_using_common_fixtures("stellar/get_address.json")
def test_get_address(client: Client, chunkify: bool, parameters, result):
def test_get_address(client: Client, parameters, result):
address_n = parse_path(parameters["path"])
address = stellar.get_address(
client, address_n, show_display=True, chunkify=chunkify
)
address = stellar.get_address(client, address_n, show_display=True)
assert address == result["address"]
@pytest.mark.skip_t1("No input flow for T1")
@parametrize_using_common_fixtures("stellar/get_address.json")
def test_get_address_chunkify_details(client: Client, parameters, result):
with client:
IF = InputFlowShowAddressQRCode(client)
client.set_input_flow(IF.get())
address_n = parse_path(parameters["path"])
address = stellar.get_address(
client, address_n, show_display=True, chunkify=True
)
assert address == result["address"]

View File

@ -20,16 +20,34 @@ from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.tezos import get_address
from trezorlib.tools import parse_path
from ...input_flows import InputFlowShowAddressQRCode
@pytest.mark.altcoin
@pytest.mark.tezos
@pytest.mark.skip_t1
@pytest.mark.parametrize("chunkify", (True, False))
def test_tezos_get_address(client: Client, chunkify: bool):
path = parse_path("m/44h/1729h/0h")
address = get_address(client, path, show_display=True, chunkify=chunkify)
assert address == "tz1Kef7BSg6fo75jk37WkKRYSnJDs69KVqt9"
pytestmark = [
pytest.mark.altcoin,
pytest.mark.tezos,
pytest.mark.skip_t1,
]
path = parse_path("m/44h/1729h/1h")
address = get_address(client, path, show_display=True, chunkify=chunkify)
assert address == "tz1ekQapZCX4AXxTJhJZhroDKDYLHDHegvm1"
TEST_VECTORS = [
("m/44h/1729h/0h", "tz1Kef7BSg6fo75jk37WkKRYSnJDs69KVqt9"),
("m/44h/1729h/1h", "tz1ekQapZCX4AXxTJhJZhroDKDYLHDHegvm1"),
]
@pytest.mark.parametrize("path, expected_address", TEST_VECTORS)
def test_tezos_get_address(client: Client, path: str, expected_address: str):
address = get_address(client, parse_path(path), show_display=True)
assert address == expected_address
@pytest.mark.parametrize("path, expected_address", TEST_VECTORS)
def test_tezos_get_address_chunkify_details(
client: Client, path: str, expected_address: str
):
with client:
IF = InputFlowShowAddressQRCode(client)
client.set_input_flow(IF.get())
address = get_address(
client, parse_path(path), show_display=True, chunkify=True
)
assert address == expected_address

View File

@ -273,7 +273,15 @@ class InputFlowShowAddressQRCode(InputFlowBase):
self.debug.press_yes()
def input_flow_tr(self) -> BRGeneratorType:
yield
# Find out the page-length of the address
br = yield
if br.pages is not None:
address_swipes = br.pages - 1
else:
address_swipes = 0
for _ in range(address_swipes):
self.debug.press_right()
# Go into details
self.debug.press_right()
# Go through details and back
@ -281,6 +289,8 @@ class InputFlowShowAddressQRCode(InputFlowBase):
self.debug.press_left()
self.debug.press_left()
# Confirm
for _ in range(address_swipes):
self.debug.press_right()
self.debug.press_middle()