From f4155107f39fc968d08aec42f271045b31749b72 Mon Sep 17 00:00:00 2001 From: Ritchie Date: Thu, 13 Nov 2025 17:10:39 +0800 Subject: [PATCH 1/4] fix(core): Bitcoin-only firmware adaptation --- .../ur_registry/chains/base_sign_request.py | 16 +- .../apps/ur_registry/crypto_multi_accounts.py | 69 +++-- core/src/trezor/lvglui/scrs/homescreen.py | 261 ++++++++++-------- 3 files changed, 192 insertions(+), 154 deletions(-) diff --git a/core/src/apps/ur_registry/chains/base_sign_request.py b/core/src/apps/ur_registry/chains/base_sign_request.py index 6e59da41c..f24acfcbd 100644 --- a/core/src/apps/ur_registry/chains/base_sign_request.py +++ b/core/src/apps/ur_registry/chains/base_sign_request.py @@ -140,7 +140,7 @@ def from_cbor(cls, cbor): @classmethod def decode(cls, decoder): - sol_sign_req = cls() + sign_req = cls() size, _ = decoder.decodeMapSize() for _ in range(size): key, _ = decoder.decodeInteger() @@ -148,21 +148,21 @@ def decode(cls, decoder): tag, _ = decoder.decodeTag() if tag != UUID.get_tag(): raise Exception(f"Expected Tag {tag}") - sol_sign_req.request_id, _ = decoder.decodeBytes() + sign_req.request_id, _ = decoder.decodeBytes() if key == SIGN_DATA: - sol_sign_req.sign_data, _ = decoder.decodeBytes() + sign_req.sign_data, _ = decoder.decodeBytes() if key == DERIVATION_PATH: tag, _ = decoder.decodeTag() if tag != CryptoKeyPath.get_tag(): raise Exception(f"Expected Tag {tag}") - sol_sign_req.derivation_path = CryptoKeyPath.decode(decoder) + sign_req.derivation_path = CryptoKeyPath.decode(decoder) if key == ADDRESS: - sol_sign_req.address, _ = decoder.decodeBytes() + sign_req.address, _ = decoder.decodeBytes() if key == ORIGIN: - sol_sign_req.origin, _ = decoder.decodeText() + sign_req.origin, _ = decoder.decodeText() if key == REQUEST_TYPE: - sol_sign_req.request_type, _ = decoder.decodeInteger() - return sol_sign_req + sign_req.request_type, _ = decoder.decodeInteger() + return sign_req def get_address_n(self) -> list[int]: path = self.derivation_path.get_path() if self.derivation_path else "" diff --git a/core/src/apps/ur_registry/crypto_multi_accounts.py b/core/src/apps/ur_registry/crypto_multi_accounts.py index 61c8bab6c..e76bf38bb 100644 --- a/core/src/apps/ur_registry/crypto_multi_accounts.py +++ b/core/src/apps/ur_registry/crypto_multi_accounts.py @@ -149,9 +149,6 @@ async def generate_crypto_multi_accounts(ctx: wire.Context) -> UREncoder: from storage import device from trezor import utils - eth_pub = await bitcoin_get_public_key.get_public_key( - ctx, GetPublicKey(address_n=paths.parse_path(helpers.ETH_STANDARD_PREFIX)) - ) btc_legacy_pub = await bitcoin_get_public_key.get_public_key( ctx, GetPublicKey(address_n=paths.parse_path(helpers.BTC_LEGACY_PREFIX)) ) @@ -164,35 +161,47 @@ async def generate_crypto_multi_accounts(ctx: wire.Context) -> UREncoder: btc_taproot_pub = await bitcoin_get_public_key.get_public_key( ctx, GetPublicKey(address_n=paths.parse_path(helpers.BTC_TAPROOT_PREFIX)) ) - tron_pub = await bitcoin_get_public_key.get_public_key( - ctx, - GetPublicKey(address_n=paths.parse_path(helpers.TRON_STANDARD_PATH)), - ) - sol_pubs = await misc_batch_get_pubkeys.batch_get_pubkeys( - ctx, - BatchGetPublickeys( - ecdsa_curve_name="ed25519", - paths=[ - Path(address_n=paths.parse_path(helpers.SOL_STANDARD_PATH)), - Path(address_n=paths.parse_path(helpers.SOL_LEDGER_LIVE_PATH)), - ], - ), - ) + keys = [ + helpers.generate_hdkey_BTCLegacy(btc_legacy_pub), + helpers.generate_hdkey_BTCSegWit(btc_segwit_pub), + helpers.generate_hdkey_BTCNativeSegWit(btc_native_segwit_pub), + helpers.generate_hdkey_BTCTaproot(btc_taproot_pub), + ] + if not utils.BITCOIN_ONLY: + eth_pub = await bitcoin_get_public_key.get_public_key( + ctx, GetPublicKey(address_n=paths.parse_path(helpers.ETH_STANDARD_PREFIX)) + ) + tron_pub = await bitcoin_get_public_key.get_public_key( + ctx, + GetPublicKey(address_n=paths.parse_path(helpers.TRON_STANDARD_PATH)), + ) + sol_pubs = await misc_batch_get_pubkeys.batch_get_pubkeys( + ctx, + BatchGetPublickeys( + ecdsa_curve_name="ed25519", + paths=[ + Path(address_n=paths.parse_path(helpers.SOL_STANDARD_PATH)), + Path(address_n=paths.parse_path(helpers.SOL_LEDGER_LIVE_PATH)), + ], + ), + ) + keys.extend( + [ + helpers.generate_hdkey_ETHStandard(ctx, eth_pub, False), + helpers.generate_hdkey_TRONStandard(tron_pub), + helpers.generate_hdkey_SOLStandard(sol_pubs.public_keys[0]), + helpers.generate_hdkey_SOLLedgerLive(sol_pubs.public_keys[1]), + ] + ) + + assert ( + btc_legacy_pub.root_fingerprint is not None + ), "Root fingerprint should not be None" + name = helpers.reveal_name(ctx, btc_legacy_pub.root_fingerprint) - assert eth_pub.root_fingerprint is not None, "Root fingerprint should not be None" - name = helpers.reveal_name(ctx, eth_pub.root_fingerprint) cma = CryptoMultiAccounts( - eth_pub.root_fingerprint, - [ - helpers.generate_hdkey_ETHStandard(ctx, eth_pub, False), - helpers.generate_hdkey_BTCLegacy(btc_legacy_pub), - helpers.generate_hdkey_BTCSegWit(btc_segwit_pub), - helpers.generate_hdkey_BTCNativeSegWit(btc_native_segwit_pub), - helpers.generate_hdkey_BTCTaproot(btc_taproot_pub), - helpers.generate_hdkey_SOLStandard(sol_pubs.public_keys[0]), - helpers.generate_hdkey_TRONStandard(tron_pub), - helpers.generate_hdkey_SOLLedgerLive(sol_pubs.public_keys[1]), - ], + btc_legacy_pub.root_fingerprint, + keys, device=name, device_id=device.get_device_id(), device_version=utils.ONEKEY_VERSION, diff --git a/core/src/trezor/lvglui/scrs/homescreen.py b/core/src/trezor/lvglui/scrs/homescreen.py index 270bd0b71..f7af80f59 100644 --- a/core/src/trezor/lvglui/scrs/homescreen.py +++ b/core/src/trezor/lvglui/scrs/homescreen.py @@ -1024,27 +1024,17 @@ def init_ui(self): self.show_page(0) def init_items(self): - if utils.BITCOIN_ONLY: - items = [ - ("connect", "app-connect", i18n_keys.APP__CONNECT_WALLET), - ("scan", "app-scan", i18n_keys.APP__SCAN), - ("my_address", "app-address", i18n_keys.APP__ADDRESS), - ("settings", "app-settings", i18n_keys.APP__SETTINGS), - ("backup", "app-backup", i18n_keys.APP__BACK_UP), - ("nft", "app-nft", i18n_keys.APP__NFT_GALLERY), - ("guide", "app-tips", i18n_keys.APP__TIPS), - ] - else: - items = [ - ("connect", "app-connect", i18n_keys.APP__CONNECT_WALLET), - ("scan", "app-scan", i18n_keys.APP__SCAN), - ("my_address", "app-address", i18n_keys.APP__ADDRESS), - ("settings", "app-settings", i18n_keys.APP__SETTINGS), - ("passkey", "app-keys", i18n_keys.FIDO_FIDO_KEYS_LABEL), - ("backup", "app-backup", i18n_keys.APP__BACK_UP), - ("nft", "app-nft", i18n_keys.APP__NFT_GALLERY), - ("guide", "app-tips", i18n_keys.APP__TIPS), - ] + items = [ + ("connect", "app-connect", i18n_keys.APP__CONNECT_WALLET), + ("scan", "app-scan", i18n_keys.APP__SCAN), + ("my_address", "app-address", i18n_keys.APP__ADDRESS), + ("settings", "app-settings", i18n_keys.APP__SETTINGS), + ("backup", "app-backup", i18n_keys.APP__BACK_UP), + ("guide", "app-tips", i18n_keys.APP__TIPS), + ] + if not utils.BITCOIN_ONLY: + items.insert(4, ("passkey", "app-keys", i18n_keys.FIDO_FIDO_KEYS_LABEL)) + items.insert(6, ("nft", "app-nft", i18n_keys.APP__NFT_GALLERY)) items_per_page = 4 cols = 2 @@ -2491,14 +2481,26 @@ def __init__(self, prev_scr=None): # ) self.add_event_cb(self.on_event, lv.EVENT.CLICKED, None) + if ( + storage_device.is_passphrase_enabled() + or passphrase.is_passphrase_pin_enabled() + ): + retrieval_encoder() + + def show_connect_wallet_guide(self, connect_type): + if not utils.BITCOIN_ONLY: + ConnectWalletGuide(connect_type, self) + else: + ConnectWalletGuide.show_wallet_tutorial("onekey", connect_type) + def on_event(self, event_obj): code = event_obj.code target = event_obj.get_target() if code == lv.EVENT.CLICKED: if target == self.by_ble: - ConnectWalletGuide("ble", self) + self.show_connect_wallet_guide("ble") elif target == self.by_usb: - ConnectWalletGuide("usb", self) + self.show_connect_wallet_guide("usb") # elif target == self.by_qrcode: # gc.collect() # WalletList(self) @@ -2507,7 +2509,10 @@ def on_event(self, event_obj): def on_click_ext(self, target): if target == self.rti_btn: - QRWalletTips(self) + if not utils.BITCOIN_ONLY: + QRWalletTips(self) + else: + WalletList.connect_onekey(target, bitcoin_only=True) def _load_scr(self, scr: "Screen", back: bool = False) -> None: lv.scr_load(scr) @@ -2587,108 +2592,127 @@ def __init__(self, c_type, prev_scr=None): self.add_event_cb(self.on_click, lv.EVENT.CLICKED, None) - def on_click(self, event_obj): - code = event_obj.code - target = event_obj.get_target() - if code == lv.EVENT.CLICKED: - if target not in [self.onekey, self.mm, self.okx]: - return - from trezor.lvglui.scrs.template import ConnectWalletTutorial - - if target == self.onekey: - title = _(i18n_keys.ITEM__ONEKEY_WALLET) - subtitle = ( - _(i18n_keys.CONTENT__IOS_ANDROID) - if self.connect_type == "ble" - else _(i18n_keys.CONTENT__DESKTOP_BROWSER_EXTENSION) - ) - steps = [ + @staticmethod + def _get_wallet_tutorial_config(wallet_type, connect_type): + is_ble = connect_type == "ble" + configs = { + "onekey": { + "title": i18n_keys.ITEM__ONEKEY_WALLET, + "subtitle": i18n_keys.CONTENT__IOS_ANDROID + if is_ble + else i18n_keys.CONTENT__DESKTOP_BROWSER_EXTENSION, + "steps": [ ( - _(i18n_keys.FORM__DOWNLOAD_ONEKEY_APP), - _(i18n_keys.FORM__DOWNLOAD_ONEKEY_APP_MOBILE) - if self.connect_type == "ble" - else _(i18n_keys.FORM__DOWNLOAD_ONEKEY_APP_DESKTOP), + i18n_keys.FORM__DOWNLOAD_ONEKEY_APP, + i18n_keys.FORM__DOWNLOAD_ONEKEY_APP_MOBILE + if is_ble + else i18n_keys.FORM__DOWNLOAD_ONEKEY_APP_DESKTOP, ), ( - _(i18n_keys.FORM__CONNECT_VIA_BLUETOOTH) - if self.connect_type == "ble" - else _(i18n_keys.FORM__CONNECT_YOUR_DEVICE), - _(i18n_keys.FORM__CONNECT_VIA_BLUETOOTH_DESC) - if self.connect_type == "ble" - else _(i18n_keys.FORM__CONNECT_YOUR_DEVICE_DESC), + i18n_keys.FORM__CONNECT_VIA_BLUETOOTH + if is_ble + else i18n_keys.FORM__CONNECT_YOUR_DEVICE, + i18n_keys.FORM__CONNECT_VIA_BLUETOOTH_DESC + if is_ble + else i18n_keys.FORM__CONNECT_YOUR_DEVICE_DESC, ), ( - _(i18n_keys.FORM__PAIR_DEVICES) - if self.connect_type == "ble" - else _(i18n_keys.FORM__START_THE_CONNECTION), - _(i18n_keys.FORM__PAIR_DEVICES_DESC) - if self.connect_type == "ble" - else _(i18n_keys.FORM__START_THE_CONNECTION_DESC), + i18n_keys.FORM__PAIR_DEVICES + if is_ble + else i18n_keys.FORM__START_THE_CONNECTION, + i18n_keys.FORM__PAIR_DEVICES_DESC + if is_ble + else i18n_keys.FORM__START_THE_CONNECTION_DESC, ), - ] - logo = "A:/res/ok-logo-96.png" - url = ( - "https://help.onekey.so/articles/11461081" - if self.connect_type == "ble" - else "https://help.onekey.so/articles/11461081#h_01HMWVPP85HWYTZGPQQTB300VX" - ) - elif target == self.mm: - title = _(i18n_keys.ITEM__METAMASK_WALLET) - subtitle = _(i18n_keys.CONTENT__BROWSER_EXTENSION) - steps = [ + ], + "logo": "A:/res/ok-logo-96.png", + "url": "https://help.onekey.so/articles/11461081" + if is_ble + else "https://help.onekey.so/articles/11461081#h_01HMWVPP85HWYTZGPQQTB300VX", + }, + "metamask": { + "title": i18n_keys.ITEM__METAMASK_WALLET, + "subtitle": i18n_keys.CONTENT__BROWSER_EXTENSION, + "steps": [ ( - _(i18n_keys.FORM__ACCESS_WALLET), - _(i18n_keys.FORM__OPEN_METAMASK_IN_YOUR_BROWSER), + i18n_keys.FORM__ACCESS_WALLET, + i18n_keys.FORM__OPEN_METAMASK_IN_YOUR_BROWSER, ), ( - _(i18n_keys.FORM__CONNECT_HARDWARE_WALLET), - _(i18n_keys.FORM__CONNECT_HARDWARE_WALLET_DESC), + i18n_keys.FORM__CONNECT_HARDWARE_WALLET, + i18n_keys.FORM__CONNECT_HARDWARE_WALLET_DESC, ), ( - _(i18n_keys.FORM__UNLOCK_ACCOUNT), - _(i18n_keys.FORM__UNLOCK_ACCOUNT_DESC), + i18n_keys.FORM__UNLOCK_ACCOUNT, + i18n_keys.FORM__UNLOCK_ACCOUNT_DESC, ), - ] - logo = "A:/res/mm-logo-96.png" - url = "https://help.onekey.so/articles/11461106" - else: - title = _(i18n_keys.ITEM__OKX_WALLET) - subtitle = ( - _(i18n_keys.CONTENT__IOS_ANDROID) - if self.connect_type == "ble" - else _(i18n_keys.CONTENT__BROWSER_EXTENSION) - ) - steps = [ + ], + "logo": "A:/res/mm-logo-96.png", + "url": "https://help.onekey.so/articles/11461106", + }, + "okx": { + "title": i18n_keys.ITEM__OKX_WALLET, + "subtitle": i18n_keys.CONTENT__IOS_ANDROID + if is_ble + else i18n_keys.CONTENT__BROWSER_EXTENSION, + "steps": [ ( - _(i18n_keys.FORM__ACCESS_WALLET), - _(i18n_keys.FORM__ACCESS_WALLET_DESC) - if self.connect_type == "ble" - else _(i18n_keys.FORM__OPEN_THE_OKX_WALLET_EXTENSION), + i18n_keys.FORM__ACCESS_WALLET, + i18n_keys.FORM__ACCESS_WALLET_DESC + if is_ble + else i18n_keys.FORM__OPEN_THE_OKX_WALLET_EXTENSION, ), ( - _(i18n_keys.FORM__CONNECT_VIA_BLUETOOTH) - if self.connect_type == "ble" - else _(i18n_keys.FORM__INSTALL_ONEKEY_BRIDGE), - _(i18n_keys.FORM__CONNECT_VIA_BLUETOOTH_DESC) - if self.connect_type == "ble" - else _(i18n_keys.FORM__INSTALL_ONEKEY_BRIDGE_DESC), + i18n_keys.FORM__CONNECT_VIA_BLUETOOTH + if is_ble + else i18n_keys.FORM__INSTALL_ONEKEY_BRIDGE, + i18n_keys.FORM__CONNECT_VIA_BLUETOOTH_DESC + if is_ble + else i18n_keys.FORM__INSTALL_ONEKEY_BRIDGE_DESC, ), ( - _(i18n_keys.FORM__IMPORT_WALLET_ACCOUNTS), - _(i18n_keys.FORM__IMPORT_WALLET_ACCOUNTS_DESC) - if self.connect_type == "ble" - else _( - i18n_keys.FORM__OKX_EXTENSION_IMPORT_WALLET_ACCOUNTS_DESC - ), + i18n_keys.FORM__IMPORT_WALLET_ACCOUNTS, + i18n_keys.FORM__IMPORT_WALLET_ACCOUNTS_DESC + if is_ble + else i18n_keys.FORM__OKX_EXTENSION_IMPORT_WALLET_ACCOUNTS_DESC, ), - ] - logo = "A:/res/okx-logo-96.png" - url = ( - " https://help.onekey.so/articles/11461103" - if self.connect_type == "ble" - else "https://help.onekey.so/articles/11461103" - ) - ConnectWalletTutorial(title, subtitle, steps, url, logo) + ], + "logo": "A:/res/okx-logo-96.png", + "url": "https://help.onekey.so/articles/11461103", + }, + } + assert wallet_type in configs, f"Invalid wallet type: {wallet_type}" + return configs[wallet_type] + + @staticmethod + def show_wallet_tutorial(wallet_type, connect_type): + config = ConnectWalletGuide._get_wallet_tutorial_config( + wallet_type, connect_type + ) + from trezor.lvglui.scrs.template import ConnectWalletTutorial + + ConnectWalletTutorial( + _(config["title"]), + _(config["subtitle"]), + [(_(step[0]), _(step[1])) for step in config["steps"]], + config["url"], + config["logo"], + ) + + def on_click(self, event_obj): + code = event_obj.code + target = event_obj.get_target() + if code == lv.EVENT.CLICKED: + if target == self.onekey: + wallet_type = "onekey" + elif target == self.mm: + wallet_type = "metamask" + elif target == self.okx: + wallet_type = "okx" + else: + raise ValueError(f"Invalid wallet type: {target}") + + ConnectWalletGuide.show_wallet_tutorial(wallet_type, self.connect_type) def _load_scr(self, scr: "Screen", back: bool = False) -> None: lv.scr_load(scr) @@ -2798,7 +2822,8 @@ def on_click(self, event_obj): def _load_scr(self, scr: "Screen", back: bool = False) -> None: lv.scr_load(scr) - def connect_onekey(self, target): + @staticmethod + def connect_onekey(target, bitcoin_only: bool = False): from trezor.qr import get_encoder if passphrase.is_enabled(): @@ -2819,8 +2844,10 @@ def connect_onekey(self, target): _(i18n_keys.ITEM__ONEKEY_WALLET) ), _(i18n_keys.CONTENT__OPEN_ONEKEY_SCAN_THE_QRCODE), - _(i18n_keys.CONTENT__BTC_SOL_ETH_N_EVM_NETWORKS), - "A:/res/support-chains-ok-qr.png", + _(i18n_keys.CONTENT__BTC_SOL_ETH_N_EVM_NETWORKS) + if not bitcoin_only + else None, + "A:/res/support-chains-ok-qr.png" if not bitcoin_only else None, None, encoder=encoder, ) @@ -3030,11 +3057,11 @@ def eventhandler(self, event_obj): if code == lv.EVENT.CLICKED: if utils.lcd_resume(): return - if target == self.nav_back.nav_btn: - self.destroy() - elif hasattr(self, "btn_yes") and target == self.btn_yes: - DynQr(self.encoder) - self.destroy(100) + if target == self.nav_back.nav_btn: + self.destroy() + elif hasattr(self, "btn_yes") and target == self.btn_yes: + DynQr(self.encoder) + self.destroy(100) def on_nav_back(self, event_obj): code = event_obj.code @@ -7507,6 +7534,8 @@ def __init__(self, prev_scr=None): ) self.passphrase = ListItemBtn(self.container, _(i18n_keys.ITEM__PASSPHRASE)) self.turbo_mode = ListItemBtn(self.container, _(i18n_keys.TITLE__TURBO_MODE)) + if utils.BITCOIN_ONLY: + self.turbo_mode.add_flag(lv.obj.FLAG.HIDDEN) self.trezor_mode = ListItemBtnWithSwitch( self.container, _(i18n_keys.ITEM__COMPATIBLE_WITH_TREZOR) ) From dd6f1601e2ea3e44c426ebfe2d8586042a520c6e Mon Sep 17 00:00:00 2001 From: Ritchie Date: Fri, 12 Dec 2025 11:51:25 +0800 Subject: [PATCH 2/4] fix(core): adaptation related to Polkadot updates --- common/protob/messages-polkadot.proto | 1 + core/src/apps/polkadot/__init__.py | 10 +- core/src/apps/polkadot/helper.py | 41 +++++- core/src/apps/polkadot/sign_tx.py | 2 +- core/src/apps/polkadot/transaction.py | 122 ++++++++++++++---- .../trezor/lvglui/res/chain-bifrost-ksm.png | Bin 0 -> 5421 bytes core/src/trezor/lvglui/res/chain-bifrost.png | Bin 0 -> 3213 bytes core/src/trezor/lvglui/res/chain-dot.png | Bin 1367 -> 11728 bytes .../src/trezor/lvglui/res/chain-hydration.png | Bin 0 -> 10702 bytes core/src/trezor/lvglui/res/chain-kusama.png | Bin 1006 -> 4635 bytes core/src/trezor/messages.py | 2 + python/src/trezorlib/messages.py | 3 + tools/i18n.py | 2 +- 13 files changed, 149 insertions(+), 34 deletions(-) create mode 100644 core/src/trezor/lvglui/res/chain-bifrost-ksm.png create mode 100644 core/src/trezor/lvglui/res/chain-bifrost.png create mode 100644 core/src/trezor/lvglui/res/chain-hydration.png mode change 100755 => 100644 core/src/trezor/lvglui/res/chain-kusama.png diff --git a/common/protob/messages-polkadot.proto b/common/protob/messages-polkadot.proto index ba3ff6bba..41252b676 100644 --- a/common/protob/messages-polkadot.proto +++ b/common/protob/messages-polkadot.proto @@ -36,6 +36,7 @@ message PolkadotSignTx { repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node required bytes raw_tx = 2; // serialized raw transaction required string network = 3; // Network name + optional uint32 prefix = 4; // SS58 address-type } /** diff --git a/core/src/apps/polkadot/__init__.py b/core/src/apps/polkadot/__init__.py index 06c6d0afb..39b8abb74 100644 --- a/core/src/apps/polkadot/__init__.py +++ b/core/src/apps/polkadot/__init__.py @@ -3,15 +3,21 @@ CURVE = "ed25519-ledger" SLIP44_ID = 354 PATTERN = PATTERN_BIP44 -PRIMARY_COLOR = 0xE6007A -PRIMARY_COLOR_KSM = 0xFFFFFF +PRIMARY_COLOR = 0x00FF33 +PRIMARY_COLOR_KSM = 0x00FF33 PRIMARY_COLOR_WESTEND = 0x969696 PRIMARY_COLOR_ASTAR = 0x04E2FE PRIMARY_COLOR_JOY = 0x4038FF PRIMARY_COLOR_MAMTA = 0x26C8BF +PRIMARY_COLOR_HYDRATION = 0x00FF33 +PRIMARY_COLOR_BIFROST = 0x00FF33 +PRIMARY_COLOR_BIFROST_KSMC = 0x00FF33 ICON = "A:/res/chain-dot.png" ICON_KSM = "A:/res/chain-kusama.png" ICON_WESTEND = "A:/res/chain-westend.png" ICON_ASTAR = "A:/res/chain-astar.png" ICON_JOY = "A:/res/chain-joystream.png" ICON_MANTA = "A:/res/chain-manta.png" +ICON_HYDRATION = "A:/res/chain-hydration.png" +ICON_BIFROST = "A:/res/chain-bifrost.png" +ICON_BIFROST_KSM = "A:/res/chain-bifrost-ksm.png" diff --git a/core/src/apps/polkadot/helper.py b/core/src/apps/polkadot/helper.py index 727b5ebf6..e56f080a8 100644 --- a/core/src/apps/polkadot/helper.py +++ b/core/src/apps/polkadot/helper.py @@ -7,12 +7,18 @@ from . import ( ICON, ICON_ASTAR, + ICON_BIFROST, + ICON_BIFROST_KSM, + ICON_HYDRATION, ICON_JOY, ICON_KSM, ICON_MANTA, ICON_WESTEND, PRIMARY_COLOR, PRIMARY_COLOR_ASTAR, + PRIMARY_COLOR_BIFROST, + PRIMARY_COLOR_BIFROST_KSMC, + PRIMARY_COLOR_HYDRATION, PRIMARY_COLOR_JOY, PRIMARY_COLOR_KSM, PRIMARY_COLOR_MAMTA, @@ -30,6 +36,9 @@ ["astar", 5], ["joystream", 126], ["manta", 77], + ["hydration", 0], + ["bifrost", 0], + ["bifrost-ksm", 0], ] COIN_AMOUNT_DECIMAL_PLACES = 10 @@ -41,6 +50,8 @@ ASTAR_COIN_TICKER = "ASTR" JOY_COIN_TICKER = "JOY" MANTA_COIN_TICKER = "MANTA" +HYDRATION_COIN_TICKER = "HDX" +BIFROST_COIN_TICKER = "BNC" def ss58_encode(address: bytes, ss58_format: int = 42) -> str: @@ -118,6 +129,30 @@ def retrieval_chain_res(ctx: Context, network: str) -> tuple[str, str, int]: chain_name = "Manta" symbol = MANTA_COIN_TICKER decimal = COIN_AMOUNT_DECIMAL_PLACES_18 + elif network == "hydration": + ctx.primary_color, ctx.icon_path = ( + lv.color_hex(PRIMARY_COLOR_HYDRATION), + ICON_HYDRATION, + ) + chain_name = "Hydration" + symbol = HYDRATION_COIN_TICKER + decimal = COIN_AMOUNT_DECIMAL_PLACES_12 + elif network == "bifrost": + ctx.primary_color, ctx.icon_path = ( + lv.color_hex(PRIMARY_COLOR_BIFROST), + ICON_BIFROST, + ) + chain_name = "Bifrost" + symbol = BIFROST_COIN_TICKER + decimal = COIN_AMOUNT_DECIMAL_PLACES_12 + elif network == "bifrost-ksm": + ctx.primary_color, ctx.icon_path = ( + lv.color_hex(PRIMARY_COLOR_BIFROST_KSMC), + ICON_BIFROST_KSM, + ) + chain_name = "Bifrost-KSM" + symbol = BIFROST_COIN_TICKER + decimal = COIN_AMOUNT_DECIMAL_PLACES_12 else: ctx.primary_color, ctx.icon_path = lv.color_hex(PRIMARY_COLOR), ICON chain_name = "UNKN Chain" @@ -126,10 +161,10 @@ def retrieval_chain_res(ctx: Context, network: str) -> tuple[str, str, int]: return chain_name, symbol, decimal -def get_address_type(network: str) -> int: - address_type = 42 +def get_address_type(network: str, preset_prefix: int | None = None) -> int: + address_type = preset_prefix if preset_prefix is not None else 42 for element in POLKADOT_ADDRESS_TYPES: if network == element[0]: address_type = element[1] - + break return address_type diff --git a/core/src/apps/polkadot/sign_tx.py b/core/src/apps/polkadot/sign_tx.py index 872f04491..ed34e940b 100644 --- a/core/src/apps/polkadot/sign_tx.py +++ b/core/src/apps/polkadot/sign_tx.py @@ -19,7 +19,7 @@ async def sign_tx( public_key = node.public_key()[1:] else: public_key = ed25519.publickey(node.private_key()) - address_type = helper.get_address_type(msg.network) + address_type = helper.get_address_type(msg.network, msg.prefix) address = helper.ss58_encode(public_key, address_type) chain_name, symbol, decimal = helper.retrieval_chain_res(ctx, msg.network) tx = transaction.Transaction.deserialize(msg.raw_tx, msg.network) diff --git a/core/src/apps/polkadot/transaction.py b/core/src/apps/polkadot/transaction.py index 4e887b7c4..6d54e953d 100644 --- a/core/src/apps/polkadot/transaction.py +++ b/core/src/apps/polkadot/transaction.py @@ -23,21 +23,26 @@ async def layout( return None @staticmethod - def _readAccountIdLookupOfT_V15(rawtx: codec.base.ScaleBytes, address_type) -> str: - value = rawtx.get_next_bytes(1)[0] - if value == 0: # Id + def _readAccountIdLookupOfT_V15( + rawtx: codec.base.ScaleBytes, address_type, skip_type_lookup: bool = False + ) -> str: + if not skip_type_lookup: + value = rawtx.get_next_bytes(1)[0] + else: + value = 0 + if value == 0: accountid = helper.ss58_encode(rawtx.get_next_bytes(32), address_type) - elif value == 1: # Index + elif value == 1: obj = codec.types.Compact(rawtx) accountid = str(obj.decode(check_remaining=False)) - elif value == 2: # Raw + elif value == 2: obj = codec.types.Compact(rawtx) value = obj.decode(check_remaining=False) clen = int(0 if value is None else value) accountid = hexlify(rawtx.get_next_bytes(clen)).decode() - elif value == 3: # Address32 + elif value == 3: accountid = hexlify(rawtx.get_next_bytes(32)).decode() - elif value == 4: # Address20 + elif value == 4: accountid = hexlify(rawtx.get_next_bytes(20)).decode() else: raise Exception("Unexpected value") @@ -49,23 +54,23 @@ def deserialize_polkadot( rawtx: codec.base.ScaleBytes, callPrivIdx: int ) -> "Transaction": tx = TransactionUnknown(rawtx) - if callPrivIdx in (1287, 1280): + if callPrivIdx in (1287, 1280, 2560): desc = Transaction._readAccountIdLookupOfT_V15(rawtx, 0) obj = codec.types.Compact(rawtx) balance = obj.decode(check_remaining=False) tx = BalancesTransfer(desc, balance) - elif callPrivIdx == 1282: + elif callPrivIdx in (1282, 2562): source = Transaction._readAccountIdLookupOfT_V15(rawtx, 0) dest = Transaction._readAccountIdLookupOfT_V15(rawtx, 0) obj = codec.types.Compact(rawtx) balance = obj.decode(check_remaining=False) tx = BalancesForceTransfer(source, dest, balance) - elif callPrivIdx == 1283: + elif callPrivIdx in (1283, 2563): desc = Transaction._readAccountIdLookupOfT_V15(rawtx, 0) obj = codec.types.Compact(rawtx) balance = obj.decode(check_remaining=False) tx = BalancesTransferKeepAlive(desc, balance) - elif callPrivIdx == 1284: + elif callPrivIdx in (1284, 2564): desc = Transaction._readAccountIdLookupOfT_V15(rawtx, 0) keep_alive = rawtx.get_next_bytes(1)[0] tx = BalancesTransferAll(desc, keep_alive) @@ -77,23 +82,23 @@ def deserialize_kusama( rawtx: codec.base.ScaleBytes, callPrivIdx: int ) -> "Transaction": tx = TransactionUnknown(rawtx) - if callPrivIdx in (1031, 1024): + if callPrivIdx in (1031, 1024, 2560): desc = Transaction._readAccountIdLookupOfT_V15(rawtx, 2) obj = codec.types.Compact(rawtx) balance = obj.decode(check_remaining=False) tx = BalancesTransfer(desc, balance) - elif callPrivIdx == 1026: + elif callPrivIdx in (1026, 2562): source = Transaction._readAccountIdLookupOfT_V15(rawtx, 2) dest = Transaction._readAccountIdLookupOfT_V15(rawtx, 2) obj = codec.types.Compact(rawtx) balance = obj.decode(check_remaining=False) tx = BalancesForceTransfer(source, dest, balance) - elif callPrivIdx == 1027: + elif callPrivIdx in (1027, 2563): desc = Transaction._readAccountIdLookupOfT_V15(rawtx, 2) obj = codec.types.Compact(rawtx) balance = obj.decode(check_remaining=False) tx = BalancesTransferKeepAlive(desc, balance) - elif callPrivIdx == 1028: + elif callPrivIdx in (1028, 2564): desc = Transaction._readAccountIdLookupOfT_V15(rawtx, 2) keep_alive = rawtx.get_next_bytes(1)[0] tx = BalancesTransferAll(desc, keep_alive) @@ -162,17 +167,33 @@ def deserialize_joy( ) -> "Transaction": tx = TransactionUnknown(rawtx) if callPrivIdx == 1280: - dest = helper.ss58_encode(rawtx.get_next_bytes(32), 126) + dest = Transaction._readAccountIdLookupOfT_V15( + rawtx, 126, skip_type_lookup=True + ) obj = codec.types.Compact(rawtx) balance = obj.decode(check_remaining=False) tx = BalancesTransfer(dest, balance) + elif callPrivIdx == 1282: + source = Transaction._readAccountIdLookupOfT_V15( + rawtx, 126, skip_type_lookup=True + ) + dest = Transaction._readAccountIdLookupOfT_V15( + rawtx, 126, skip_type_lookup=True + ) + obj = codec.types.Compact(rawtx) + balance = obj.decode(check_remaining=False) + tx = BalancesForceTransfer(source, dest, balance) elif callPrivIdx == 1283: - dest = helper.ss58_encode(rawtx.get_next_bytes(32), 126) + dest = Transaction._readAccountIdLookupOfT_V15( + rawtx, 126, skip_type_lookup=True + ) obj = codec.types.Compact(rawtx) balance = obj.decode(check_remaining=False) tx = BalancesTransferKeepAlive(dest, balance) elif callPrivIdx == 1284: - dest = helper.ss58_encode(rawtx.get_next_bytes(32), 126) + dest = Transaction._readAccountIdLookupOfT_V15( + rawtx, 126, skip_type_lookup=True + ) keep_alive = rawtx.get_next_bytes(1)[0] tx = BalancesTransferAll(dest, keep_alive) @@ -180,29 +201,67 @@ def deserialize_joy( @staticmethod def deserialize_manta( - rawtx: codec.base.ScaleBytes, callPrivIdx: int + rawtx: codec.base.ScaleBytes, callPrivIdx: int, address_type: int ) -> "Transaction": tx = TransactionUnknown(rawtx) if callPrivIdx == 2560: - desc = Transaction._readAccountIdLookupOfT_V15(rawtx, 77) + desc = Transaction._readAccountIdLookupOfT_V15(rawtx, address_type) obj = codec.types.Compact(rawtx) balance = obj.decode(check_remaining=False) tx = BalancesTransfer(desc, balance) + elif callPrivIdx == 2562: + source = Transaction._readAccountIdLookupOfT_V15(rawtx, address_type) + dest = Transaction._readAccountIdLookupOfT_V15(rawtx, address_type) + obj = codec.types.Compact(rawtx) + balance = obj.decode(check_remaining=False) + tx = BalancesForceTransfer(source, dest, balance) elif callPrivIdx == 2563: - desc = Transaction._readAccountIdLookupOfT_V15(rawtx, 77) + desc = Transaction._readAccountIdLookupOfT_V15(rawtx, address_type) obj = codec.types.Compact(rawtx) balance = obj.decode(check_remaining=False) tx = BalancesTransferKeepAlive(desc, balance) elif callPrivIdx == 2564: - desc = Transaction._readAccountIdLookupOfT_V15(rawtx, 77) + desc = Transaction._readAccountIdLookupOfT_V15(rawtx, address_type) keep_alive = rawtx.get_next_bytes(1)[0] tx = BalancesTransferAll(desc, keep_alive) - elif callPrivIdx == 2562: - source = Transaction._readAccountIdLookupOfT_V15(rawtx, 77) - dest = Transaction._readAccountIdLookupOfT_V15(rawtx, 77) + + return tx + + @staticmethod + def deserialize_hydration( + rawtx: codec.base.ScaleBytes, callPrivIdx: int + ) -> "Transaction": + tx = TransactionUnknown(rawtx) + if callPrivIdx == 1792: + desc = Transaction._readAccountIdLookupOfT_V15( + rawtx, 0, skip_type_lookup=True + ) + obj = codec.types.Compact(rawtx) + balance = obj.decode(check_remaining=False) + tx = BalancesTransfer(desc, balance) + elif callPrivIdx == 1794: + source = Transaction._readAccountIdLookupOfT_V15( + rawtx, 0, skip_type_lookup=True + ) + dest = Transaction._readAccountIdLookupOfT_V15( + rawtx, 0, skip_type_lookup=True + ) obj = codec.types.Compact(rawtx) balance = obj.decode(check_remaining=False) tx = BalancesForceTransfer(source, dest, balance) + elif callPrivIdx == 1795: + desc = Transaction._readAccountIdLookupOfT_V15( + rawtx, 0, skip_type_lookup=True + ) + obj = codec.types.Compact(rawtx) + balance = obj.decode(check_remaining=False) + tx = BalancesTransferKeepAlive(desc, balance) + elif callPrivIdx == 1796: + desc = Transaction._readAccountIdLookupOfT_V15( + rawtx, 0, skip_type_lookup=True + ) + keep_alive = rawtx.get_next_bytes(1)[0] + tx = BalancesTransferAll(desc, keep_alive) return tx @@ -224,8 +283,14 @@ def deserialize(raw_tx: bytes, network: str) -> "Transaction": tx = Transaction.deserialize_astar(rawtx, callPrivIdx) elif network == "joystream": tx = Transaction.deserialize_joy(rawtx, callPrivIdx) - elif network == "manta": - tx = Transaction.deserialize_manta(rawtx, callPrivIdx) + elif network in ("manta", "bifrost", "bifrost-ksm"): + if network == "manta": + address_type = 77 + else: + address_type = 0 + tx = Transaction.deserialize_manta(rawtx, callPrivIdx, address_type) + elif network == "hydration": + tx = Transaction.deserialize_hydration(rawtx, callPrivIdx) else: tx = TransactionUnknown(rawtx) @@ -248,6 +313,9 @@ def deserialize(raw_tx: bytes, network: str) -> "Transaction": tip = obj.decode(check_remaining=False) tx.tip = tip if tip is not None else 0 + # optional: assetId(asset location if assetId is not equal to 0) + # optional: mode + return tx diff --git a/core/src/trezor/lvglui/res/chain-bifrost-ksm.png b/core/src/trezor/lvglui/res/chain-bifrost-ksm.png new file mode 100644 index 0000000000000000000000000000000000000000..c9be7922959ba4f7f50e03b216664c1700b88b5b GIT binary patch literal 5421 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 literal 0 HcmV?d00001 diff --git a/core/src/trezor/lvglui/res/chain-dot.png b/core/src/trezor/lvglui/res/chain-dot.png index 4cbb7832ea01301bc85f4d11566c4338f0a49ca3..50ece63f3967af46467838184a7d815fc6de6150 100644 GIT binary patch literal 11728 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 R8bSa7002ovPDHLkV1j)_sL}uc diff --git a/core/src/trezor/lvglui/res/chain-hydration.png b/core/src/trezor/lvglui/res/chain-hydration.png new file mode 100644 index 0000000000000000000000000000000000000000..dbf7e5afac5b4cbad635b3b86fe1b39203234dd6 GIT binary patch literal 10702 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 A!~g&Q literal 0 HcmV?d00001 diff --git a/core/src/trezor/lvglui/res/chain-kusama.png b/core/src/trezor/lvglui/res/chain-kusama.png old mode 100755 new mode 100644 index af0fa509d18cacad566cc5e78e1d95bec7a7c20d..cd867f3c8c1eb4db854f5cb0bcac7df2ee5903c6 GIT binary patch literal 4635 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 RJT(9S002ovPDHLkV1irKpTBYFIcc3K0bcJ zgbBY73#tLF7bywy3ud^VAW)$2e!s)|3G)rk$M**;*pQ#{(7=d+ftlOW#WAEJZtty| zWwRZ4*b>ej$`V#zd1J4h=k4$H2fuB3q{6bHE%MXdAMfNaKko3D}m^3e_y=ly!q#aPy1>0S#SL}=;dCVth?EM?oq)r z0Ze-Zxt5i^xX7obQuXx+&w>9EW-lVe);X%Qo@c32uMscUV|w;2uYJaPR+-ta=GS&M zNBq=YU?0-_Z868&wGG?1{d!_F^N{hbo!76P`1;!I)PV%Po%#0|8qppRko;KUANEk_3wB6PF~A(L1tGs38{aYYQ89O z$DfXq+A4Bw-hCFK4I6g9-H>mjq~PXUzN_Z2;A9D%_S51H2a57Pu?iIIt0{8(&AM)D zt)0`GE#{uJ2U!g{%JM(W5}MAT!4zxfDR*D%)VkMtZy*0C?&sg*bgpj8jiZxgOg8`D zS<%)eGQG5a_h$WxQ=2yR=J#hfFmgN#*|b3Sq5~s?e%r^l55F&cz3btQB4;t#XchT* z^ZosQ|F_m}TkJ31$HH**$aax`4;|cfm<46)#2?Q-?Q`L131gMR1>=uX?meEfZyCGl zWWL_2kas`7ENnTp!&%<;)o$VGe&@cZ9II&kf9d1eopzT6Cs+3WcRKg^hvBxI)vGM! z`!W`pL^vLBxXf`%uJ^0@Hg6#|tz%#NLPNL4o)mE~Syz_t|NHMg%h;Jr9S63adN@_H zVabcREfaZcZy$&_a^~jW=`51KSa$FK^!{g-pYQYS)vN61usld@0p(gxS3j3^P6 None: pass diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py index e422267d6..1d2478828 100644 --- a/python/src/trezorlib/messages.py +++ b/python/src/trezorlib/messages.py @@ -9913,6 +9913,7 @@ class PolkadotSignTx(protobuf.MessageType): 1: protobuf.Field("address_n", "uint32", repeated=True, required=False), 2: protobuf.Field("raw_tx", "bytes", repeated=False, required=True), 3: protobuf.Field("network", "string", repeated=False, required=True), + 4: protobuf.Field("prefix", "uint32", repeated=False, required=False), } def __init__( @@ -9921,10 +9922,12 @@ def __init__( raw_tx: "bytes", network: "str", address_n: Optional[Sequence["int"]] = None, + prefix: Optional["int"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.raw_tx = raw_tx self.network = network + self.prefix = prefix class PolkadotSignedTx(protobuf.MessageType): diff --git a/tools/i18n.py b/tools/i18n.py index e99481ec3..8ae82e98a 100644 --- a/tools/i18n.py +++ b/tools/i18n.py @@ -98,7 +98,7 @@ def main(): while True: response = client.keys( LOKALISE_PROJECT_ID, - {"include_translations": 1, "limit": PAGE_SIZE, "page": page}, + {"include_translations": 1, "limit": PAGE_SIZE, "page": page, "replace_breaks": 1}, ) items = response.items if not items: From 7d74accb7d2202d87ed3a0c57040e7a17ceaa2d7 Mon Sep 17 00:00:00 2001 From: Ritchie Date: Fri, 12 Dec 2025 11:53:30 +0800 Subject: [PATCH 3/4] feat(core): add independent switch for USB and BLE --- .github/workflows/build-pro.yml | 8 +- .../chains/ethereum/typed_data_transacion.py | 8 +- .../hardware_requests/get_multi_accounts.py | 4 + core/src/apps/ur_registry/helpers.py | 3 + core/src/storage/device.py | 58 ++++++ .../trezor/lvglui/scrs/components/button.py | 38 ++++ core/src/trezor/lvglui/scrs/homescreen.py | 165 ++++++++++++------ core/src/trezor/uart.py | 48 ++--- core/src/trezor/utils.py | 92 +++++++--- 9 files changed, 317 insertions(+), 107 deletions(-) diff --git a/.github/workflows/build-pro.yml b/.github/workflows/build-pro.yml index 49030151f..8637f0f7c 100644 --- a/.github/workflows/build-pro.yml +++ b/.github/workflows/build-pro.yml @@ -27,8 +27,8 @@ jobs: bitcoin_only: 1 secret_key_1_name: "SECRET_KEY_1" secret_key_2_name: "SECRET_KEY_2" - output_dir: prod-bc-only - artifact_suffix: "prod-bc-only" + output_dir: prod-btc-only + artifact_suffix: "prod-btc-only" - production: 0 bitcoin_only: 0 secret_key_1_name: "SECRET_QA_KEY_1" @@ -39,8 +39,8 @@ jobs: bitcoin_only: 1 secret_key_1_name: "SECRET_QA_KEY_1" secret_key_2_name: "SECRET_QA_KEY_2" - output_dir: qa-bc-only - artifact_suffix: "qa-bc-only" + output_dir: qa-btc-only + artifact_suffix: "qa-btc-only" steps: - name: "Checkout" diff --git a/core/src/apps/ur_registry/chains/ethereum/typed_data_transacion.py b/core/src/apps/ur_registry/chains/ethereum/typed_data_transacion.py index 96aa5c666..659fa59a0 100644 --- a/core/src/apps/ur_registry/chains/ethereum/typed_data_transacion.py +++ b/core/src/apps/ur_registry/chains/ethereum/typed_data_transacion.py @@ -258,7 +258,9 @@ async def interact(self): raise ValueError(f"Invalid operation: {operation}") chain_id_origin = data["domain"]["chainId"] if isinstance(chain_id_origin, str): - if chain_id_origin.startswith(("0x", "0X")): + if chain_id_origin.startswith( + "0x" + ) or chain_id_origin.startswith("0X"): chain_id = int.from_bytes( EthereumTypedDataTransacion.decode_hex(chain_id_origin), "big", @@ -292,7 +294,9 @@ async def interact(self): print(f"Message error: {response}.") except Exception as e: if __debug__: - print(f"Data error: {e}") + import sys + + sys.print_exception(e) # type: ignore["print_exception" is not a known member of module] response = messages.Failure( code=FailureType.DataError, message=f"Error: {e}" ) diff --git a/core/src/apps/ur_registry/chains/hardware_requests/get_multi_accounts.py b/core/src/apps/ur_registry/chains/hardware_requests/get_multi_accounts.py index a813320b2..79090f040 100644 --- a/core/src/apps/ur_registry/chains/hardware_requests/get_multi_accounts.py +++ b/core/src/apps/ur_registry/chains/hardware_requests/get_multi_accounts.py @@ -28,11 +28,15 @@ async def run(self): root_fingerprint = None if any(key not in param for key in ("paths", "chain") for param in params): raise ValueError("Invalid param") + if __debug__: + print(f"params: {params}") for param in params: chain = param["chain"] paths = param["paths"] if utils.BITCOIN_ONLY: if chain.lower() not in ("btc", "tbtc", "sbtc"): + if __debug__: + print(f"unsupported chain: {chain} with params: {param}") raise ValueError( "Only Bitcoin chains are supported in BITCOIN_ONLY mode" ) diff --git a/core/src/apps/ur_registry/helpers.py b/core/src/apps/ur_registry/helpers.py index c0cd73f70..1e71119bc 100644 --- a/core/src/apps/ur_registry/helpers.py +++ b/core/src/apps/ur_registry/helpers.py @@ -62,6 +62,7 @@ def generate_HDKey_ED25519( def reveal_name(ctx, root_fingerprint: int, eth_only: bool = False) -> str: from apps.common import passphrase import storage + from trezor import utils device_name = "OneKey Pro" @@ -71,6 +72,8 @@ def reveal_name(ctx, root_fingerprint: int, eth_only: bool = False) -> str: if serial_no: name_components.append(serial_no) + if utils.BITCOIN_ONLY: + name_components.append("btc") name = ":".join(name_components) if (passphrase.is_enabled() and ctx.passphrase) or ( diff --git a/core/src/storage/device.py b/core/src/storage/device.py index 3776ef166..6bd84d308 100644 --- a/core/src/storage/device.py +++ b/core/src/storage/device.py @@ -93,6 +93,8 @@ _FIDO_ENABLED_VALUE: bool | None = None _TURBOMODE_VALUE: bool | None = None _DEVICE_NAME_DISPLAY_ENABLED_VALUE: bool | None = None +_USB_ENABLED_VALUE: bool | None = None +_BLE_ENABLED_BACKUP_VALUE: bool | None = None if utils.USE_THD89: import uctypes @@ -264,6 +266,10 @@ offset += uctypes.sizeof(struct_bool, uctypes.LITTLE_ENDIAN) struct_public["lockscreen"] = (offset, struct_lockscreen) offset += uctypes.sizeof(struct_lockscreen, uctypes.LITTLE_ENDIAN) + struct_public["usb_enabled"] = (offset, struct_bool) + offset += uctypes.sizeof(struct_bool, uctypes.LITTLE_ENDIAN) + struct_public["ble_enabled_bak"] = (offset, struct_bool) + offset += uctypes.sizeof(struct_bool, uctypes.LITTLE_ENDIAN) # public_field = uctypes.struct(0, struct_public, uctypes.LITTLE_ENDIAN) assert ( @@ -329,6 +335,8 @@ _FIDO_ENABLED = struct_public["fido_enabled"][0] _TURBOMODE = struct_public["turbomode"][0] _DEVICE_NAME_DISPLAY_ENABLED = struct_public["device_name_display_enabled"][0] + _USB_ENABLED = struct_public["usb_enabled"][0] + _BLE_ENABLED_BACKUP = struct_public["ble_enabled_bak"][0] U2F_COUNTER = 0x00 # u2f counter # recovery key @@ -399,6 +407,8 @@ _FIDO_ENABLED = (0x91) # bool _TURBOMODE = (0x92) # bool _DEVICE_NAME_DISPLAY_ENABLED = (0x93) # bool + _USB_ENABLED = (0x94) # bool + _BLE_ENABLED_BACKUP = (0x95) # bool # fmt: on SAFETY_CHECK_LEVEL_STRICT: Literal[0] = const(0) SAFETY_CHECK_LEVEL_PROMPT: Literal[1] = const(1) @@ -498,6 +508,30 @@ def set_ble_status(enable: bool) -> None: _BLE_ENABLED_VALUE = enable +def ble_enabled_backup() -> bool: + global _BLE_ENABLED_BACKUP_VALUE + if _BLE_ENABLED_BACKUP_VALUE is None: + ble_enabled = common.get(_NAMESPACE, _BLE_ENABLED_BACKUP, public=True) + if ble_enabled == common._FALSE_BYTE: + _BLE_ENABLED_BACKUP_VALUE = False + else: + _BLE_ENABLED_BACKUP_VALUE = True + return _BLE_ENABLED_BACKUP_VALUE + + +def set_ble_status_backup(enable: bool) -> None: + global _BLE_ENABLED_BACKUP_VALUE + if _BLE_ENABLED_BACKUP_VALUE == enable: + return + common.set_bool( + _NAMESPACE, + _BLE_ENABLED_BACKUP, + enable, + public=True, + ) + _BLE_ENABLED_BACKUP_VALUE = enable + + def set_ble_version(version: str) -> None: """Set ble firmware version.""" if len(version.encode("utf-8")) > BLE_VERSION_MAXLENGTH: @@ -587,6 +621,30 @@ def set_usb_lock_enable(enable: bool) -> None: _USE_USB_PROTECT_VALUE = enable +def is_usb_enabled() -> bool: + global _USB_ENABLED_VALUE + if _USB_ENABLED_VALUE is None: + usb_enabled = common.get(_NAMESPACE, _USB_ENABLED, public=True) + if usb_enabled == common._FALSE_BYTE: + _USB_ENABLED_VALUE = False + else: + _USB_ENABLED_VALUE = True + return _USB_ENABLED_VALUE + + +def set_usb_status(enable: bool) -> None: + global _USB_ENABLED_VALUE + if _USB_ENABLED_VALUE == enable: + return + common.set_bool( + _NAMESPACE, + _USB_ENABLED, + enable, + public=True, + ) + _USB_ENABLED_VALUE = enable + + def enable_fingerprint_unlock(enable: bool) -> None: global _USE_FINGERPRINT_UNLOCK_VALUE diff --git a/core/src/trezor/lvglui/scrs/components/button.py b/core/src/trezor/lvglui/scrs/components/button.py index 542bfe89a..6f4c97bac 100644 --- a/core/src/trezor/lvglui/scrs/components/button.py +++ b/core/src/trezor/lvglui/scrs/components/button.py @@ -291,3 +291,41 @@ def clear_state(self) -> None: def add_state(self) -> None: self.switch.add_state(lv.STATE.CHECKED) + + def disable(self) -> None: + self.add_style( + StyleWrapper() + .bg_color(lv_colors.ONEKEY_GRAY_3) + .text_color(lv_colors.WHITE_2), + 0, + ) + self.switch.add_style(StyleWrapper().bg_opa(lv.OPA._40), 0) + self.switch.add_style( + StyleWrapper().bg_opa(lv.OPA._40), + lv.PART.INDICATOR | lv.STATE.CHECKED, + ) + self.switch.add_style( + StyleWrapper().bg_opa(lv.OPA._40), + lv.PART.KNOB | lv.STATE.DEFAULT, + ) + self.clear_flag(lv.obj.FLAG.CLICKABLE) + self.switch.clear_flag(lv.obj.FLAG.CLICKABLE) + + def enable(self) -> None: + self.add_style( + StyleWrapper() + .bg_color(lv_colors.ONEKEY_BLACK_3) + .text_color(lv_colors.WHITE), + 0, + ) + self.switch.add_style(StyleWrapper().bg_opa(lv.OPA.COVER), 0) + self.switch.add_style( + StyleWrapper().bg_opa(lv.OPA.COVER), + lv.PART.INDICATOR | lv.STATE.CHECKED, + ) + self.switch.add_style( + StyleWrapper().bg_opa(lv.OPA.COVER), + lv.PART.KNOB | lv.STATE.DEFAULT, + ) + self.add_flag(lv.obj.FLAG.CLICKABLE) + self.switch.add_flag(lv.obj.FLAG.CLICKABLE) diff --git a/core/src/trezor/lvglui/scrs/homescreen.py b/core/src/trezor/lvglui/scrs/homescreen.py index f7af80f59..eb2e1b673 100644 --- a/core/src/trezor/lvglui/scrs/homescreen.py +++ b/core/src/trezor/lvglui/scrs/homescreen.py @@ -2445,6 +2445,12 @@ def __init__(self, prev_scr=None): self._load_scr(self) return airgap_enabled = storage_device.is_airgap_mode() + usb_enabled = False + ble_enabled = False + if not airgap_enabled: + usb_enabled = storage_device.is_usb_enabled() + ble_enabled = uart.is_ble_opened() + if airgap_enabled: self.waring_bar = Banner( self.content_area, @@ -2468,11 +2474,12 @@ def __init__(self, prev_scr=None): _(i18n_keys.ITEM__USB), left_img_src="A:/res/connect-way-usb-on.png", ) - if airgap_enabled: - self.by_ble.disable() - self.by_ble.img_left.set_src("A:/res/connect-way-ble-off.png") + if not usb_enabled: self.by_usb.disable() self.by_usb.img_left.set_src("A:/res/connect-way-usb-off.png") + if not ble_enabled: + self.by_ble.disable() + self.by_ble.img_left.set_src("A:/res/connect-way-ble-off.png") # self.by_qrcode = ListItemBtn( # self.container, @@ -2962,7 +2969,7 @@ async def handle_airgap_response(self, screen): if await DUMMY_CONTEXT.wait(screen.request()): screen.destroy() - AirGapSetting(self) + WalletScreen(self) else: screen.destroy() @@ -7509,8 +7516,12 @@ def collect_animation_targets(self) -> list: targets.append(self.advanced_zone) if hasattr(self, "air_gap") and self.air_gap: targets.append(self.air_gap) - if hasattr(self, "description") and self.description: - targets.append(self.description) + if hasattr(self, "usb") and self.usb: + targets.append(self.usb) + if hasattr(self, "ble") and self.ble: + targets.append(self.ble) + # if hasattr(self, "description") and self.description: + # targets.append(self.description) if hasattr(self, "danger_zone") and self.danger_zone: targets.append(self.danger_zone) return targets @@ -7558,40 +7569,66 @@ def __init__(self, prev_scr=None): self.advanced_zone.set_text(_(i18n_keys.TITLE__ADVANCED)) self.advanced_zone.align_to(self.container, lv.ALIGN.OUT_BOTTOM_LEFT, 12, 28) - self.air_gap = ListItemBtnWithSwitch( - self.content_area, _(i18n_keys.ITEM__AIR_GAP_MODE) + self.container_advanced = ContainerFlexCol( + self.content_area, + self.advanced_zone, + align=lv.ALIGN.OUT_BOTTOM_LEFT, + pos=(-12, 16), + padding_row=2, ) - self.air_gap.add_style( - StyleWrapper().bg_color(lv_colors.ONEKEY_BLACK_3).bg_opa(lv.OPA.COVER), 0 + switch_style = ( + StyleWrapper().bg_color(lv_colors.ONEKEY_BLACK_3).bg_opa(lv.OPA.COVER) ) - self.air_gap.set_style_radius(40, 0) - self.air_gap.align_to(self.advanced_zone, lv.ALIGN.OUT_BOTTOM_LEFT, -12, 16) - - self.description = lv.label(self.content_area) - self.description.set_size(456, lv.SIZE.CONTENT) - self.description.set_long_mode(lv.label.LONG.WRAP) - self.description.set_style_text_color(lv_colors.ONEKEY_GRAY, lv.STATE.DEFAULT) - self.description.set_style_text_font(font_GeistRegular26, lv.STATE.DEFAULT) - self.description.set_style_text_line_space(3, 0) - self.description.align_to(self.air_gap, lv.ALIGN.OUT_BOTTOM_LEFT, 12, 16) + self.air_gap = ListItemBtnWithSwitch( + self.container_advanced, _(i18n_keys.ITEM__AIR_GAP_MODE) + ) + self.air_gap.add_style(switch_style, 0) + self.usb = ListItemBtnWithSwitch( + self.container_advanced, _(i18n_keys.ITEM__USB) + ) + self.usb.add_style(switch_style, 0) + self.ble = ListItemBtnWithSwitch( + self.container_advanced, _(i18n_keys.ITEM__BLUETOOTH) + ) + self.ble.add_style(switch_style, 0) + # self.description = lv.label(self.content_area) + # self.description.set_size(456, lv.SIZE.CONTENT) + # self.description.set_long_mode(lv.label.LONG.WRAP) + # self.description.set_style_text_color(lv_colors.ONEKEY_GRAY, lv.STATE.DEFAULT) + # self.description.set_style_text_font(font_GeistRegular26, lv.STATE.DEFAULT) + # self.description.set_style_text_line_space(3, 0) + # self.description.align_to(self.air_gap, lv.ALIGN.OUT_BOTTOM_LEFT, 12, 16) air_gap_enabled = storage_device.is_airgap_mode() if air_gap_enabled: self.air_gap.add_state() - self.description.set_text( - _( - i18n_keys.CONTENT__BLUETOOTH_USB_AND_NFT_TRANSFER_FUNCTIONS_HAVE_BEEN_DISABLED - ) - ) + self.usb.disable() + self.ble.disable() + # self.description.set_text( + # _( + # i18n_keys.CONTENT__BLUETOOTH_USB_AND_NFT_TRANSFER_FUNCTIONS_HAVE_BEEN_DISABLED + # ) + # ) else: self.air_gap.clear_state() - self.description.set_text( - _( - i18n_keys.CONTENT__AFTER_ENABLING_THE_AIRGAP_BLUETOOTH_USB_AND_NFC_TRANSFER_WILL_BE_DISABLED_SIMULTANEOUSLY - ) - ) - self.air_gap.add_event_cb(self.on_event, lv.EVENT.VALUE_CHANGED, None) - self.air_gap.add_event_cb(self.on_event, lv.EVENT.READY, None) - self.air_gap.add_event_cb(self.on_event, lv.EVENT.CANCEL, None) + if storage_device.is_usb_enabled(): + self.usb.add_state() + else: + self.usb.clear_state() + if uart.is_ble_opened(): + self.ble.add_state() + else: + self.ble.clear_state() + # self.description.set_text( + # _( + # i18n_keys.CONTENT__AFTER_ENABLING_THE_AIRGAP_BLUETOOTH_USB_AND_NFC_TRANSFER_WILL_BE_DISABLED_SIMULTANEOUSLY + # ) + # ) + + self.container_advanced.add_event_cb( + self.on_event, lv.EVENT.VALUE_CHANGED, None + ) + self.container_advanced.add_event_cb(self.on_event, lv.EVENT.READY, None) + self.container_advanced.add_event_cb(self.on_event, lv.EVENT.CANCEL, None) # Danger Zone: Reset Device self.danger_zone = lv.label(self.content_area) self.danger_zone.set_size(456, lv.SIZE.CONTENT) @@ -7599,7 +7636,9 @@ def __init__(self, prev_scr=None): self.danger_zone.set_style_text_color(lv_colors.WHITE_2, lv.STATE.DEFAULT) self.danger_zone.set_style_text_font(font_GeistSemiBold30, lv.STATE.DEFAULT) self.danger_zone.set_text(_(i18n_keys.TITLE__DANGER_ZONE)) - self.danger_zone.align_to(self.description, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 40) + self.danger_zone.align_to( + self.container_advanced, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 40 + ) self.rest_device = ListItemBtn( self.content_area, _(i18n_keys.ITEM__RESET_DEVICE), @@ -7675,33 +7714,47 @@ def on_event(self, event_obj): enable=False, callback_obj=self.air_gap, ) + elif target == self.usb.switch: + if target.has_state(lv.STATE.CHECKED): + utils.enable_usb() + else: + utils.disable_usb() + elif target == self.ble.switch: + if target.has_state(lv.STATE.CHECKED): + utils.enable_ble() + else: + utils.disable_ble() elif code == lv.EVENT.READY: if not storage_device.is_airgap_mode(): - self.description.set_text( - _( - i18n_keys.CONTENT__BLUETOOTH_USB_AND_NFT_TRANSFER_FUNCTIONS_HAVE_BEEN_DISABLED - ) - ) - self.danger_zone.align_to( - self.description, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 40 - ) - self.rest_device.align_to( - self.danger_zone, lv.ALIGN.OUT_BOTTOM_MID, -12, 16 - ) + # self.description.set_text( + # _( + # i18n_keys.CONTENT__BLUETOOTH_USB_AND_NFT_TRANSFER_FUNCTIONS_HAVE_BEEN_DISABLED + # ) + # ) + # self.danger_zone.align_to( + # self.description, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 40 + # ) + # self.rest_device.align_to( + # self.danger_zone, lv.ALIGN.OUT_BOTTOM_MID, -12, 16 + # ) utils.enable_airgap_mode() + self.usb.disable() + self.ble.disable() else: - self.description.set_text( - _( - i18n_keys.CONTENT__AFTER_ENABLING_THE_AIRGAP_BLUETOOTH_USB_AND_NFC_TRANSFER_WILL_BE_DISABLED_SIMULTANEOUSLY - ) - ) - self.danger_zone.align_to( - self.description, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 40 - ) - self.rest_device.align_to( - self.danger_zone, lv.ALIGN.OUT_BOTTOM_MID, -12, 16 - ) + # self.description.set_text( + # _( + # i18n_keys.CONTENT__AFTER_ENABLING_THE_AIRGAP_BLUETOOTH_USB_AND_NFC_TRANSFER_WILL_BE_DISABLED_SIMULTANEOUSLY + # ) + # ) + # self.danger_zone.align_to( + # self.description, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 40 + # ) + # self.rest_device.align_to( + # self.danger_zone, lv.ALIGN.OUT_BOTTOM_MID, -12, 16 + # ) utils.disable_airgap_mode() + self.usb.enable() + self.ble.enable() elif code == lv.EVENT.CANCEL: if storage_device.is_airgap_mode(): self.air_gap.add_state() diff --git a/core/src/trezor/uart.py b/core/src/trezor/uart.py index f931b4875..968693cc1 100644 --- a/core/src/trezor/uart.py +++ b/core/src/trezor/uart.py @@ -2,7 +2,7 @@ from micropython import const from typing import TYPE_CHECKING -from storage import device +import storage.device as storage_device from trezor import config, io, log, loop, motor, utils, workflow from trezor.lvglui import StatusBar from trezor.ui import display @@ -118,8 +118,8 @@ async def handle_fingerprint(): warning_level = 3 elif isinstance(e, fingerprint.NotMatch): # increase failed count - device.finger_failed_count_incr() - failed_count = device.finger_failed_count() + storage_device.finger_failed_count_incr() + failed_count = storage_device.finger_failed_count() if failed_count >= utils.MAX_FP_ATTEMPTS: from trezor.lvglui.scrs.pinscreen import InputPin @@ -153,8 +153,8 @@ async def handle_fingerprint(): if __debug__: print(f"fingerprint match {match_id}") # motor.vibrate(motor.SUCCESS) - if device.is_passphrase_pin_enabled(): - device.set_passphrase_pin_enabled(False) + if storage_device.is_passphrase_pin_enabled(): + storage_device.set_passphrase_pin_enabled(False) # # 1. publish signal if fingerprints.has_takers(): if __debug__: @@ -181,10 +181,10 @@ async def handle_fingerprint(): async def handle_usb_state(): while True: try: - utils.AIRGAP_MODE_CHANGED = False + utils.USB_STATE_CHANGED = False usb_state = loop.wait(io.USB_STATE) state, enable = await usb_state - if enable is not None and not device.is_airgap_mode(): + if enable is not None and utils.is_usb_enabled(): import usb usb.bus.connect_ctrl(enable) @@ -209,9 +209,13 @@ async def handle_usb_state(): if utils.BATTERY_CAP: StatusBar.get_instance().set_battery_img(utils.BATTERY_CAP, False) _request_charging_status() - if not utils.AIRGAP_MODE_CHANGED: # not enable or disable airgap mode - usb_auto_lock = device.is_usb_lock_enabled() - if usb_auto_lock and device.is_initialized() and config.has_pin(): + if not utils.USB_STATE_CHANGED: # not enable or disable airgap mode + usb_auto_lock = storage_device.is_usb_lock_enabled() + if ( + usb_auto_lock + and storage_device.is_initialized() + and config.has_pin() + ): from trezor.lvglui.scrs import fingerprints from trezor.crypto import se_thd89 @@ -226,7 +230,7 @@ async def handle_usb_state(): elif not usb_auto_lock and not state: await safe_reloop(ack=False) else: - utils.AIRGAP_MODE_CHANGED = False + utils.USB_STATE_CHANGED = False base.reload_settings_from_storage() except Exception as exec: if __debug__: @@ -371,7 +375,7 @@ async def _deal_ble_pair(value): close_camera() flashled_close() - if not device.is_initialized(): + if not storage_device.is_initialized(): from trezor.lvglui.scrs.ble import PairForbiddenScreen PairForbiddenScreen() @@ -418,7 +422,7 @@ async def _deal_button_press(value: bytes) -> None: if res == _PRESS_SHORT: if display.backlight(): display.backlight(0) - if device.is_initialized(): + if storage_device.is_initialized(): if utils.is_initialization_processing(): return utils.AUTO_POWER_OFF = True @@ -450,7 +454,8 @@ async def _deal_button_press(value: bytes) -> None: close_camera() PowerOff( True - if not utils.is_initialization_processing() and device.is_initialized() + if not utils.is_initialization_processing() + and storage_device.is_initialized() else False ) await loop.sleep(200) @@ -543,7 +548,7 @@ async def _deal_pair_res(value: bytes) -> None: motor.vibrate(motor.ERROR) StatusBar.get_instance().show_ble(StatusBar.BLE_STATE_ENABLED) - if device.is_initialized(): + if storage_device.is_initialized(): if PENDING_PAIR_CODE is not None: PENDING_PAIR_FAILED = True if PAIR_ERROR_SCREEN is None or PAIR_ERROR_SCREEN.destroyed: @@ -552,7 +557,7 @@ async def _deal_pair_res(value: bytes) -> None: workflow.spawn(show_pairing_error()) else: motor.vibrate(motor.SUCCESS) - if device.is_initialized(): + if storage_device.is_initialized(): from trezor.ui.layouts import show_pairing_success workflow.spawn(show_pairing_success()) @@ -578,17 +583,17 @@ async def _deal_ble_status(value: bytes) -> None: return StatusBar.get_instance().show_ble(StatusBar.BLE_STATE_ENABLED) if config.is_unlocked(): - device.set_ble_status(enable=True) + storage_device.set_ble_status(enable=True) elif res == _BLE_STATUS_CLOSED: utils.BLE_CONNECTED = False - if not device.is_initialized(): + if not storage_device.is_initialized(): StatusBar.get_instance().show_ble(StatusBar.BLE_STATE_ENABLED) ctrl_ble(True) return BLE_ENABLED = False StatusBar.get_instance().show_ble(StatusBar.BLE_STATE_DISABLED) if config.is_unlocked(): - device.set_ble_status(enable=False) + storage_device.set_ble_status(enable=False) def _retrieve_flashled_brightness(value: bytes) -> None: @@ -731,9 +736,12 @@ def ctrl_ble(enable: bool) -> None: """Request to open or close ble. @param enable: True to open, False to close """ + global BLE_ENABLED if enable: + BLE_ENABLED = True BLE_CTRL.ctrl(0x81, b"\x01") else: + BLE_ENABLED = False BLE_CTRL.ctrl(0x81, b"\x02") @@ -849,7 +857,7 @@ def stop_mode(reset_timer: bool = False): wireless_charge = True utils.enter_lowpower( - reset_timer, device.get_autoshutdown_delay_ms(), lp_timer_enable + reset_timer, storage_device.get_autoshutdown_delay_ms(), lp_timer_enable ) if wireless_charge: fetch_battery_temperature() diff --git a/core/src/trezor/utils.py b/core/src/trezor/utils.py index ca59b294a..ff3d8b3f5 100644 --- a/core/src/trezor/utils.py +++ b/core/src/trezor/utils.py @@ -87,7 +87,7 @@ def board_version() -> str: CHARGE_WIRELESS_STATUS = CHARGE_WIRELESS_STOP CHARGE_ENABLE: bool | None = None CHARGING = False -AIRGAP_MODE_CHANGED = False +USB_STATE_CHANGED = False RESTART_MAIN_LOOP = False if __debug__: @@ -167,7 +167,7 @@ def turn_on_lcd_if_possible(timeouts_ms: int | None = None) -> bool: def lcd_resume(timeouts_ms: int | None = None) -> bool: from trezor.ui import display - from storage import device + import storage.device as storage_device from apps import base from trezor import config, uart @@ -176,12 +176,12 @@ def lcd_resume(timeouts_ms: int | None = None) -> bool: # if ChargingPromptScr.has_instance(): # ChargingPromptScr.get_instance().destroy() uart.ctrl_wireless_charge(False) - if display.backlight() != device.get_brightness() or timeouts_ms: + if display.backlight() != storage_device.get_brightness() or timeouts_ms: global AUTO_POWER_OFF from trezor.lvglui.scrs.homescreen import BacklightSetting if not BacklightSetting.page_is_visible(): - display.backlight(device.get_brightness()) + display.backlight(storage_device.get_brightness()) AUTO_POWER_OFF = False from trezor.lvglui.scrs import fingerprints @@ -206,14 +206,14 @@ async def internal_reloop(): async def turn_off_lcd(): from trezor.ui import display from trezor import loop, wire - from storage import device + import storage.device as storage_device if display.backlight(): global AUTO_POWER_OFF display.backlight(0) AUTO_POWER_OFF = True await wire.signal_ack() - if device.is_initialized(): + if storage_device.is_initialized(): global RESTART_MAIN_LOOP RESTART_MAIN_LOOP = True loop.clear() @@ -233,39 +233,81 @@ def is_low_battery(): return False -def disable_airgap_mode(): - from storage import device - from trezor import uart - from trezor.lvglui import StatusBar +def is_usb_enabled() -> bool: + import storage.device as storage_device + + airgap_enabled = storage_device.is_airgap_mode() + if airgap_enabled: + return False - global AIRGAP_MODE_CHANGED + return storage_device.is_usb_enabled() - device.enable_airgap_mode(False) - StatusBar.get_instance().show_air_gap_mode_tips(False) - uart.ctrl_ble(enable=True) - AIRGAP_MODE_CHANGED = True + +def disable_usb(persist: bool = True) -> None: + import usb + import storage.device as storage_device + + global USB_STATE_CHANGED + if persist: + storage_device.set_usb_status(False) + usb.bus.connect_ctrl(False) + USB_STATE_CHANGED = True + + +def enable_usb(restore: bool = False) -> None: import usb + import storage.device as storage_device - usb.bus.connect_ctrl(True) + global USB_STATE_CHANGED + if restore: + usb.bus.connect_ctrl(storage_device.is_usb_enabled()) + else: + storage_device.set_usb_status(True) + usb.bus.connect_ctrl(True) + USB_STATE_CHANGED = True -def enable_airgap_mode(): - from storage import device + +def disable_ble(persist_backup: bool = False) -> None: + import storage.device as storage_device + from trezor import uart + + if persist_backup: + storage_device.set_ble_status_backup(uart.is_ble_opened()) + uart.ctrl_ble(False) + + +def enable_ble(restore_backup: bool = False) -> None: from trezor import uart + import storage.device as storage_device + + if restore_backup: + uart.ctrl_ble(storage_device.ble_enabled_backup()) + else: + uart.ctrl_ble(True) + + +def disable_airgap_mode(): + import storage.device as storage_device from trezor.lvglui import StatusBar - global AIRGAP_MODE_CHANGED + storage_device.enable_airgap_mode(False) + StatusBar.get_instance().show_air_gap_mode_tips(False) + enable_ble(restore_backup=True) + enable_usb(restore=True) + + +def enable_airgap_mode(): + import storage.device as storage_device + from trezor.lvglui import StatusBar - device.enable_airgap_mode(True) + storage_device.enable_airgap_mode(True) StatusBar.get_instance().show_air_gap_mode_tips(True) - uart.ctrl_ble(enable=False) + disable_ble(persist_backup=True) + disable_usb(persist=False) from trezorio import nfc nfc.pwr_ctrl(False) - AIRGAP_MODE_CHANGED = True - import usb - - usb.bus.connect_ctrl(False) def show_app_guide(): From b7d7d9325826755f036b73517b09e1860d97270b Mon Sep 17 00:00:00 2001 From: Ritchie Date: Tue, 16 Dec 2025 17:03:22 +0800 Subject: [PATCH 4/4] chore: bump version to 4.18.0 --- core/embed/firmware/version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/embed/firmware/version.h b/core/embed/firmware/version.h index 01475a74d..5d346aa2c 100644 --- a/core/embed/firmware/version.h +++ b/core/embed/firmware/version.h @@ -9,8 +9,8 @@ #define FIX_VERSION_BUILD VERSION_BUILD #define ONEKEY_VERSION_MAJOR 4 -#define ONEKEY_VERSION_MINOR 17 -#define ONEKEY_VERSION_PATCH 1 +#define ONEKEY_VERSION_MINOR 18 +#define ONEKEY_VERSION_PATCH 0 #define ONEKEY_VERSION_BUILD 0 // Minimum SE version required for firmware upgrade