diff --git a/common/protob/messages-ton.proto b/common/protob/messages-ton.proto index 3e4efbdfd7..88f58a9a57 100644 --- a/common/protob/messages-ton.proto +++ b/common/protob/messages-ton.proto @@ -119,3 +119,39 @@ message TonSignedProof { optional bytes signature = 1; // signed transaction message } + +/** + * Request: Require Device to sign TON Connect sign data payload + * @start + * @next TonSignedData + * @next Failure + */ +message TonSignData { + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + required TonSignDataType type = 2; // payload type + required bytes payload = 3; // payload bytes: TEXT=utf-8 bytes, BINARY=raw bytes, CELL=BOC bytes + optional string schema = 4; // CELL only: schema string included in digest as crc32(utf8(schema)) + required string appdomain = 5; // app domain / origin + required uint64 timestamp = 6; // UNIX timestamp in seconds (UTC) at the moment on creating the signature + optional string from_address = 7; // expected signer address for validation only (not part of signed digest) + optional TonWalletVersion wallet_version = 8 [default=V4R2]; // ton wallet version + optional uint32 wallet_id = 9 [default=698983191]; // subwallet_id value + optional TonWorkChain workchain = 10 [default=BASECHAIN]; + optional bool is_bounceable = 11 [default=false]; // affects derived/displayed signer address format only + optional bool is_testnet_only = 12 [default=false]; // affects derived/displayed signer address format only + + enum TonSignDataType { + TEXT = 0; + BINARY = 1; + CELL = 2; + } +} + +/** + * Response: signature corresponding to TonSignData + * @end + */ +message TonSignedData { + optional bytes signature = 1; // ed25519 signature over digest field + optional bytes digest = 2; // exact 32-byte TON Connect signData digest that was signed +} diff --git a/common/protob/messages.proto b/common/protob/messages.proto index d153309370..c454107a97 100644 --- a/common/protob/messages.proto +++ b/common/protob/messages.proto @@ -543,6 +543,8 @@ enum MessageType { MessageType_TonSignProof = 11905 [(wire_in) = true]; MessageType_TonSignedProof = 11906 [(wire_out) = true]; MessageType_TonTxAck = 11907 [(wire_in) = true]; + MessageType_TonSignData = 11908 [(wire_in) = true]; + MessageType_TonSignedData = 11909 [(wire_out) = true]; // scdo MessageType_ScdoGetAddress = 12001 [(wire_in) = true]; diff --git a/core/embed/firmware/version.h b/core/embed/firmware/version.h index 6a9213e6a5..3c52c9bcb6 100644 --- a/core/embed/firmware/version.h +++ b/core/embed/firmware/version.h @@ -9,7 +9,7 @@ #define FIX_VERSION_BUILD VERSION_BUILD #define ONEKEY_VERSION_MAJOR 4 -#define ONEKEY_VERSION_MINOR 20 +#define ONEKEY_VERSION_MINOR 21 #define ONEKEY_VERSION_PATCH 0 #define ONEKEY_VERSION_BUILD 0 diff --git a/core/src/all_modules.py b/core/src/all_modules.py index cd9ea4804b..a15b01341e 100644 --- a/core/src/all_modules.py +++ b/core/src/all_modules.py @@ -137,6 +137,8 @@ import trezor.enums.SolanaOffChainMessageFormat trezor.enums.SolanaOffChainMessageVersion import trezor.enums.SolanaOffChainMessageVersion +trezor.enums.TonSignDataType +import trezor.enums.TonSignDataType trezor.enums.TonWalletVersion import trezor.enums.TonWalletVersion trezor.enums.TonWorkChain @@ -891,6 +893,8 @@ import apps.ton.get_address apps.ton.layout import apps.ton.layout +apps.ton.sign_data +import apps.ton.sign_data apps.ton.sign_message import apps.ton.sign_message apps.ton.sign_proof diff --git a/core/src/apps/algorand/transactions/transaction.py b/core/src/apps/algorand/transactions/transaction.py index 4c1e37e791..95dd3649a6 100644 --- a/core/src/apps/algorand/transactions/transaction.py +++ b/core/src/apps/algorand/transactions/transaction.py @@ -75,7 +75,8 @@ def as_hash(hash): """Confirm that a value is 32 bytes. If all zeros, or a falsy value, return None""" if not hash: return None - assert isinstance(hash, (bytes, bytearray)), f"{hash} is not bytes" + if not isinstance(hash, (bytes, bytearray)): + raise TypeError(f"{hash} is not bytes") if len(hash) != constants.hash_len: raise error.WrongHashLengthError if not any(hash): @@ -1191,9 +1192,12 @@ def __init__( @staticmethod def state_schema(schema): """Confirm the argument is a StateSchema, or false which is coerced to None""" - if not schema or not schema.dictify(): + if not schema: return None # Coerce false/empty values to None, to help __eq__ - assert isinstance(schema, StateSchema), f"{schema} is not a StateSchema" + if not isinstance(schema, StateSchema): + raise TypeError(f"{schema} is not a StateSchema") + if not schema.dictify(): + return None # Coerce empty values to None, to help __eq__ return schema @staticmethod @@ -1201,7 +1205,8 @@ def teal_bytes(teal): """Confirm the argument is bytes-like, or false which is coerced to None""" if not teal: return None # Coerce false values like "" to None, to help __eq__ - assert isinstance(teal, (bytes, bytearray)), f"Program {teal} is not bytes" + if not isinstance(teal, (bytes, bytearray)): + raise TypeError(f"Program {teal} is not bytes") return teal @staticmethod @@ -1216,7 +1221,7 @@ def as_bytes(e): if isinstance(e, int): # Uses 8 bytes, big endian to match TEAL's btoi return e.to_bytes(8, "big") # raises for negative or too big - assert False, f"{e} is not bytes, str, or int" + raise TypeError(f"{e} is not bytes, str, or int") if not lst: return None diff --git a/core/src/apps/base.py b/core/src/apps/base.py index a30115eba8..46f490fced 100644 --- a/core/src/apps/base.py +++ b/core/src/apps/base.py @@ -393,10 +393,10 @@ async def handle_DoPreauthorized( req = await ctx.call_any(PreauthorizedRequest(), *wire_types) - assert req.MESSAGE_WIRE_TYPE is not None - handler = workflow_handlers.find_registered_handler( - ctx.iface, req.MESSAGE_WIRE_TYPE - ) + wire_type = req.MESSAGE_WIRE_TYPE + if wire_type is None: + return wire.unexpected_message() + handler = workflow_handlers.find_registered_handler(ctx.iface, wire_type) if handler is None: return wire.unexpected_message() @@ -444,11 +444,12 @@ async def handle_UnlockPath(ctx: wire.Context, msg: UnlockPath) -> protobuf.Mess wire_types = (MessageType.GetAddress, MessageType.GetPublicKey, MessageType.SignTx) req = await ctx.call_any(UnlockedPathRequest(mac=expected_mac), *wire_types) - assert req.MESSAGE_WIRE_TYPE in wire_types - handler = workflow_handlers.find_registered_handler( - ctx.iface, req.MESSAGE_WIRE_TYPE - ) - assert handler is not None + wire_type = req.MESSAGE_WIRE_TYPE + if wire_type is None or wire_type not in wire_types: + return wire.unexpected_message() + handler = workflow_handlers.find_registered_handler(ctx.iface, wire_type) + if handler is None: + return wire.unexpected_message() return await handler(ctx, req, msg) # type: ignore [Expected 2 positional arguments] diff --git a/core/src/apps/benfen/layout.py b/core/src/apps/benfen/layout.py index 10b6f1b2f3..34277add80 100755 --- a/core/src/apps/benfen/layout.py +++ b/core/src/apps/benfen/layout.py @@ -17,7 +17,8 @@ async def require_show_overview( if value == "All": amount_str = "All" else: - assert isinstance(value, int) + if not isinstance(value, int): + raise TypeError("value must be int") amount_str = format_benfen_amount(value, currency_symbol) return await should_show_details( ctx, @@ -42,7 +43,8 @@ async def require_confirm_fee( if value == "All": amount_str = "All" else: - assert isinstance(value, int) + if not isinstance(value, int): + raise TypeError("value must be int") amount_str = format_benfen_amount(value, currency_symbol) total_amount = ( format_benfen_amount(value + gas_price, currency_symbol) diff --git a/core/src/apps/binance/helpers.py b/core/src/apps/binance/helpers.py index 742e675f0d..52b118b0b9 100644 --- a/core/src/apps/binance/helpers.py +++ b/core/src/apps/binance/helpers.py @@ -91,6 +91,7 @@ def address_from_public_key(pubkey: bytes, hrp: str) -> str: h = sha256_ripemd160(pubkey).digest() - assert (len(h) * 8) % 5 == 0 # no padding will be added by convertbits + if (len(h) * 8) % 5 != 0: + raise RuntimeError("Unexpected address hash length") convertedbits = bech32.convertbits(h, 8, 5) return bech32.bech32_encode(hrp, convertedbits, bech32.Encoding.BECH32) diff --git a/core/src/apps/bitcoin/addresses.py b/core/src/apps/bitcoin/addresses.py index 59a8624fcf..af9dce91f2 100644 --- a/core/src/apps/bitcoin/addresses.py +++ b/core/src/apps/bitcoin/addresses.py @@ -133,7 +133,8 @@ def address_p2wsh_in_p2sh(witness_script_hash: bytes, coin: CoinInfo) -> str: def address_p2wpkh(pubkey: bytes, coin: CoinInfo) -> str: - assert coin.bech32_prefix is not None + if coin.bech32_prefix is None: + raise wire.ProcessError("Bech32 not enabled on this coin") pubkeyhash = ecdsa_hash_pubkey(pubkey, coin) return encode_bech32_address(coin.bech32_prefix, 0, pubkeyhash) @@ -143,13 +144,15 @@ def address_p2wsh(witness_script_hash: bytes, hrp: str) -> str: def address_p2tr(pubkey: bytes, coin: CoinInfo) -> str: - assert coin.bech32_prefix is not None + if coin.bech32_prefix is None: + raise wire.ProcessError("Bech32 not enabled on this coin") output_pubkey = bip340.tweak_public_key(pubkey[1:]) return encode_bech32_address(coin.bech32_prefix, 1, output_pubkey) def address_to_cashaddr(address: str, coin: CoinInfo) -> str: - assert coin.cashaddr_prefix is not None + if coin.cashaddr_prefix is None: + raise wire.ProcessError("Cashaddr not enabled on this coin") raw = base58.decode_check(address, coin.b58_hash) version, data = raw[0], raw[1:] if version == coin.address_type: diff --git a/core/src/apps/bitcoin/common.py b/core/src/apps/bitcoin/common.py index 647f112cda..2c82e2efbb 100644 --- a/core/src/apps/bitcoin/common.py +++ b/core/src/apps/bitcoin/common.py @@ -128,7 +128,8 @@ def ecdsa_hash_pubkey(pubkey: bytes, coin: CoinInfo) -> bytes: def encode_bech32_address(prefix: str, witver: int, script: bytes) -> str: - assert witver in _BECH32_WITVERS + if witver not in _BECH32_WITVERS: + raise wire.ProcessError("Invalid address witness version") address = bech32.encode(prefix, witver, script) if address is None: raise wire.ProcessError("Invalid address") @@ -137,10 +138,12 @@ def encode_bech32_address(prefix: str, witver: int, script: bytes) -> str: def decode_bech32_address(prefix: str, address: str) -> tuple[int, bytes]: witver, raw = bech32.decode(prefix, address) + if witver is None: + raise wire.DataError("Invalid address witness program") if witver not in _BECH32_WITVERS: raise wire.DataError("Invalid address witness program") - assert witver is not None - assert raw is not None + if raw is None: + raise wire.DataError("Invalid address witness program") # check that P2TR address encodes a valid BIP340 public key if witver == 1 and not bip340.verify_publickey(raw): raise wire.DataError("Invalid Taproot witness program") @@ -162,7 +165,8 @@ def input_is_taproot(txi: TxInput) -> bool: return True if txi.script_type == InputScriptType.EXTERNAL: - assert txi.script_pubkey is not None + if txi.script_pubkey is None: + raise wire.DataError("Missing script pubkey") if txi.script_pubkey[0] == OP_1: return True diff --git a/core/src/apps/bitcoin/get_public_key.py b/core/src/apps/bitcoin/get_public_key.py index c2000874ed..ca6162d941 100644 --- a/core/src/apps/bitcoin/get_public_key.py +++ b/core/src/apps/bitcoin/get_public_key.py @@ -51,19 +51,23 @@ async def get_public_key( and script_type == InputScriptType.SPENDP2SHWITNESS and (msg.ignore_xpub_magic or coin.xpub_magic_segwit_p2sh is not None) ): - assert coin.xpub_magic_segwit_p2sh is not None - node_xpub = node.serialize_public( + xpub_magic = ( coin.xpub_magic if msg.ignore_xpub_magic else coin.xpub_magic_segwit_p2sh ) + if xpub_magic is None: + raise wire.DataError("Invalid xpub magic") + node_xpub = node.serialize_public(xpub_magic) elif ( coin.segwit and script_type == InputScriptType.SPENDWITNESS and (msg.ignore_xpub_magic or coin.xpub_magic_segwit_native is not None) ): - assert coin.xpub_magic_segwit_native is not None - node_xpub = node.serialize_public( + xpub_magic = ( coin.xpub_magic if msg.ignore_xpub_magic else coin.xpub_magic_segwit_native ) + if xpub_magic is None: + raise wire.DataError("Invalid xpub magic") + node_xpub = node.serialize_public(xpub_magic) else: raise wire.DataError("Invalid combination of coin and script_type") diff --git a/core/src/apps/bitcoin/keychain.py b/core/src/apps/bitcoin/keychain.py index 539bf090c5..a154b48ec2 100644 --- a/core/src/apps/bitcoin/keychain.py +++ b/core/src/apps/bitcoin/keychain.py @@ -102,13 +102,15 @@ def validate_path_against_script_type( patterns = [] if msg is not None: - assert address_n is None and script_type is None + if address_n is not None or script_type is not None: + raise ValueError("Provide either msg or explicit path/script type") address_n = msg.address_n script_type = msg.script_type or InputScriptType.SPENDADDRESS multisig = bool(getattr(msg, "multisig", False)) else: - assert address_n is not None and script_type is not None + if address_n is None or script_type is None: + raise ValueError("Missing path or script type") if script_type == InputScriptType.SPENDADDRESS and not multisig: patterns.append(PATTERN_BIP44) diff --git a/core/src/apps/bitcoin/scripts.py b/core/src/apps/bitcoin/scripts.py index a314494dcc..399432d895 100644 --- a/core/src/apps/bitcoin/scripts.py +++ b/core/src/apps/bitcoin/scripts.py @@ -69,7 +69,8 @@ def write_input_script_prefixed( write_bytes_prefixed(w, script_sig) elif script_type == InputScriptType.SPENDMULTISIG: # p2sh multisig - assert multisig is not None # checked in sanitize_tx_input + if multisig is None: + raise wire.ProcessError("Missing multisig data") signature_index = multisig_pubkey_index(multisig, pubkey) write_input_script_multisig_prefixed( w, multisig, signature, signature_index, sighash_type, coin diff --git a/core/src/apps/bitcoin/scripts_decred.py b/core/src/apps/bitcoin/scripts_decred.py index 0ed6961fab..0840ebc663 100644 --- a/core/src/apps/bitcoin/scripts_decred.py +++ b/core/src/apps/bitcoin/scripts_decred.py @@ -41,7 +41,8 @@ def write_input_script_prefixed( ) elif script_type == InputScriptType.SPENDMULTISIG: # p2sh multisig - assert multisig is not None # checked in sanitize_tx_input + if multisig is None: + raise wire.ProcessError("Missing multisig data") signature_index = multisig_pubkey_index(multisig, pubkey) write_input_script_multisig_prefixed( w, multisig, signature, signature_index, sighash_type, coin diff --git a/core/src/apps/bitcoin/sign_taproot.py b/core/src/apps/bitcoin/sign_taproot.py index 818f206f83..143f6a6447 100644 --- a/core/src/apps/bitcoin/sign_taproot.py +++ b/core/src/apps/bitcoin/sign_taproot.py @@ -55,9 +55,12 @@ async def sign_taproot( found_ours = False contains_script_path_spending = False for i, input in enumerate(psbt.inputs): - assert input.prev_txid is not None - assert input.prev_out is not None - assert input.sequence is not None + if input.prev_txid is None: + raise wire.DataError("Missing previous transaction ID") + if input.prev_out is None: + raise wire.DataError("Missing previous output index") + if input.sequence is None: + raise wire.DataError("Missing input sequence") if input.non_witness_utxo is not None: # TODO: check if non-witness UTXO is presigned @@ -71,7 +74,8 @@ async def sign_taproot( amount = input.witness_utxo.nValue is_wit, wit_ver, _ = is_witness(scriptPub) - assert is_wit and wit_ver == 1, "Only taproot input is allowed" + if not is_wit or wit_ver != 1: + raise wire.DataError("Only taproot input is allowed") total_in += amount for key, (_, origin) in input.tap_bip32_paths.items(): if origin.fingerprint != master_fp: @@ -82,12 +86,15 @@ async def sign_taproot( raise wire.DataError("Wallet mismatch") node = keychain.derive(origin.path) intend_key = node.public_key()[1:] - assert intend_key == key, "Invalid key" + if intend_key != key: + raise wire.DataError("Invalid key") if not input.tap_scripts: - assert key == input.tap_internal_key, "Invalid internal key" + if key != input.tap_internal_key: + raise wire.DataError("Invalid internal key") else: script, _ = list(input.tap_scripts.keys())[0] - assert key in script, "Invalid script" + if key not in script: + raise wire.DataError("Invalid script") if not contains_script_path_spending: contains_script_path_spending = True @@ -114,7 +121,8 @@ async def sign_taproot( wit, ver, prog = out.is_witness() out_address = None if wit: - assert coin.bech32_prefix is not None + if coin.bech32_prefix is None: + raise wire.DataError("Bech32 not enabled on this coin") out_address = encode_bech32_address(coin.bech32_prefix, ver, prog) elif out.is_p2pkh(): out_address = base58.encode_check( @@ -128,9 +136,8 @@ async def sign_taproot( ) elif out.is_opreturn(): if out.nValue != 0: - assert ( - contains_script_path_spending and len(psbt.inputs) == 1 - ), "OpReturn output should have 0 value" + if not (contains_script_path_spending and len(psbt.inputs) == 1): + raise wire.DataError("OpReturn output should have 0 value") op_return_data = out.scriptPubKey[2:] else: raise Exception("Invalid output type") @@ -228,7 +235,8 @@ async def sign_taproot( if not script_path_spending: input.tap_key_sig = signature else: - assert leaf_hash is not None + if leaf_hash is None: + raise RuntimeError("Missing leaf hash") input.tap_script_sigs[(key, leaf_hash)] = signature return SignedPsbt(psbt=psbt.serialize()) diff --git a/core/src/apps/bitcoin/sign_tx/__init__.py b/core/src/apps/bitcoin/sign_tx/__init__.py index 1f045d52a5..8d09f1fc43 100644 --- a/core/src/apps/bitcoin/sign_tx/__init__.py +++ b/core/src/apps/bitcoin/sign_tx/__init__.py @@ -93,7 +93,8 @@ async def sign_tx( req = signer.send(res) if isinstance(req, tuple): request_class, req = req - assert TxRequest.is_type_of(req) + if not TxRequest.is_type_of(req): + raise RuntimeError("Invalid transaction request") if req.request_type == RequestType.TXFINISHED: from trezor.ui.layouts import confirm_final diff --git a/core/src/apps/bitcoin/sign_tx/bitcoin.py b/core/src/apps/bitcoin/sign_tx/bitcoin.py index e1007a9f68..9f65417f2c 100644 --- a/core/src/apps/bitcoin/sign_tx/bitcoin.py +++ b/core/src/apps/bitcoin/sign_tx/bitcoin.py @@ -236,7 +236,8 @@ async def step3_verify_inputs(self) -> None: writers.write_tx_input_check(h_check, txi) # txi.script_pubkey checked in sanitize_tx_input - assert txi.script_pubkey is not None + if txi.script_pubkey is None: + raise wire.DataError("Missing script pubkey") await self.verify_presigned_external_input( i, txi, txi.script_pubkey ) @@ -338,7 +339,8 @@ async def process_internal_input(self, txi: TxInput, node: bip32.HDNode) -> None await self.approver.add_internal_input(txi, node) async def process_external_input(self, txi: TxInput) -> None: - assert txi.script_pubkey is not None # checked in sanitize_tx_input + if txi.script_pubkey is None: + raise wire.DataError("Missing script pubkey") self.approver.add_external_input(txi) @@ -353,8 +355,10 @@ async def process_external_input(self, txi: TxInput) -> None: raise wire.DataError("Invalid external input") async def process_original_input(self, txi: TxInput, script_pubkey: bytes) -> None: - assert txi.orig_hash is not None - assert txi.orig_index is not None + if txi.orig_hash is None: + raise wire.DataError("Missing original transaction hash") + if txi.orig_index is None: + raise wire.DataError("Missing original transaction index") for orig in self.orig_txs: if orig.orig_hash == txi.orig_hash: @@ -422,8 +426,10 @@ async def fetch_removed_original_outputs( async def get_original_output( self, txo: TxOutput, script_pubkey: bytes ) -> TxOutput: - assert txo.orig_hash is not None - assert txo.orig_index is not None + if txo.orig_hash is None: + raise wire.DataError("Missing original transaction hash") + if txo.orig_index is None: + raise wire.DataError("Missing original transaction index") for orig in self.orig_txs: if orig.orig_hash == txo.orig_hash: @@ -710,7 +716,8 @@ async def get_legacy_tx_digest( multisig.multisig_pubkey_index(txi.multisig, key_sign_pub) if txi.script_type == InputScriptType.SPENDMULTISIG: - assert txi.multisig is not None # checked in sanitize_tx_input + if txi.multisig is None: + raise wire.DataError("Missing multisig data") script_pubkey = scripts.output_script_multisig( multisig.multisig_get_pubkeys(txi.multisig), txi.multisig.m, @@ -755,7 +762,8 @@ async def sign_nonsegwit_input(self, i: int) -> None: raise wire.ProcessError("Transaction has changed during signing") tx_digest, txi, node = await self.get_legacy_tx_digest(i, self.tx_info) - assert node is not None + if node is None: + raise RuntimeError("Signing node missing") # compute the signature from the tx digest signature = ecdsa_sign(node, tx_digest) @@ -814,7 +822,8 @@ async def get_prevtx_output( script_pubkey = txo_bin.script_pubkey self.check_prevtx_output(txo_bin) - assert script_pubkey is not None # prev_index < tx.outputs_count + if script_pubkey is None: + raise wire.DataError("Missing previous output script") await self.write_prev_tx_footer(txh, tx, prev_hash) @@ -904,7 +913,8 @@ async def write_prev_tx_footer( def set_serialized_signature(self, index: int, signature: bytes) -> None: # Only one signature per TxRequest can be serialized. - assert self.tx_req.serialized is not None + if self.tx_req.serialized is None: + raise RuntimeError("Tx request serialized data missing") ensure(self.tx_req.serialized.signature is None) self.tx_req.serialized.signature_index = index @@ -917,7 +927,8 @@ def input_derive_script( self, txi: TxInput, node: bip32.HDNode | None = None ) -> bytes: if input_is_external(txi): - assert txi.script_pubkey is not None # checked in sanitize_tx_input + if txi.script_pubkey is None: + raise wire.DataError("Missing script pubkey") return txi.script_pubkey if node is None: @@ -928,7 +939,8 @@ def input_derive_script( def output_derive_script(self, txo: TxOutput) -> bytes: if txo.script_type == OutputScriptType.PAYTOOPRETURN: - assert txo.op_return_data is not None # checked in sanitize_tx_output + if txo.op_return_data is None: + raise wire.DataError("Missing OP_RETURN data") return scripts.output_script_paytoopreturn(txo.op_return_data) if txo.address_n: @@ -944,6 +956,7 @@ def output_derive_script(self, txo: TxOutput) -> bytes: input_script_type, self.coin, node, txo.multisig ) - assert txo.address is not None # checked in sanitize_tx_output + if txo.address is None: + raise wire.DataError("Missing output address") return scripts.output_derive_script(txo.address, self.coin) diff --git a/core/src/apps/bitcoin/sign_tx/bitcoinlike.py b/core/src/apps/bitcoin/sign_tx/bitcoinlike.py index dfa347301f..005708f6d2 100644 --- a/core/src/apps/bitcoin/sign_tx/bitcoinlike.py +++ b/core/src/apps/bitcoin/sign_tx/bitcoinlike.py @@ -77,7 +77,8 @@ def write_tx_header( ) -> None: writers.write_uint32(w, tx.version) # nVersion if self.coin.timestamp: - assert tx.timestamp is not None # checked in sanitize_* + if tx.timestamp is None: + raise wire.DataError("Missing timestamp") writers.write_uint32(w, tx.timestamp) if witness_marker: write_compact_size(w, 0x00) # segwit witness marker diff --git a/core/src/apps/bitcoin/sign_tx/decred.py b/core/src/apps/bitcoin/sign_tx/decred.py index a291a99f47..75c245f872 100644 --- a/core/src/apps/bitcoin/sign_tx/decred.py +++ b/core/src/apps/bitcoin/sign_tx/decred.py @@ -239,7 +239,8 @@ async def step4_serialize_inputs(self) -> None: h_witness, ecdsa_hash_pubkey(key_sign_pub, self.coin) ) elif txi_sign.script_type == InputScriptType.SPENDMULTISIG: - assert txi_sign.multisig is not None + if txi_sign.multisig is None: + raise wire.DataError("Missing multisig data") scripts_decred.write_output_script_multisig( h_witness, multisig.multisig_get_pubkeys(txi_sign.multisig), @@ -328,7 +329,8 @@ def process_sstx_commitment_owned(self, txo: TxOutput) -> bytearray: return scripts_decred.output_script_paytoopreturn(op_return_data) async def approve_staking_ticket(self) -> None: - assert isinstance(self.approver, DecredApprover) + if not isinstance(self.approver, DecredApprover): + raise RuntimeError("Invalid Decred approver") if self.tx_info.tx.outputs_count != 3: raise wire.DataError("Ticket has wrong number of outputs.") @@ -389,7 +391,8 @@ def write_tx_header( writers.write_uint32(w, version) def write_tx_footer(self, w: writers.Writer, tx: SignTx | PrevTx) -> None: - assert tx.expiry is not None # checked in sanitize_* + if tx.expiry is None: + raise wire.DataError("Missing expiry") writers.write_uint32(w, tx.lock_time) writers.write_uint32(w, tx.expiry) diff --git a/core/src/apps/bitcoin/sign_tx/helpers.py b/core/src/apps/bitcoin/sign_tx/helpers.py index 078f0c19b5..473fc0fd6a 100644 --- a/core/src/apps/bitcoin/sign_tx/helpers.py +++ b/core/src/apps/bitcoin/sign_tx/helpers.py @@ -273,10 +273,22 @@ def confirm_nondefault_locktime(lock_time: int, lock_time_disabled: bool) -> Awa return (yield UiConfirmNonDefaultLocktime(lock_time, lock_time_disabled)) +def _require_details(tx_req: TxRequest): + if tx_req.details is None: + raise RuntimeError("Tx request details missing") + return tx_req.details + + +def _require_serialized(tx_req: TxRequest): + if tx_req.serialized is None: + raise RuntimeError("Tx request serialized data missing") + return tx_req.serialized + + def request_tx_meta(tx_req: TxRequest, coin: CoinInfo, tx_hash: bytes | None = None) -> Awaitable[PrevTx]: # type: ignore [awaitable-is-generator] - assert tx_req.details is not None + details = _require_details(tx_req) tx_req.request_type = RequestType.TXMETA - tx_req.details.tx_hash = tx_hash + details.tx_hash = tx_hash ack = yield TxAckPrevMeta, tx_req _clear_tx_request(tx_req) return sanitize_tx_meta(ack.tx, coin) @@ -285,57 +297,57 @@ def request_tx_meta(tx_req: TxRequest, coin: CoinInfo, tx_hash: bytes | None = N def request_tx_extra_data( tx_req: TxRequest, offset: int, size: int, tx_hash: bytes | None = None ) -> Awaitable[bytearray]: # type: ignore [awaitable-is-generator] - assert tx_req.details is not None + details = _require_details(tx_req) tx_req.request_type = RequestType.TXEXTRADATA - tx_req.details.extra_data_offset = offset - tx_req.details.extra_data_len = size - tx_req.details.tx_hash = tx_hash + details.extra_data_offset = offset + details.extra_data_len = size + details.tx_hash = tx_hash ack = yield TxAckPrevExtraData, tx_req _clear_tx_request(tx_req) return ack.tx.extra_data_chunk def request_tx_input(tx_req: TxRequest, i: int, coin: CoinInfo, tx_hash: bytes | None = None) -> Awaitable[TxInput]: # type: ignore [awaitable-is-generator] - assert tx_req.details is not None + details = _require_details(tx_req) if tx_hash: tx_req.request_type = RequestType.TXORIGINPUT - tx_req.details.tx_hash = tx_hash + details.tx_hash = tx_hash else: tx_req.request_type = RequestType.TXINPUT - tx_req.details.request_index = i + details.request_index = i ack = yield TxAckInput, tx_req _clear_tx_request(tx_req) return sanitize_tx_input(ack.tx.input, coin) def request_tx_prev_input(tx_req: TxRequest, i: int, coin: CoinInfo, tx_hash: bytes | None = None) -> Awaitable[PrevInput]: # type: ignore [awaitable-is-generator] - assert tx_req.details is not None + details = _require_details(tx_req) tx_req.request_type = RequestType.TXINPUT - tx_req.details.request_index = i - tx_req.details.tx_hash = tx_hash + details.request_index = i + details.tx_hash = tx_hash ack = yield TxAckPrevInput, tx_req _clear_tx_request(tx_req) return sanitize_tx_prev_input(ack.tx.input, coin) def request_tx_output(tx_req: TxRequest, i: int, coin: CoinInfo, tx_hash: bytes | None = None) -> Awaitable[TxOutput]: # type: ignore [awaitable-is-generator] - assert tx_req.details is not None + details = _require_details(tx_req) if tx_hash: tx_req.request_type = RequestType.TXORIGOUTPUT - tx_req.details.tx_hash = tx_hash + details.tx_hash = tx_hash else: tx_req.request_type = RequestType.TXOUTPUT - tx_req.details.request_index = i + details.request_index = i ack = yield TxAckOutput, tx_req _clear_tx_request(tx_req) return sanitize_tx_output(ack.tx.output, coin) def request_tx_prev_output(tx_req: TxRequest, i: int, coin: CoinInfo, tx_hash: bytes | None = None) -> Awaitable[PrevOutput]: # type: ignore [awaitable-is-generator] - assert tx_req.details is not None + details = _require_details(tx_req) tx_req.request_type = RequestType.TXOUTPUT - tx_req.details.request_index = i - tx_req.details.tx_hash = tx_hash + details.request_index = i + details.tx_hash = tx_hash ack = yield TxAckPrevOutput, tx_req _clear_tx_request(tx_req) # return sanitize_tx_prev_output(ack.tx, coin) # no sanitize is required @@ -343,9 +355,9 @@ def request_tx_prev_output(tx_req: TxRequest, i: int, coin: CoinInfo, tx_hash: b def request_payment_req(tx_req: TxRequest, i: int) -> Awaitable[TxAckPaymentRequest]: # type: ignore [awaitable-is-generator] - assert tx_req.details is not None + details = _require_details(tx_req) tx_req.request_type = RequestType.TXPAYMENTREQ - tx_req.details.request_index = i + details.request_index = i ack = yield TxAckPaymentRequest, tx_req _clear_tx_request(tx_req) return sanitize_payment_req(ack) @@ -358,19 +370,20 @@ def request_tx_finish(tx_req: TxRequest) -> Awaitable[None]: # type: ignore [aw def _clear_tx_request(tx_req: TxRequest) -> None: - assert tx_req.details is not None - assert tx_req.serialized is not None - assert tx_req.serialized.serialized_tx is not None + details = _require_details(tx_req) + serialized = _require_serialized(tx_req) + if serialized.serialized_tx is None: + raise RuntimeError("Serialized transaction missing") tx_req.request_type = None - tx_req.details.request_index = None - tx_req.details.tx_hash = None - tx_req.details.extra_data_len = None - tx_req.details.extra_data_offset = None - tx_req.serialized.signature = None - tx_req.serialized.signature_index = None + details.request_index = None + details.tx_hash = None + details.extra_data_len = None + details.extra_data_offset = None + serialized.signature = None + serialized.signature_index = None # typechecker thinks serialized_tx is `bytes`, which is immutable # we know that it is `bytearray` in reality - tx_req.serialized.serialized_tx[:] = bytes() # type: ignore ["__setitem__" method not defined on type "bytes"] + serialized.serialized_tx[:] = bytes() # type: ignore ["__setitem__" method not defined on type "bytes"] # Data sanitizers diff --git a/core/src/apps/bitcoin/sign_tx/layout.py b/core/src/apps/bitcoin/sign_tx/layout.py index 97cd71a159..e2e859b78e 100644 --- a/core/src/apps/bitcoin/sign_tx/layout.py +++ b/core/src/apps/bitcoin/sign_tx/layout.py @@ -48,7 +48,8 @@ async def confirm_output( ) -> None: if output.script_type == OutputScriptType.PAYTOOPRETURN: data = output.op_return_data - assert data is not None + if data is None: + raise wire.DataError("Missing OP_RETURN data") if omni.is_valid(data): # OMNI transaction layout = layouts.confirm_metadata( @@ -62,7 +63,8 @@ async def confirm_output( # generic OP_RETURN layout = confirm_op_return(ctx, data, output.amount, coin) else: - assert output.address is not None + if output.address is None: + raise wire.DataError("Missing output address") address_short = addresses.address_short(coin, output.address) layout = layouts.confirm_output( @@ -77,7 +79,8 @@ async def confirm_output( async def confirm_decred_sstx_submission( ctx: wire.Context, output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit ) -> None: - assert output.address is not None + if output.address is None: + raise wire.DataError("Missing output address") address_short = addresses.address_short(coin, output.address) await altcoin.confirm_decred_sstx_submission( @@ -102,7 +105,8 @@ async def confirm_payment_request( else: raise wire.DataError("Unrecognized memo type in payment request memo.") - assert msg.amount is not None + if msg.amount is None: + raise wire.DataError("Missing payment request amount") return await layouts.confirm_payment_request( ctx, @@ -130,7 +134,8 @@ async def confirm_modify_output( coin: CoinInfo, amount_unit: AmountUnit, ) -> None: - assert txo.address is not None + if txo.address is None: + raise wire.DataError("Missing output address") address_short = addresses.address_short(coin, txo.address) amount_change = txo.amount - orig_txo.amount await layouts.confirm_modify_output( diff --git a/core/src/apps/bitcoin/sign_tx/payment_request.py b/core/src/apps/bitcoin/sign_tx/payment_request.py index 4935d87634..6ab0073537 100644 --- a/core/src/apps/bitcoin/sign_tx/payment_request.py +++ b/core/src/apps/bitcoin/sign_tx/payment_request.py @@ -86,7 +86,8 @@ def verify(self) -> None: def _add_output(self, txo: TxOutput) -> None: # For change outputs txo.address filled in by output_derive_script(). - assert txo.address is not None + if txo.address is None: + raise wire.DataError("Missing output address") writers.write_uint64(self.h_outputs, txo.amount) writers.write_bytes_prefixed(self.h_outputs, txo.address.encode()) diff --git a/core/src/apps/bitcoin/sign_tx/tx_weight.py b/core/src/apps/bitcoin/sign_tx/tx_weight.py index bd4df97f82..fd7ddfa2a7 100644 --- a/core/src/apps/bitcoin/sign_tx/tx_weight.py +++ b/core/src/apps/bitcoin/sign_tx/tx_weight.py @@ -52,7 +52,8 @@ def __init__(self) -> None: def input_script_size(cls, i: TxInput) -> int: script_type = i.script_type if common.input_is_external_unverified(i): - assert i.script_pubkey is not None # checked in sanitize_tx_input + if i.script_pubkey is None: + raise wire.DataError("Missing script pubkey") # Guess the script type from the scriptPubKey. if i.script_pubkey[0] == 0x76: # OP_DUP (P2PKH) diff --git a/core/src/apps/bitcoin/sign_tx/zcash_v4.py b/core/src/apps/bitcoin/sign_tx/zcash_v4.py index be8d450f8e..d75ce50e9b 100644 --- a/core/src/apps/bitcoin/sign_tx/zcash_v4.py +++ b/core/src/apps/bitcoin/sign_tx/zcash_v4.py @@ -65,8 +65,10 @@ def hash143( ) ) - assert tx.version_group_id is not None - assert tx.expiry is not None + if tx.version_group_id is None: + raise wire.DataError("Version group ID is missing") + if tx.expiry is None: + raise wire.DataError("Expiry is missing") zero_hash = b"\x00" * TX_HASH_SIZE # 1. nVersion | fOverwintered @@ -186,7 +188,8 @@ def write_tx_header( write_uint32(w, tx.version_group_id) # nVersionGroupId def write_tx_footer(self, w: Writer, tx: SignTx | PrevTx) -> None: - assert tx.expiry is not None # checked in sanitize_* + if tx.expiry is None: + raise wire.DataError("Expiry is missing") write_uint32(w, tx.lock_time) if tx.version >= 3: write_uint32(w, tx.expiry) # expiryHeight diff --git a/core/src/apps/cardano/auxiliary_data.py b/core/src/apps/cardano/auxiliary_data.py index 2155490cca..0a29ab9699 100644 --- a/core/src/apps/cardano/auxiliary_data.py +++ b/core/src/apps/cardano/auxiliary_data.py @@ -169,7 +169,8 @@ async def _show_cvote_registration( ) else: address_parameters = parameters.payment_address_parameters - assert address_parameters # _validate_cvote_registration_parameters + if address_parameters is None: + raise wire.ProcessError("Invalid auxiliary data") show_both_credentials = should_show_credentials(address_parameters) show_payment_warning = _should_show_payment_warning( address_parameters.address_type @@ -226,7 +227,8 @@ def get_hash_and_supplement( ) return auxiliary_data_hash, auxiliary_data_supplement else: - assert auxiliary_data.hash is not None # validate_auxiliary_data + if auxiliary_data.hash is None: + raise wire.ProcessError("Invalid auxiliary data") return auxiliary_data.hash, messages.CardanoTxAuxiliaryDataSupplement( type=CardanoTxAuxiliaryDataSupplementType.NONE ) @@ -280,7 +282,8 @@ def _get_signed_cvote_registration_payload( payment_address = addresses.get_bytes_unsafe(parameters.payment_address) else: address_parameters = parameters.payment_address_parameters - assert address_parameters # _validate_cvote_registration_parameters + if address_parameters is None: + raise wire.ProcessError("Invalid auxiliary data") payment_address = addresses.derive_bytes( keychain, address_parameters, diff --git a/core/src/apps/cardano/certificates.py b/core/src/apps/cardano/certificates.py index 762dea4329..9aaf8aa86c 100644 --- a/core/src/apps/cardano/certificates.py +++ b/core/src/apps/cardano/certificates.py @@ -166,7 +166,8 @@ def cborize( certificate.pool, ) elif cert_type == CardanoCertificateType.VOTE_DELEGATION: - assert certificate.drep is not None + if certificate.drep is None: + raise ProcessError("Invalid certificate") return ( cert_type, cborize_stake_credential( @@ -202,10 +203,12 @@ def cborize_pool_registration_init( ) -> CborSequence: from apps.common import cbor - assert certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION + if certificate.type != CardanoCertificateType.STAKE_POOL_REGISTRATION: + raise ProcessError("Invalid certificate") pool_parameters = certificate.pool_parameters - assert pool_parameters is not None + if pool_parameters is None: + raise ProcessError("Invalid certificate") return ( certificate.type, @@ -325,10 +328,12 @@ def validate_pool_relay(pool_relay: messages.CardanoPoolRelayParameters) -> None def cborize_drep(drep: messages.CardanoDRep) -> tuple[int, bytes] | tuple[int]: if drep.type == CardanoDRepType.KEY_HASH: - assert drep.key_hash is not None + if drep.key_hash is None: + raise ProcessError("Invalid certificate") return 0, drep.key_hash elif drep.type == CardanoDRepType.SCRIPT_HASH: - assert drep.script_hash is not None + if drep.script_hash is None: + raise ProcessError("Invalid certificate") return 1, drep.script_hash elif drep.type == CardanoDRepType.ABSTAIN: return (2,) @@ -354,7 +359,8 @@ def _cborize_ipv6_address(ipv6_address: bytes | None) -> bytes | None: return None # ipv6 addresses are serialized to CBOR as uint_32[4] little endian - assert len(ipv6_address) == _IPV6_ADDRESS_SIZE + if len(ipv6_address) != _IPV6_ADDRESS_SIZE: + raise ValueError("Invalid IPv6 address length") result = b"" for i in range(0, 4): diff --git a/core/src/apps/cardano/helpers/account_path_check.py b/core/src/apps/cardano/helpers/account_path_check.py index 4b85ad2367..900394fb96 100644 --- a/core/src/apps/cardano/helpers/account_path_check.py +++ b/core/src/apps/cardano/helpers/account_path_check.py @@ -55,7 +55,8 @@ def _is_byron_and_shelley_equivalent(self, account_path: list[int]) -> bool: self_account_path = self.account_path # local_cache_attribute - assert isinstance(self_account_path, list) + if not isinstance(self_account_path, list): + raise RuntimeError("Cardano account path missing") is_control_path_byron_or_shelley = seed.is_byron_path( self_account_path ) or seed.is_shelley_path(self_account_path) diff --git a/core/src/apps/cardano/helpers/hash_builder_collection.py b/core/src/apps/cardano/helpers/hash_builder_collection.py index 22b6d238e1..3628691578 100644 --- a/core/src/apps/cardano/helpers/hash_builder_collection.py +++ b/core/src/apps/cardano/helpers/hash_builder_collection.py @@ -26,6 +26,11 @@ def __init__(self, size: int) -> None: self.parent: "HashBuilderCollection | None" = None self.has_unfinished_child = False + def _require_hash_fn(self): + if self.hash_fn is None: + raise RuntimeError("Hash builder is not started") + return self.hash_fn + def start(self, hash_fn: HashContext) -> "HashBuilderCollection": self.hash_fn = hash_fn self.hash_fn.update(self._header_bytes()) @@ -33,28 +38,28 @@ def start(self, hash_fn: HashContext) -> "HashBuilderCollection": def _insert_child(self, child: "HashBuilderCollection") -> None: child.parent = self - assert self.hash_fn is not None - child.start(self.hash_fn) + child.start(self._require_hash_fn()) self.has_unfinished_child = True def _do_enter_item(self) -> None: - assert self.hash_fn is not None - assert self.remaining > 0 + self._require_hash_fn() + if self.remaining <= 0: + raise RuntimeError("Too many items added") if self.has_unfinished_child: raise RuntimeError # can't add item until child is finished self.remaining -= 1 def _hash_item(self, item: Any) -> bytes: - assert self.hash_fn is not None + hash_fn = self._require_hash_fn() encoded_item = cbor.encode(item) - self.hash_fn.update(encoded_item) + hash_fn.update(encoded_item) return encoded_item def _hash_item_streamed(self, item: Any) -> None: - assert self.hash_fn is not None + hash_fn = self._require_hash_fn() for chunk in cbor.encode_streamed(item): - self.hash_fn.update(chunk) + hash_fn.update(chunk) def _header_bytes(self) -> bytes: raise NotImplementedError @@ -68,7 +73,7 @@ def finish(self) -> None: self.parent = None def __enter__(self) -> "HashBuilderCollection": - assert self.hash_fn is not None + self._require_hash_fn() return self def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: @@ -116,7 +121,8 @@ def add(self, key: K, value: V) -> V: self._do_enter_item() # enter key, this must not nest - assert not isinstance(key, HashBuilderCollection) + if isinstance(key, HashBuilderCollection): + raise RuntimeError("Hash builder key cannot be nested") encoded_key = self._hash_item(key) # check key ordering @@ -144,10 +150,11 @@ class HashBuilderEmbeddedCBOR(HashBuilderCollection): """ def add(self, chunk: bytes) -> None: - assert self.hash_fn is not None - assert self.remaining >= len(chunk) + hash_fn = self._require_hash_fn() + if self.remaining < len(chunk): + raise RuntimeError("Embedded CBOR data exceeds declared size") self.remaining -= len(chunk) - self.hash_fn.update(chunk) + hash_fn.update(chunk) def _header_bytes(self) -> bytes: return cbor.create_embedded_cbor_bytes_header(self.size) diff --git a/core/src/apps/cardano/layout.py b/core/src/apps/cardano/layout.py index 50d0c61f2c..27e94f1616 100644 --- a/core/src/apps/cardano/layout.py +++ b/core/src/apps/cardano/layout.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING -from trezor import messages +from trezor import messages, wire from trezor.enums import ( ButtonRequestType, CardanoAddressType, @@ -32,7 +32,6 @@ from .helpers.credential import Credential from .seed import Keychain - from trezor import wire ADDRESS_TYPE_NAMES = { CardanoAddressType.BYRON: "Legacy", @@ -114,13 +113,15 @@ async def show_native_script( append = props.append # local_cache_attribute if script_type == CNST.PUB_KEY: - assert key_hash is not None or key_path # validate_script + if key_hash is None and not key_path: + raise wire.DataError("Invalid native script") if key_hash: append((None, bech32.encode(bech32.HRP_SHARED_KEY_HASH, key_hash))) elif key_path: append((address_n_to_str(key_path), None)) elif script_type == CNST.N_OF_K: - assert script.required_signatures_count is not None # validate_script + if script.required_signatures_count is None: + raise wire.DataError("Invalid native script") append( ( f"Requires {script.required_signatures_count} out of {len(scripts)} signatures.", @@ -128,10 +129,12 @@ async def show_native_script( ) ) elif script_type == CNST.INVALID_BEFORE: - assert script.invalid_before is not None # validate_script + if script.invalid_before is None: + raise wire.DataError("Invalid native script") append((str(script.invalid_before), None)) elif script_type == CNST.INVALID_HEREAFTER: - assert script.invalid_hereafter is not None # validate_script + if script.invalid_hereafter is None: + raise wire.DataError("Invalid native script") append((str(script.invalid_hereafter), None)) if script_type in ( @@ -139,7 +142,8 @@ async def show_native_script( CNST.ANY, CNST.N_OF_K, ): - assert scripts # validate_script + if not scripts: + raise wire.DataError("Invalid native script") append((f"Contains {len(scripts)} nested scripts.", None)) await confirm_properties( @@ -160,10 +164,11 @@ async def show_script_hash( display_format: CardanoNativeScriptHashDisplayFormat, ) -> None: - assert display_format in ( + if display_format not in ( CardanoNativeScriptHashDisplayFormat.BECH32, CardanoNativeScriptHashDisplayFormat.POLICY_ID, - ) + ): + raise wire.DataError("Invalid native script hash display format") if display_format == CardanoNativeScriptHashDisplayFormat.BECH32: await confirm_properties( @@ -240,7 +245,8 @@ async def confirm_sending( async def confirm_sending_token( ctx: wire.Context, policy_id: bytes, token: messages.CardanoToken ) -> None: - assert token.amount is not None # _validate_token + if token.amount is None: + raise wire.DataError("Invalid token") await confirm_properties( ctx, @@ -576,7 +582,8 @@ async def confirm_certificate( ) -> None: # stake pool registration requires custom confirmation logic not covered # in this call - assert certificate.type != CardanoCertificateType.STAKE_POOL_REGISTRATION + if certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION: + raise wire.DataError("Invalid certificate") if certificate.type == CardanoCertificateType.STAKE_REGISTRATION: transaction_type_value = _(i18n_keys.LIST_VALUE__STAKE_KEY_REGISTRATION) @@ -595,7 +602,8 @@ async def confirm_certificate( ] if certificate.type == CardanoCertificateType.STAKE_DELEGATION: - assert certificate.pool is not None # validate_certificate + if certificate.pool is None: + raise wire.DataError("Invalid certificate") props.append( ( _(i18n_keys.LIST_KEY__TO_POOL__COLON), @@ -606,7 +614,8 @@ async def confirm_certificate( CardanoCertificateType.STAKE_REGISTRATION_CONWAY, CardanoCertificateType.STAKE_DEREGISTRATION_CONWAY, ): - assert certificate.deposit is not None # validate_certificate + if certificate.deposit is None: + raise wire.DataError("Invalid certificate") props.append( ( _(i18n_keys.LIST_VALUE__DEPOSIT) + ":", @@ -615,7 +624,8 @@ async def confirm_certificate( ) elif certificate.type == CardanoCertificateType.VOTE_DELEGATION: - assert certificate.drep is not None # validate_certificate + if certificate.drep is None: + raise wire.DataError("Invalid certificate") props.append(_format_drep(certificate.drep)) await confirm_properties( @@ -683,7 +693,8 @@ async def confirm_stake_pool_owner( ) ) else: - assert owner.staking_key_hash is not None # validate_pool_owners + if owner.staking_key_hash is None: + raise wire.DataError("Invalid pool owner") props.append( ( "Pool owner:", @@ -818,13 +829,15 @@ def _format_stake_credential( def _format_drep(drep: messages.CardanoDRep) -> tuple[str, str]: if drep.type == CardanoDRepType.KEY_HASH: - assert drep.key_hash is not None # validate_drep + if drep.key_hash is None: + raise wire.DataError("Invalid DRep") return ( "Delegating to key hash:", bech32.encode(bech32.HRP_DREP_KEY_HASH, drep.key_hash), ) elif drep.type == CardanoDRepType.SCRIPT_HASH: - assert drep.script_hash is not None # validate_drep + if drep.script_hash is None: + raise wire.DataError("Invalid DRep") return ( "Delegating to script:", bech32.encode(bech32.HRP_DREP_SCRIPT_HASH, drep.script_hash), @@ -930,7 +943,8 @@ async def show_auxiliary_data_hash( async def confirm_token_minting( ctx: wire.Context, policy_id: bytes, token: messages.CardanoToken ) -> None: - assert token.mint_amount is not None # _validate_token + if token.mint_amount is None: + raise wire.DataError("Invalid token") await confirm_properties( ctx, "confirm_mint", @@ -1012,9 +1026,8 @@ async def confirm_reference_input( async def confirm_required_signer( ctx: wire.Context, required_signer: messages.CardanoTxRequiredSigner ) -> None: - assert ( - required_signer.key_hash is not None or required_signer.key_path - ) # _validate_required_signer + if required_signer.key_hash is None and not required_signer.key_path: + raise wire.DataError("Invalid required signer") formatted_signer = ( bech32.encode(bech32.HRP_REQUIRED_SIGNER_KEY_HASH, required_signer.key_hash) if required_signer.key_hash is not None diff --git a/core/src/apps/cardano/seed.py b/core/src/apps/cardano/seed.py index d8c13d2f46..fee9aa0160 100644 --- a/core/src/apps/cardano/seed.py +++ b/core/src/apps/cardano/seed.py @@ -85,11 +85,12 @@ def derive(self, node_path: Bip32Path) -> bip32.HDNode: path_root = self._get_path_root(node_path) # this is true now, so for simplicity we don't branch on path type - assert ( + if not ( len(BYRON_ROOT) == len(SHELLEY_ROOT) and len(MULTISIG_ROOT) == len(SHELLEY_ROOT) and len(MINTING_ROOT) == len(SHELLEY_ROOT) - ) + ): + raise RuntimeError("Cardano root path length mismatch") suffix = node_path[len(SHELLEY_ROOT) :] # derive child node from the root @@ -117,21 +118,24 @@ def is_minting_path(path: Bip32Path) -> bool: def derive_and_store_secrets(passphrase: str) -> None: - assert device.is_initialized() + if not device.is_initialized(): + raise wire.NotInitialized("Device is not initialized") if not mnemonic.is_bip39(): # nothing to do for SLIP-39, where we can derive the root from the main seed return if not utils.USE_THD89: - assert cache.get(cache.APP_COMMON_DERIVE_CARDANO) + if not cache.get(cache.APP_COMMON_DERIVE_CARDANO): + raise RuntimeError("Cardano derivation is not enabled") icarus_secret = mnemonic.derive_cardano_icarus( passphrase, trezor_derivation=False ) words = mnemonic.get_secret() - assert words is not None, "Mnemonic is not set" + if words is None: + raise RuntimeError("Mnemonic is not set") # count ASCII spaces, add 1 to get number of words words_count = sum(c == 0x20 for c in words) + 1 @@ -156,7 +160,8 @@ async def _get_secret(ctx: wire.Context, cache_entry: int) -> bytes: if secret is None: await derive_and_store_roots(ctx) secret = cache.get(cache_entry) - assert secret is not None + if secret is None: + raise RuntimeError("Cardano secret missing") return secret diff --git a/core/src/apps/cardano/sign_tx/signer.py b/core/src/apps/cardano/sign_tx/signer.py index 8363528703..cd20d55f8b 100644 --- a/core/src/apps/cardano/sign_tx/signer.py +++ b/core/src/apps/cardano/sign_tx/signer.py @@ -374,7 +374,8 @@ async def _show_output_init(self, output: CardanoTxOutput) -> None: ) await self._show_output_credentials(output.address_parameters) else: - assert output.address is not None # _validate_output + if output.address is None: + raise ProcessError("Invalid output") address = output.address await layout.confirm_sending( @@ -521,7 +522,8 @@ async def _process_output_value( should_show_tokens: bool, ) -> None: """Should be used only when the output contains tokens.""" - assert output.asset_groups_count > 0 + if output.asset_groups_count <= 0: + raise ProcessError("Invalid token bundle in output") output_value_list.append(output.amount) @@ -595,7 +597,8 @@ async def _process_tokens( if should_show_tokens: await layout.confirm_sending_token(self.ctx, policy_id, token) - assert token.amount is not None # _validate_token + if token.amount is None: + raise ProcessError("Invalid token bundle in output") tokens_dict.add(token.asset_name_bytes, token.amount) def _validate_token( @@ -625,7 +628,8 @@ async def _process_inline_datum( inline_datum_size: int, should_show: bool, ) -> None: - assert inline_datum_size > 0 + if inline_datum_size <= 0: + raise ProcessError("Invalid inline datum chunk") chunks_count = self._get_chunks_count(inline_datum_size) for chunk_number in range(chunks_count): @@ -652,7 +656,8 @@ async def _process_reference_script( reference_script_size: int, should_show: bool, ) -> None: - assert reference_script_size > 0 + if reference_script_size <= 0: + raise ProcessError("Invalid reference script chunk") chunks_count = self._get_chunks_count(reference_script_size) for chunk_number in range(chunks_count): @@ -685,7 +690,8 @@ async def _process_certificates(self, certificates_set: HashBuilderSet) -> None: if certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION: pool_parameters = certificate.pool_parameters - assert pool_parameters is not None # _validate_certificate + if pool_parameters is None: + raise ProcessError("Invalid certificate") pool_items_list: HashBuilderList = HashBuilderList( _POOL_REGISTRATION_CERTIFICATE_ITEMS_COUNT @@ -737,7 +743,8 @@ async def _show_certificate( ) if certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION: - assert certificate.pool_parameters is not None + if certificate.pool_parameters is None: + raise ProcessError("Invalid certificate") await layout.confirm_stake_pool_parameters( self.ctx, certificate.pool_parameters, self.msg.network_id ) @@ -899,13 +906,15 @@ async def _process_minting_tokens( self._validate_token(token, is_mint=True) await layout.confirm_token_minting(self.ctx, policy_id, token) - assert token.mint_amount is not None # _validate_token + if token.mint_amount is None: + raise ProcessError("Invalid mint token bundle") tokens.add(token.asset_name_bytes, token.mint_amount) # script data hash async def _process_script_data_hash(self) -> None: - assert self.msg.script_data_hash is not None + if self.msg.script_data_hash is None: + raise ProcessError("Invalid script data hash") self._validate_script_data_hash() await self._show_if_showing_details( layout.confirm_script_data_hash(self.ctx, self.msg.script_data_hash) @@ -915,7 +924,8 @@ async def _process_script_data_hash(self) -> None: def _validate_script_data_hash(self) -> None: from ..helpers import SCRIPT_DATA_HASH_SIZE - assert self.msg.script_data_hash is not None + if self.msg.script_data_hash is None: + raise ProcessError("Invalid script data hash") if len(self.msg.script_data_hash) != SCRIPT_DATA_HASH_SIZE: raise ProcessError("Invalid script data hash") @@ -1058,7 +1068,8 @@ async def _show_collateral_return_init(self, output: CardanoTxOutput) -> None: output.address_parameters, ) else: - assert output.address is not None # _validate_output + if output.address is None: + raise ProcessError("Invalid collateral return") address = output.address await layout.confirm_sending( @@ -1172,13 +1183,15 @@ def _get_output_address(self, output: CardanoTxOutput) -> bytes: self.msg.network_id, ) else: - assert output.address is not None # _validate_output + if output.address is None: + raise ProcessError("Invalid output") return addresses.get_bytes_unsafe(output.address) def _get_output_address_type(self, output: CardanoTxOutput) -> CardanoAddressType: if output.address_parameters: return output.address_parameters.address_type - assert output.address is not None # _validate_output + if output.address is None: + raise ProcessError("Invalid output") return addresses.get_type(addresses.get_bytes_unsafe(output.address)) def _derive_withdrawal_address_bytes( @@ -1204,7 +1217,8 @@ def _derive_withdrawal_address_bytes( ) def _get_chunks_count(self, data_size: int) -> int: - assert data_size > 0 + if data_size <= 0: + raise ProcessError("Invalid tx signing request") return (data_size - 1) // _MAX_CHUNK_SIZE + 1 def _validate_chunk( diff --git a/core/src/apps/common/authorization.py b/core/src/apps/common/authorization.py index d03678868e..5e2a921f55 100644 --- a/core/src/apps/common/authorization.py +++ b/core/src/apps/common/authorization.py @@ -4,7 +4,6 @@ from trezor import protobuf, utils from trezor.crypto import se_thd89 from trezor.enums import MessageType -from trezor.utils import ensure WIRE_TYPES: dict[int, tuple[int, ...]] = { MessageType.AuthorizeCoinJoin: (MessageType.SignTx, MessageType.GetOwnershipProof), @@ -26,14 +25,15 @@ def set(auth_message: protobuf.MessageType) -> None: # only wire-level messages can be stored as authorization # (because only wire-level messages have wire_type, which we use as identifier) - ensure(auth_message.MESSAGE_WIRE_TYPE is not None) - assert auth_message.MESSAGE_WIRE_TYPE is not None # so that typechecker knows too + wire_type = auth_message.MESSAGE_WIRE_TYPE + if wire_type is None: + raise AssertionError if utils.USE_THD89: - se_thd89.authorization_set(auth_message.MESSAGE_WIRE_TYPE, buffer) + se_thd89.authorization_set(wire_type, buffer) else: storage.cache.set( storage.cache.APP_COMMON_AUTHORIZATION_TYPE, - auth_message.MESSAGE_WIRE_TYPE.to_bytes(2, "big"), + wire_type.to_bytes(2, "big"), ) storage.cache.set(storage.cache.APP_COMMON_AUTHORIZATION_DATA, buffer) diff --git a/core/src/apps/common/request_pin.py b/core/src/apps/common/request_pin.py index 02ebcbcd9e..20d49cd12b 100644 --- a/core/src/apps/common/request_pin.py +++ b/core/src/apps/common/request_pin.py @@ -293,7 +293,7 @@ async def error_pin_invalid(ctx: wire.GenericContext) -> NoReturn: red=True, exc=wire.PinInvalid, ) - assert False + raise wire.PinInvalid async def error_pin_used(ctx: wire.Context) -> NoReturn: @@ -307,7 +307,7 @@ async def error_pin_used(ctx: wire.Context) -> NoReturn: red=True, exc=wire.PinInvalid, ) - assert False + raise wire.PinInvalid async def passphrase_pin_used(ctx: wire.Context): @@ -400,4 +400,4 @@ async def error_pin_matches_wipe_code(ctx: wire.Context) -> NoReturn: red=True, exc=wire.PinInvalid, ) - assert False + raise wire.PinInvalid diff --git a/core/src/apps/common/seed.py b/core/src/apps/common/seed.py index 44233d5be6..1df6783db5 100644 --- a/core/src/apps/common/seed.py +++ b/core/src/apps/common/seed.py @@ -27,7 +27,8 @@ def __init__(self, seed: bytes | None = None, data: bytes | None = None) -> None self.data = se_thd89.slip21_node() else: - assert seed is None or data is None, "Specify exactly one of: seed, data" + if seed is not None and data is not None: + raise ValueError("Specify exactly one of: seed, data") if data is not None: self.data = data elif seed is not None: @@ -129,7 +130,8 @@ async def get_seed(ctx: wire.Context) -> bytes: await derive_and_store_roots(ctx) common_seed = cache.get(cache.APP_COMMON_SEED) if not utils.USE_THD89: - assert common_seed is not None + if common_seed is None: + raise RuntimeError("Seed cache missing") return common_seed else: return b"" diff --git a/core/src/apps/conflux/helpers.py b/core/src/apps/conflux/helpers.py index ef1bdd855e..63480c08c1 100644 --- a/core/src/apps/conflux/helpers.py +++ b/core/src/apps/conflux/helpers.py @@ -54,12 +54,14 @@ def maybe_upper(i: int) -> str: def eth_address_to_cfx(address: str): - assert type(address) == str + if type(address) is not str: + raise TypeError("address must be str") return "0x1" + address.lower()[3:] def hex_address_bytes(hex_address: str): - assert type(hex_address) == str + if type(hex_address) is not str: + raise TypeError("hex_address must be str") return binascii.unhexlify(hex_address.lower().replace("0x", "")) @@ -82,7 +84,8 @@ def _poly_mod(v): :param v: bytes :return: int64 """ - assert type(v) == bytes or type(v) == bytearray + if type(v) not in (bytes, bytearray): + raise TypeError("v must be bytes or bytearray") c = 1 for d in v: c0 = c >> 35 @@ -174,9 +177,11 @@ def bytes_from_address(address: str) -> bytes: def decode_hex_address(base32_address): - assert type(base32_address) == str + if type(base32_address) is not str: + raise TypeError("base32_address must be str") parts = base32_address.split(":") - assert len(parts) >= 2, "invalid base32 address" + if len(parts) < 2: + raise ValueError("invalid base32 address") b32str = parts[-1] b32len = len(b32str) diff --git a/core/src/apps/conflux/sign_message_cip23.py b/core/src/apps/conflux/sign_message_cip23.py index ffc5fe6fff..217d85412f 100644 --- a/core/src/apps/conflux/sign_message_cip23.py +++ b/core/src/apps/conflux/sign_message_cip23.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING +from trezor import wire from trezor.crypto.curve import secp256k1 from trezor.crypto.hashlib import sha3_256 from trezor.lvglui.scrs import lv @@ -33,7 +34,8 @@ async def sign_message_cip23( await paths.validate_path(ctx, keychain, msg.address_n) node = keychain.derive(msg.address_n) - assert msg.domain_hash is not None, "domain_hash is required" + if msg.domain_hash is None: + raise wire.DataError("domain_hash is required") message_hash = msg.message_hash or b"" address = address_from_bytes(node.ethereum_pubkeyhash()) diff --git a/core/src/apps/cosmos/get_address.py b/core/src/apps/cosmos/get_address.py index 25806c7095..db9fa46f1d 100644 --- a/core/src/apps/cosmos/get_address.py +++ b/core/src/apps/cosmos/get_address.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING +from trezor import wire from trezor.crypto import bech32 from trezor.crypto.scripts import sha256_ripemd160 from trezor.lvglui.scrs import lv @@ -28,7 +29,8 @@ async def get_address( hrp = msg.hrp if msg.hrp is not None else DEFAULT_BECH32_HRP h = sha256_ripemd160(public_key).digest() convertedbits = bech32.convertbits(h, 8, 5) - assert convertedbits is not None, "Unsuccessful bech32.convertbits call" + if convertedbits is None: + raise wire.ProcessError("Unsuccessful bech32.convertbits call") address = bech32.bech32_encode(hrp, convertedbits, bech32.Encoding.BECH32) if msg.show_display: path = paths.address_n_to_str(msg.address_n) diff --git a/core/src/apps/cosmos/sign_tx.py b/core/src/apps/cosmos/sign_tx.py index b8a95bc7fe..bc4c82eec6 100644 --- a/core/src/apps/cosmos/sign_tx.py +++ b/core/src/apps/cosmos/sign_tx.py @@ -46,7 +46,8 @@ async def sign_tx( else: h = sha256_ripemd160(public_key).digest() convertedbits = bech32.convertbits(h, 8, 5) - assert convertedbits is not None, "Unsuccessful bech32.convertbits call" + if convertedbits is None: + raise RuntimeError("Unsuccessful bech32.convertbits call") signer = bech32.bech32_encode(hrp, convertedbits, bech32.Encoding.BECH32) _chain_name, primary_color, ctx.icon_path = retrieve_theme_by_hrp(hrp) ctx.primary_color = lv.color_hex(primary_color) diff --git a/core/src/apps/eos/actions/__init__.py b/core/src/apps/eos/actions/__init__.py index edfe792081..5a26885b6a 100644 --- a/core/src/apps/eos/actions/__init__.py +++ b/core/src/apps/eos/actions/__init__.py @@ -12,6 +12,12 @@ from trezor.utils import Writer +def _require_action_payload(payload): + if payload is None: + raise ValueError("Invalid action") + return payload + + async def process_action( ctx: wire.Context, sha: HashWriter, action: EosTxActionAck ) -> None: @@ -24,59 +30,59 @@ async def process_action( w = bytearray() if account == "eosio": if name == "buyram": - assert action.buy_ram is not None # check_action - await layout.confirm_action_buyram(ctx, action.buy_ram) - writers.write_action_buyram(w, action.buy_ram) + buy_ram = _require_action_payload(action.buy_ram) + await layout.confirm_action_buyram(ctx, buy_ram) + writers.write_action_buyram(w, buy_ram) elif name == "buyrambytes": - assert action.buy_ram_bytes is not None # check_action - await layout.confirm_action_buyrambytes(ctx, action.buy_ram_bytes) - writers.write_action_buyrambytes(w, action.buy_ram_bytes) + buy_ram_bytes = _require_action_payload(action.buy_ram_bytes) + await layout.confirm_action_buyrambytes(ctx, buy_ram_bytes) + writers.write_action_buyrambytes(w, buy_ram_bytes) elif name == "sellram": - assert action.sell_ram is not None # check_action - await layout.confirm_action_sellram(ctx, action.sell_ram) - writers.write_action_sellram(w, action.sell_ram) + sell_ram = _require_action_payload(action.sell_ram) + await layout.confirm_action_sellram(ctx, sell_ram) + writers.write_action_sellram(w, sell_ram) elif name == "delegatebw": - assert action.delegate is not None # check_action - await layout.confirm_action_delegate(ctx, action.delegate) - writers.write_action_delegate(w, action.delegate) + delegate = _require_action_payload(action.delegate) + await layout.confirm_action_delegate(ctx, delegate) + writers.write_action_delegate(w, delegate) elif name == "undelegatebw": - assert action.undelegate is not None # check_action - await layout.confirm_action_undelegate(ctx, action.undelegate) - writers.write_action_undelegate(w, action.undelegate) + undelegate = _require_action_payload(action.undelegate) + await layout.confirm_action_undelegate(ctx, undelegate) + writers.write_action_undelegate(w, undelegate) elif name == "refund": - assert action.refund is not None # check_action - await layout.confirm_action_refund(ctx, action.refund) - writers.write_action_refund(w, action.refund) + refund = _require_action_payload(action.refund) + await layout.confirm_action_refund(ctx, refund) + writers.write_action_refund(w, refund) elif name == "voteproducer": - assert action.vote_producer is not None # check_action - await layout.confirm_action_voteproducer(ctx, action.vote_producer) - writers.write_action_voteproducer(w, action.vote_producer) + vote_producer = _require_action_payload(action.vote_producer) + await layout.confirm_action_voteproducer(ctx, vote_producer) + writers.write_action_voteproducer(w, vote_producer) elif name == "updateauth": - assert action.update_auth is not None # check_action - await layout.confirm_action_updateauth(ctx, action.update_auth) - writers.write_action_updateauth(w, action.update_auth) + update_auth = _require_action_payload(action.update_auth) + await layout.confirm_action_updateauth(ctx, update_auth) + writers.write_action_updateauth(w, update_auth) elif name == "deleteauth": - assert action.delete_auth is not None # check_action - await layout.confirm_action_deleteauth(ctx, action.delete_auth) - writers.write_action_deleteauth(w, action.delete_auth) + delete_auth = _require_action_payload(action.delete_auth) + await layout.confirm_action_deleteauth(ctx, delete_auth) + writers.write_action_deleteauth(w, delete_auth) elif name == "linkauth": - assert action.link_auth is not None # check_action - await layout.confirm_action_linkauth(ctx, action.link_auth) - writers.write_action_linkauth(w, action.link_auth) + link_auth = _require_action_payload(action.link_auth) + await layout.confirm_action_linkauth(ctx, link_auth) + writers.write_action_linkauth(w, link_auth) elif name == "unlinkauth": - assert action.unlink_auth is not None # check_action - await layout.confirm_action_unlinkauth(ctx, action.unlink_auth) - writers.write_action_unlinkauth(w, action.unlink_auth) + unlink_auth = _require_action_payload(action.unlink_auth) + await layout.confirm_action_unlinkauth(ctx, unlink_auth) + writers.write_action_unlinkauth(w, unlink_auth) elif name == "newaccount": - assert action.new_account is not None # check_action - await layout.confirm_action_newaccount(ctx, action.new_account) - writers.write_action_newaccount(w, action.new_account) + new_account = _require_action_payload(action.new_account) + await layout.confirm_action_newaccount(ctx, new_account) + writers.write_action_newaccount(w, new_account) else: raise ValueError("Unrecognized action type for eosio") elif name == "transfer": - assert action.transfer is not None # check_action - await layout.confirm_action_transfer(ctx, action.transfer, account) - writers.write_action_transfer(w, action.transfer) + transfer = _require_action_payload(action.transfer) + await layout.confirm_action_transfer(ctx, transfer, account) + writers.write_action_transfer(w, transfer) else: await process_unknown_action(ctx, w, action) @@ -87,7 +93,8 @@ async def process_action( async def process_unknown_action( ctx: wire.Context, w: Writer, action: EosTxActionAck ) -> None: - assert action.unknown is not None + if action.unknown is None: + raise ValueError("Bad response. Unknown struct expected.") checksum = HashWriter(sha256()) writers.write_uvarint(checksum, action.unknown.data_size) checksum.extend(action.unknown.data_chunk) diff --git a/core/src/apps/ethereum/helpers.py b/core/src/apps/ethereum/helpers.py index 8fef99387e..b50c47ac01 100644 --- a/core/src/apps/ethereum/helpers.py +++ b/core/src/apps/ethereum/helpers.py @@ -1,6 +1,8 @@ from typing import TYPE_CHECKING from ubinascii import hexlify +from trezor import wire + from . import networks if TYPE_CHECKING: @@ -49,7 +51,6 @@ def _maybe_upper(i: int) -> str: def bytes_from_address(address: str) -> bytes: from ubinascii import unhexlify - from trezor import wire if len(address) == 40: return unhexlify(address) @@ -82,17 +83,20 @@ def get_type_name(field: EthereumFieldType) -> str: } if data_type == EthereumDataType.STRUCT: - assert field.struct_name is not None # validate_field_type + if field.struct_name is None: + raise wire.DataError("Missing struct_name in struct") return field.struct_name elif data_type == EthereumDataType.ARRAY: - assert field.entry_type is not None # validate_field_type + if field.entry_type is None: + raise wire.DataError("Missing entry_type in array") type_name = get_type_name(field.entry_type) if size is None: return f"{type_name}[]" else: return f"{type_name}[{size}]" elif data_type in (EthereumDataType.UINT, EthereumDataType.INT): - assert size is not None # validate_field_type + if size is None: + raise wire.DataError("Invalid size in int/uint") return TYPE_TRANSLATION_DICT[data_type] + str(size * 8) elif data_type == EthereumDataType.BYTES: if size: @@ -122,17 +126,20 @@ def get_type_name_onekey(field: EthereumFieldTypeOneKey) -> str: } if data_type == EthereumDataTypeOneKey.STRUCT: - assert field.struct_name is not None # validate_field_type + if field.struct_name is None: + raise wire.DataError("Missing struct_name in struct") return field.struct_name elif data_type == EthereumDataTypeOneKey.ARRAY: - assert field.entry_type is not None # validate_field_type + if field.entry_type is None: + raise wire.DataError("Missing entry_type in array") type_name = get_type_name_onekey(field.entry_type) if size is None: return f"{type_name}[]" else: return f"{type_name}[{size}]" elif data_type in (EthereumDataTypeOneKey.UINT, EthereumDataTypeOneKey.INT): - assert size is not None # validate_field_type + if size is None: + raise wire.DataError("Invalid size in int/uint") return TYPE_TRANSLATION_DICT[data_type] + str(size * 8) elif data_type == EthereumDataTypeOneKey.BYTES: if size: diff --git a/core/src/apps/ethereum/onekey/keychain.py b/core/src/apps/ethereum/onekey/keychain.py index f373188794..92eacf79e6 100644 --- a/core/src/apps/ethereum/onekey/keychain.py +++ b/core/src/apps/ethereum/onekey/keychain.py @@ -43,7 +43,11 @@ # tools (MEW, Metamask) do not use such scheme and set a = 0 and then # iterate the address index i. For compatibility, we allow this scheme as well. -PATTERNS_ADDRESS = (paths.PATTERN_BIP44, paths.PATTERN_SEP5) +PATTERNS_ADDRESS = ( + paths.PATTERN_BIP44, + paths.PATTERN_SEP5, + paths.PATTERN_SEP5_LEDGER_LIVE_LEGACY, +) def _schemas_from_address_n( diff --git a/core/src/apps/ethereum/onekey/sign_tx.py b/core/src/apps/ethereum/onekey/sign_tx.py index 49d8afee9c..e6e206fd0b 100644 --- a/core/src/apps/ethereum/onekey/sign_tx.py +++ b/core/src/apps/ethereum/onekey/sign_tx.py @@ -283,9 +283,8 @@ async def handle_erc_721_or_1155( token_id = int.from_bytes(msg.data_initial_chunk[68:100], "big") value = int.from_bytes(msg.data_initial_chunk[100:132], "big") - assert ( - int.from_bytes(msg.data_initial_chunk[132:164], "big") == 0xA0 - ) # dyn data position + if int.from_bytes(msg.data_initial_chunk[132:164], "big") != 0xA0: + raise wire.DataError("Invalid call data") data_len = int.from_bytes(msg.data_initial_chunk[164:196], "big") if data_len > 0: data = msg.data_initial_chunk[-data_len:] @@ -304,7 +303,8 @@ async def handle_erc_721_or_1155( token_id = int.from_bytes(msg.data_initial_chunk[68:100], "big") value = 1 if from_addr: - assert recipient is not None + if recipient is None: + raise wire.DataError("Invalid call data") return from_addr, recipient, token_id, value else: return None @@ -481,7 +481,8 @@ async def handle_safe_tx( else: call_data = hexlify(nest_data).decode() call_method = None - assert signature_pos >= 340 + data_len, "invalid call data" + if signature_pos < 340 + data_len: + raise wire.DataError("Invalid call data") if DATA_LEFT is not None: remaining_data = data[340:] + DATA_LEFT nest_data = remaining_data[:data_len] @@ -491,7 +492,8 @@ async def handle_safe_tx( else: signature_data = data[signature_pos:] signatures_len = int.from_bytes(signature_data[:20], "big") - assert len(signature_data) >= 20 + signatures_len, "invalid call data length" + if len(signature_data) < 20 + signatures_len: + raise wire.DataError("Invalid call data length") signatures = signature_data[20 : 20 + signatures_len] if not is_eip1559: from ..layout import require_confirm_safe_exec_transaction diff --git a/core/src/apps/ethereum/onekey/sign_tx_eip7702.py b/core/src/apps/ethereum/onekey/sign_tx_eip7702.py index f4700b5e26..b42b6820bc 100644 --- a/core/src/apps/ethereum/onekey/sign_tx_eip7702.py +++ b/core/src/apps/ethereum/onekey/sign_tx_eip7702.py @@ -45,7 +45,8 @@ def authorization_list_item_length(item: EthereumAuthorizationOneKey) -> int: - assert item.signature is not None, "Authorization signature is required" + if item.signature is None: + raise wire.DataError("Authorization signature is required") return rlp.length( [ item.chain_id, @@ -75,7 +76,8 @@ def write_authorization_list( ) rlp.write_header(w, payload_length, rlp.LIST_HEADER_BYTE) for item in authorization_list: - assert item.signature is not None, "Authorization signature is required" + if item.signature is None: + raise wire.DataError("Authorization signature is required") rlp.write( w, [ diff --git a/core/src/apps/ethereum/onekey/sign_typed_data.py b/core/src/apps/ethereum/onekey/sign_typed_data.py index 974084ce89..62c7a4f47f 100644 --- a/core/src/apps/ethereum/onekey/sign_typed_data.py +++ b/core/src/apps/ethereum/onekey/sign_typed_data.py @@ -168,6 +168,18 @@ def keccak256(message: bytes) -> bytes: return h.get_digest() +def _require_entry_type(field: EthereumFieldType) -> EthereumFieldType: + if field.entry_type is None: + raise wire.DataError("Missing entry_type in array") + return field.entry_type + + +def _require_struct_name(field: EthereumFieldType) -> str: + if field.struct_name is None: + raise wire.DataError("Missing struct_name in struct") + return field.struct_name + + class TypedDataEnvelope: """Encapsulates the type information for the message being hashed and signed.""" @@ -196,14 +208,12 @@ async def _collect_types(self, type_name: str) -> None: member_type = member.type validate_field_type(member_type) while member_type.data_type == EthereumDataType.ARRAY: - assert member_type.entry_type is not None # validate_field_type - member_type = member_type.entry_type + member_type = _require_entry_type(member_type) if ( member_type.data_type == EthereumDataType.STRUCT and member_type.struct_name not in self.types ): - assert member_type.struct_name is not None # validate_field_type - await self._collect_types(member_type.struct_name) + await self._collect_types(_require_struct_name(member_type)) async def hash_struct( self, @@ -269,11 +279,9 @@ def find_typed_dependencies( for member in self.types[primary_type].members: member_type = member.type while member_type.data_type == EthereumDataType.ARRAY: - assert member_type.entry_type is not None # validate_field_type - member_type = member_type.entry_type + member_type = _require_entry_type(member_type) if member_type.data_type == EthereumDataType.STRUCT: - assert member_type.struct_name is not None # validate_field_type - self.find_typed_dependencies(member_type.struct_name, results) + self.find_typed_dependencies(_require_struct_name(member_type), results) async def get_and_encode_data( self, @@ -301,8 +309,7 @@ async def get_and_encode_data( # Arrays and structs need special recursive handling if field_type.data_type == EthereumDataType.STRUCT: - assert field_type.struct_name is not None # validate_field_type - struct_name = field_type.struct_name + struct_name = _require_struct_name(field_type) current_parent_objects[-1] = field_name if show_data: @@ -330,8 +337,7 @@ async def get_and_encode_data( else: array_size = field_type.size - assert field_type.entry_type is not None # validate_field_type - entry_type = field_type.entry_type + entry_type = _require_entry_type(field_type) current_parent_objects[-1] = field_name if show_data: @@ -350,8 +356,7 @@ async def get_and_encode_data( el_member_path[-1] = i # TODO: we do not support arrays of arrays, check if we should if entry_type.data_type == EthereumDataType.STRUCT: - assert entry_type.struct_name is not None # validate_field_type - struct_name = entry_type.struct_name + struct_name = _require_struct_name(entry_type) # Metamask V4 implementation has a bug, that causes the # behavior of structs in array be different from SPEC # Explanation at https://github.com/MetaMask/eth-sig-util/pull/107 @@ -442,7 +447,8 @@ def encode_field( def write_leftpad32(w: HashWriter, value: bytes, signed: bool = False) -> None: - assert len(value) <= 32 + if len(value) > 32: + raise wire.DataError("Invalid length") # Values need to be sign-extended, so accounting for negative ints if signed and value[0] & 0x80: @@ -456,7 +462,8 @@ def write_leftpad32(w: HashWriter, value: bytes, signed: bool = False) -> None: def write_rightpad32(w: HashWriter, value: bytes) -> None: - assert len(value) <= 32 + if len(value) > 32: + raise wire.DataError("Invalid length") w.extend(value) for _i in range(32 - len(value)): diff --git a/core/src/apps/ethereum/sign_tx.py b/core/src/apps/ethereum/sign_tx.py index d8e17dfbf5..4f4aea2c24 100644 --- a/core/src/apps/ethereum/sign_tx.py +++ b/core/src/apps/ethereum/sign_tx.py @@ -180,9 +180,8 @@ async def handle_erc_721_or_1155( token_id = int.from_bytes(msg.data_initial_chunk[68:100], "big") value = int.from_bytes(msg.data_initial_chunk[100:132], "big") - assert ( - int.from_bytes(msg.data_initial_chunk[132:164], "big") == 0xA0 - ) # dyn data position + if int.from_bytes(msg.data_initial_chunk[132:164], "big") != 0xA0: + raise wire.DataError("Invalid call data") data_len = int.from_bytes(msg.data_initial_chunk[164:196], "big") data = msg.data_initial_chunk[-data_len:] if not (data_len == 1 and data == b"\x00"): @@ -200,7 +199,8 @@ async def handle_erc_721_or_1155( token_id = int.from_bytes(msg.data_initial_chunk[68:100], "big") value = 1 if from_addr: - assert recipient is not None + if recipient is None: + raise wire.DataError("Invalid call data") return from_addr, recipient, token_id, value else: return None diff --git a/core/src/apps/ethereum/sign_typed_data.py b/core/src/apps/ethereum/sign_typed_data.py index 2fa0f24564..7a04768615 100644 --- a/core/src/apps/ethereum/sign_typed_data.py +++ b/core/src/apps/ethereum/sign_typed_data.py @@ -160,6 +160,18 @@ def keccak256(message: bytes) -> bytes: return h.get_digest() +def _require_entry_type(field: EthereumFieldType) -> EthereumFieldType: + if field.entry_type is None: + raise wire.DataError("Missing entry_type in array") + return field.entry_type + + +def _require_struct_name(field: EthereumFieldType) -> str: + if field.struct_name is None: + raise wire.DataError("Missing struct_name in struct") + return field.struct_name + + class TypedDataEnvelope: """Encapsulates the type information for the message being hashed and signed.""" @@ -188,14 +200,12 @@ async def _collect_types(self, type_name: str) -> None: member_type = member.type validate_field_type(member_type) while member_type.data_type == EthereumDataType.ARRAY: - assert member_type.entry_type is not None # validate_field_type - member_type = member_type.entry_type + member_type = _require_entry_type(member_type) if ( member_type.data_type == EthereumDataType.STRUCT and member_type.struct_name not in self.types ): - assert member_type.struct_name is not None # validate_field_type - await self._collect_types(member_type.struct_name) + await self._collect_types(_require_struct_name(member_type)) async def hash_struct( self, @@ -259,11 +269,9 @@ def find_typed_dependencies( for member in self.types[primary_type].members: member_type = member.type while member_type.data_type == EthereumDataType.ARRAY: - assert member_type.entry_type is not None # validate_field_type - member_type = member_type.entry_type + member_type = _require_entry_type(member_type) if member_type.data_type == EthereumDataType.STRUCT: - assert member_type.struct_name is not None # validate_field_type - self.find_typed_dependencies(member_type.struct_name, results) + self.find_typed_dependencies(_require_struct_name(member_type), results) async def get_and_encode_data( self, @@ -291,8 +299,7 @@ async def get_and_encode_data( # Arrays and structs need special recursive handling if field_type.data_type == EthereumDataType.STRUCT: - assert field_type.struct_name is not None # validate_field_type - struct_name = field_type.struct_name + struct_name = _require_struct_name(field_type) current_parent_objects[-1] = field_name if show_data: @@ -320,8 +327,7 @@ async def get_and_encode_data( else: array_size = field_type.size - assert field_type.entry_type is not None # validate_field_type - entry_type = field_type.entry_type + entry_type = _require_entry_type(field_type) current_parent_objects[-1] = field_name if show_data: @@ -340,8 +346,7 @@ async def get_and_encode_data( el_member_path[-1] = i # TODO: we do not support arrays of arrays, check if we should if entry_type.data_type == EthereumDataType.STRUCT: - assert entry_type.struct_name is not None # validate_field_type - struct_name = entry_type.struct_name + struct_name = _require_struct_name(entry_type) # Metamask V4 implementation has a bug, that causes the # behavior of structs in array be different from SPEC # Explanation at https://github.com/MetaMask/eth-sig-util/pull/107 @@ -432,7 +437,8 @@ def encode_field( def write_leftpad32(w: HashWriter, value: bytes, signed: bool = False) -> None: - assert len(value) <= 32 + if len(value) > 32: + raise wire.DataError("Invalid length") # Values need to be sign-extended, so accounting for negative ints if signed and value[0] & 0x80: @@ -446,7 +452,8 @@ def write_leftpad32(w: HashWriter, value: bytes, signed: bool = False) -> None: def write_rightpad32(w: HashWriter, value: bytes) -> None: - assert len(value) <= 32 + if len(value) > 32: + raise wire.DataError("Invalid length") w.extend(value) for _i in range(32 - len(value)): diff --git a/core/src/apps/kaspa/addresses.py b/core/src/apps/kaspa/addresses.py index 588a0f491e..b468a427da 100644 --- a/core/src/apps/kaspa/addresses.py +++ b/core/src/apps/kaspa/addresses.py @@ -48,11 +48,13 @@ def encode_address( pubkey = ( bip340.tweak_public_key(raw_pubkey[1:]) if use_tweak else raw_pubkey[1:] ) - assert len(pubkey) == Version.public_key_len(Version.PubKey) + if len(pubkey) != Version.public_key_len(Version.PubKey): + raise RuntimeError("Invalid public key length") version = Version.PubKey else: pubkey = raw_pubkey - assert len(pubkey) == Version.public_key_len(Version.PubKeyECDSA) + if len(pubkey) != Version.public_key_len(Version.PubKeyECDSA): + raise RuntimeError("Invalid public key length") version = Version.PubKeyECDSA return cashaddr.encode(prefix, version, pubkey) diff --git a/core/src/apps/management/recovery_device/homescreen.py b/core/src/apps/management/recovery_device/homescreen.py index 6310781397..d2ec5dfa99 100644 --- a/core/src/apps/management/recovery_device/homescreen.py +++ b/core/src/apps/management/recovery_device/homescreen.py @@ -100,7 +100,8 @@ async def _continue_recovery_process( is_first_step = backup_type is None if not is_first_step: - assert word_count is not None + if word_count is None: + raise RuntimeError("Recovery word count missing") # If we continue recovery, show starting screen with word count immediately. await _request_share_first_screen(ctx, word_count) @@ -119,7 +120,8 @@ async def _continue_recovery_process( # word_count = 12 # ...and only then show the starting screen with word count. await _request_share_first_screen(ctx, word_count) - assert word_count is not None + if word_count is None: + raise RuntimeError("Recovery word count missing") # ask for mnemonic words one by one try: @@ -151,12 +153,14 @@ async def _continue_recovery_process( # its result) and word_count (from _request_word_count earlier), which means # that the first step is complete. except MnemonicError: - assert words is not None + if words is None: + raise RuntimeError("Recovery words missing") words_list = words.split(" ") while True: result = await layout.show_invalid_mnemonic(ctx, words_list) if result is not None: - assert word_count is not None + if word_count is None: + raise RuntimeError("Recovery word count missing") try: word = await request_word( ctx, @@ -178,7 +182,8 @@ async def _continue_recovery_process( # sys.print_exception(e) - assert backup_type is not None + if backup_type is None: + raise RuntimeError("Recovery backup type missing") if dry_run: result = await _finish_recovery_dry_run(ctx, secret, backup_type) else: @@ -303,7 +308,8 @@ async def _process_words( if __debug__: print(f"secret: {secret}, backup_type: {backup_type}") if secret is None: # SLIP-39 - assert share is not None + if share is None: + raise RuntimeError("Recovery share missing") if share.group_count and share.group_count > 1: if __debug__: print( diff --git a/core/src/apps/management/recovery_device/word_validity.py b/core/src/apps/management/recovery_device/word_validity.py index 68fc9c70b2..6b5e044a51 100644 --- a/core/src/apps/management/recovery_device/word_validity.py +++ b/core/src/apps/management/recovery_device/word_validity.py @@ -87,7 +87,8 @@ def _check_slip39_advanced( if current_word == group[0].split(" ")[current_index]: remaining_shares = storage.recovery.fetch_slip39_remaining_shares() # if backup_type is not None, some share was already entered -> remaining needs to be set - assert remaining_shares is not None + if remaining_shares is None: + raise RuntimeError if remaining_shares[i] == 0: raise ThresholdReached diff --git a/core/src/apps/management/update_res.py b/core/src/apps/management/update_res.py index 6646043a69..422aec9a4c 100644 --- a/core/src/apps/management/update_res.py +++ b/core/src/apps/management/update_res.py @@ -94,7 +94,7 @@ async def update_res(ctx: wire.Context, msg: ResourceUpdate) -> Success: # make sure the outgoing USB buffer is flushed await loop.wait(ctx.iface.iface_num() | io.POLL_WRITE) utils.reset() - assert False # this should be not reachable + raise wire.FirmwareError("Device reset did not happen") return Success(message="Success") diff --git a/core/src/apps/management/upload_res.py b/core/src/apps/management/upload_res.py index 86cd591cf7..99f7d3f3cd 100644 --- a/core/src/apps/management/upload_res.py +++ b/core/src/apps/management/upload_res.py @@ -295,7 +295,8 @@ async def upload_res(ctx: wire.Context, msg: ResourceUpload) -> Success: if res_type == ResourceType.Nft and config_path: with io.fatfs.open(config_path, "w") as f: - assert msg.nft_meta_data + if not msg.nft_meta_data: + raise wire.DataError("NFT metadata is required") f.write(msg.nft_meta_data) f.sync() _verify_file_size(config_path, metadata_len) diff --git a/core/src/apps/monero/get_address.py b/core/src/apps/monero/get_address.py index f3649cf77c..dea1362656 100644 --- a/core/src/apps/monero/get_address.py +++ b/core/src/apps/monero/get_address.py @@ -42,7 +42,8 @@ async def get_address( raise wire.DataError("Subaddress cannot be integrated") if have_payment_id: - assert msg.payment_id is not None + if msg.payment_id is None: + raise wire.DataError("Payment ID is required") if len(msg.payment_id) != 8: raise ValueError("Invalid payment ID length") addr = addresses.encode_addr( @@ -53,8 +54,8 @@ async def get_address( ) if have_subaddress: - assert msg.account is not None - assert msg.minor is not None + if msg.account is None or msg.minor is None: + raise wire.ProcessError("Invalid subaddress indexes") pub_spend, pub_view = monero.generate_sub_address_keys( creds.view_key_private, creds.spend_key_public, msg.account, msg.minor diff --git a/core/src/apps/monero/key_image_sync.py b/core/src/apps/monero/key_image_sync.py index 6e48a0f785..d58078e3e8 100644 --- a/core/src/apps/monero/key_image_sync.py +++ b/core/src/apps/monero/key_image_sync.py @@ -80,7 +80,8 @@ async def _init_step( async def _sync_step( s: KeyImageSync, ctx: wire.Context, tds: MoneroKeyImageSyncStepRequest ) -> MoneroKeyImageSyncStepAck: - assert s.creds is not None + if s.creds is None: + raise RuntimeError("Key image credentials missing") if not tds.tdis: raise wire.DataError("Empty") diff --git a/core/src/apps/monero/layout.py b/core/src/apps/monero/layout.py index 6c11187ae6..c8b16872d7 100644 --- a/core/src/apps/monero/layout.py +++ b/core/src/apps/monero/layout.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING -from trezor import strings, ui +from trezor import strings, ui, wire from trezor.enums import ButtonRequestType from trezor.lvglui.i18n import gettext as _, keys as i18n_keys from trezor.ui.layouts import ( @@ -135,7 +135,8 @@ async def _require_confirm_output( addr = encode_addr( version, dst.addr.spend_public_key, dst.addr.view_public_key, payment_id ) - assert dst.amount is not None + if dst.amount is None: + raise wire.DataError("Missing destination amount") await confirm_output( ctx, address=addr, diff --git a/core/src/apps/monero/live_refresh.py b/core/src/apps/monero/live_refresh.py index 1cec5a1d60..92bc9e9549 100644 --- a/core/src/apps/monero/live_refresh.py +++ b/core/src/apps/monero/live_refresh.py @@ -71,7 +71,8 @@ async def _init_step( async def _refresh_step( s: LiveRefreshState, ctx: Context, msg: MoneroLiveRefreshStepRequest ) -> MoneroLiveRefreshStepAck: - assert s.creds is not None + if s.creds is None: + raise RuntimeError("Live refresh credentials missing") buff = bytearray(32 * 3) buff_mv = memoryview(buff) diff --git a/core/src/apps/monero/signing/step_01_init_transaction.py b/core/src/apps/monero/signing/step_01_init_transaction.py index e4f861be0f..d44b2d5b95 100644 --- a/core/src/apps/monero/signing/step_01_init_transaction.py +++ b/core/src/apps/monero/signing/step_01_init_transaction.py @@ -43,7 +43,8 @@ async def init_transaction( state.input_count = tsx_data.num_inputs state.output_count = len(tsx_data.outputs) - assert state.input_count is not None + if state.input_count is None: + raise RuntimeError("Input count missing") state.progress_total = 4 + 3 * state.input_count + state.output_count state.progress_cur = 0 diff --git a/core/src/apps/monero/signing/step_07_all_outputs_set.py b/core/src/apps/monero/signing/step_07_all_outputs_set.py index a311a8aa94..5c790fb93f 100644 --- a/core/src/apps/monero/signing/step_07_all_outputs_set.py +++ b/core/src/apps/monero/signing/step_07_all_outputs_set.py @@ -57,12 +57,13 @@ async def all_outputs_set(state: State) -> MoneroTransactionAllOutSetAck: rv_type=state.tx_type, ) + if state.full_message_hasher is None: + raise RuntimeError("Full message hasher missing") _out_pk(state) state.full_message_hasher.rctsig_base_done() state.current_output_index = None state.current_input_index = -1 - assert state.full_message_hasher is not None state.full_message = state.full_message_hasher.get_digest() state.full_message_hasher = None state.output_pk_commitments = None diff --git a/core/src/apps/monero/signing/step_09_sign_input.py b/core/src/apps/monero/signing/step_09_sign_input.py index 40ba221b62..4f401b4bfe 100644 --- a/core/src/apps/monero/signing/step_09_sign_input.py +++ b/core/src/apps/monero/signing/step_09_sign_input.py @@ -176,7 +176,8 @@ async def sign_input( state.mem_trace(5, True) - assert state.full_message is not None + if state.full_message is None: + raise RuntimeError("Full message missing") state.mem_trace("CLSAG") clsag.generate_clsag_simple( state.full_message, diff --git a/core/src/apps/monero/xmr/monero.py b/core/src/apps/monero/xmr/monero.py index 4a1b6d0d1c..f50446db62 100644 --- a/core/src/apps/monero/xmr/monero.py +++ b/core/src/apps/monero/xmr/monero.py @@ -113,7 +113,8 @@ def is_out_to_account( if sub_pub_key and crypto.point_eq(subaddress_spendkey_obj, sub_pub_key): # sub_pub_key is only set if sub_addr_{major, minor} are set - assert sub_addr_major is not None and sub_addr_minor is not None + if sub_addr_major is None or sub_addr_minor is None: + raise RuntimeError("Subaddress index missing") return (sub_addr_major, sub_addr_minor), additional_derivation if subaddresses: diff --git a/core/src/apps/nem/mosaic/layout.py b/core/src/apps/nem/mosaic/layout.py index 00acabfe16..7adb97811f 100644 --- a/core/src/apps/nem/mosaic/layout.py +++ b/core/src/apps/nem/mosaic/layout.py @@ -89,9 +89,12 @@ async def require_confirm_properties( # levy if definition.levy: # asserts below checked in nem.validators._validate_mosaic_creation - assert definition.levy_address is not None - assert definition.levy_namespace is not None - assert definition.levy_mosaic is not None + if ( + definition.levy_address is None + or definition.levy_namespace is None + or definition.levy_mosaic is None + ): + raise ValueError("Missing mosaic levy data") properties.append(("Levy recipient:", definition.levy_address)) diff --git a/core/src/apps/nem/mosaic/serialize.py b/core/src/apps/nem/mosaic/serialize.py index a043e815af..3a26386081 100644 --- a/core/src/apps/nem/mosaic/serialize.py +++ b/core/src/apps/nem/mosaic/serialize.py @@ -44,10 +44,13 @@ def serialize_mosaic_creation( if creation.definition.levy: # all below asserts checked by nem.validators._validate_mosaic_creation - assert creation.definition.levy_namespace is not None - assert creation.definition.levy_mosaic is not None - assert creation.definition.levy_address is not None - assert creation.definition.fee is not None + if ( + creation.definition.levy_namespace is None + or creation.definition.levy_mosaic is None + or creation.definition.levy_address is None + or creation.definition.fee is None + ): + raise ValueError("Missing mosaic levy data") levy_identifier_w = bytearray() write_bytes_with_len( diff --git a/core/src/apps/nem/multisig/layout.py b/core/src/apps/nem/multisig/layout.py index 13b747276f..a9fed9771c 100644 --- a/core/src/apps/nem/multisig/layout.py +++ b/core/src/apps/nem/multisig/layout.py @@ -13,8 +13,8 @@ async def ask_multisig(ctx: Context, msg: NEMSignTx) -> None: - assert msg.multisig is not None # sign_tx - assert msg.multisig.signer is not None # sign_tx + if msg.multisig is None or msg.multisig.signer is None: + raise ValueError("Missing multisig signer") address = nem.compute_address(msg.multisig.signer, msg.transaction.network) if msg.cosigning: await _require_confirm_address(ctx, "Cosign transaction for", address) diff --git a/core/src/apps/nem/sign_tx.py b/core/src/apps/nem/sign_tx.py index b7a6c22a3d..c5ca369e6b 100644 --- a/core/src/apps/nem/sign_tx.py +++ b/core/src/apps/nem/sign_tx.py @@ -77,7 +77,8 @@ async def sign_tx(ctx: wire.Context, msg: NEMSignTx, keychain: Keychain) -> NEMS if msg.multisig: # wrap transaction in multisig wrapper if msg.cosigning: - assert msg.multisig.signer is not None + if msg.multisig.signer is None: + raise wire.DataError("Missing multisig signer") tx = multisig.cosign( seed.remove_ed25519_prefix(node.public_key()), msg.transaction, diff --git a/core/src/apps/neo/helpers.py b/core/src/apps/neo/helpers.py index 4cda0cc85a..c83d577235 100644 --- a/core/src/apps/neo/helpers.py +++ b/core/src/apps/neo/helpers.py @@ -8,7 +8,8 @@ def build_check_sig_script_hash(pubkey: bytes) -> bytes: - assert len(pubkey) == 33, "Compressed public key is expected" + if len(pubkey) != 33: + raise ValueError("Compressed public key is expected") verification_script = b"\x0C\x21" + pubkey + b"\x41\x56\xe7\xb3\x27" return scripts.sha256_ripemd160(verification_script).digest() @@ -21,7 +22,8 @@ def neo_address_from_pubkey(pubkey: bytes, version: bytes = ADDRESS_VERSION) -> def neo_address_from_script_hash( script_hash: bytes, version: bytes = ADDRESS_VERSION ) -> str: - assert len(script_hash) == 20, "Script hash is expected to be 20 bytes" + if len(script_hash) != 20: + raise ValueError("Script hash is expected to be 20 bytes") return base58.encode_check(version + script_hash) diff --git a/core/src/apps/neo/transaction.py b/core/src/apps/neo/transaction.py index 7ed41498ac..119a7d9dad 100644 --- a/core/src/apps/neo/transaction.py +++ b/core/src/apps/neo/transaction.py @@ -427,56 +427,68 @@ def __init__(self): self._token: NeoTokenInfo | None = None def deserialize(self, reader: BufferReader) -> None: - assert reader.remaining_count() > self.HEADER_SIZE, "transaction is too short" + if reader.remaining_count() <= self.HEADER_SIZE: + raise ValueError("transaction is too short") self._version = reader.get() - assert self._version == 0, "version must be 0" + if self._version != 0: + raise ValueError("version must be 0") self._nonce = readers.read_uint32_le(reader) self._system_fee = readers.read_int64_le(reader) - assert self._system_fee >= 0, "system_fee must be positive" + if self._system_fee < 0: + raise ValueError("system_fee must be positive") self._network_fee = readers.read_int64_le(reader) - assert self._network_fee >= 0, "network_fee must be positive" + if self._network_fee < 0: + raise ValueError("network_fee must be positive") self._valid_until_block = readers.read_uint32_le(reader) signers_count = readers.read_compact_size(reader) - assert signers_count > 0, "signers must be non-empty" + if signers_count == 0: + raise ValueError("signers must be non-empty") self._signers = [Signer().deserialize(reader) for _ in range(signers_count)] - assert signers_count == len(set(self._signers)), "signers must be unique" + if signers_count != len(set(self._signers)): + raise ValueError("signers must be unique") attributes_count = readers.read_compact_size(reader) - assert ( - attributes_count <= self.MAX_TX_ATTRIBUTES - ), f"attributes must be less than or equal to {self.MAX_TX_ATTRIBUTES}" + if attributes_count > self.MAX_TX_ATTRIBUTES: + raise ValueError( + f"attributes must be less than or equal to {self.MAX_TX_ATTRIBUTES}" + ) self._attributes = [ TransactionAttribute.deserialize_from(reader) for _ in range(attributes_count) ] - assert attributes_count == len( - set(self._attributes) - ), "attributes must be unique" + if attributes_count != len(set(self._attributes)): + raise ValueError("attributes must be unique") script_length = readers.read_compact_size(reader) - assert script_length > 0, "script must be non-empty" + if script_length == 0: + raise ValueError("script must be non-empty") self._script = reader.read(script_length) - assert reader.remaining_count() == 0, "reader must be empty" + if reader.remaining_count() != 0: + raise ValueError("reader must be empty") if not self.parse_asset_transfer(): self.parse_vote() def sender(self) -> str: account = self._signers[0].account - assert account is not None, "signers must be non-empty" + if account is None: + raise RuntimeError("signers must be non-empty") return neo_address_from_script_hash(account) def source(self) -> str: - assert self._source_script_hash is not None, "Invalid transaction state" + if self._source_script_hash is None: + raise RuntimeError("Invalid transaction state") return neo_address_from_script_hash(self._source_script_hash) def destination(self) -> str: - assert self._destination_script_hash is not None, "Invalid transaction state" + if self._destination_script_hash is None: + raise RuntimeError("Invalid transaction state") return neo_address_from_script_hash(self._destination_script_hash) def vote_to(self) -> str: - assert self._vote_to is not None, "Invalid transaction state" + if self._vote_to is None: + raise RuntimeError("Invalid transaction state") from .helpers import neo_address_from_pubkey return neo_address_from_pubkey(self._vote_to) @@ -485,12 +497,14 @@ def token(self) -> NeoTokenInfo: if self._token is None: from .tokens import token_by_contract_script_hash - assert self._contract_script_hash is not None, "Invalid transaction state" + if self._contract_script_hash is None: + raise RuntimeError("Invalid transaction state") self._token = token_by_contract_script_hash(self._contract_script_hash) return self._token def token_contract_hash(self) -> str: - assert self._contract_script_hash is not None, "Invalid transaction state" + if self._contract_script_hash is None: + raise RuntimeError("Invalid transaction state") from binascii import hexlify return hexlify(bytes(reversed(self._contract_script_hash))).decode() @@ -502,7 +516,8 @@ def is_unknown_token(self) -> bool: return token == UNKNOWN_TOKEN def display_amount(self) -> str: - assert self._is_asset_transfer, "Invalid transaction state" + if not self._is_asset_transfer: + raise RuntimeError("Invalid transaction state") token = self.token() return f"{format_amount(self._amount, token.decimals)} {token.symbol}" @@ -520,7 +535,8 @@ def total_fee(self) -> str: return f"{format_amount(self._system_fee + self._network_fee, 8)} GAS" def parse_asset_transfer(self) -> bool: - assert self._script, "Invalid transaction state" + if not self._script: + raise RuntimeError("Invalid transaction state") reader = BufferReader(self._script) op_code = reader.get() # first byte should be 0xb (OpCode.PUSHNULL), indicating no data for the Nep17.transfer() 'data' argument @@ -567,14 +583,14 @@ def parse_asset_transfer(self) -> bool: ending_script = reader.read(len(CONTRACT_SYSCALL_SEQUENCE)) if ending_script != CONTRACT_SYSCALL_SEQUENCE: return False - assert ( - reader.remaining_count() == 0 - ), "extra code after the transfer script is not allowed" + if reader.remaining_count() != 0: + raise ValueError("extra code after the transfer script is not allowed") self._is_asset_transfer = True return True def parse_vote(self) -> bool: - assert self._script, "Invalid transaction state" + if not self._script: + raise RuntimeError("Invalid transaction state") reader = BufferReader(self._script) op_code = reader.get() # first byte should be 0xb when removing a vote, or 0x0C when voting @@ -587,7 +603,8 @@ def parse_vote(self) -> bool: if op_code != 0x21: return False self._vote_to = reader.read(_COMPRESSED_PUBLIC_KEY_SIZE) - assert self._vote_to[0] in [0x02, 0x03], "invalid compressed public key" + if self._vote_to[0] not in (0x02, 0x03): + raise ValueError("invalid compressed public key") self._is_remove_vote = False # check for source script hash op_code = reader.get() @@ -611,8 +628,7 @@ def parse_vote(self) -> bool: ending_script = reader.read(len(CONTRACT_SYSCALL_SEQUENCE)) if ending_script != CONTRACT_SYSCALL_SEQUENCE: return False - assert ( - reader.remaining_count() == 0 - ), "extra code after the vote script is not allowed" + if reader.remaining_count() != 0: + raise ValueError("extra code after the vote script is not allowed") self._is_vote = True return True diff --git a/core/src/apps/nexa/sign_tx.py b/core/src/apps/nexa/sign_tx.py index d67c6ffa69..2e40093ecb 100644 --- a/core/src/apps/nexa/sign_tx.py +++ b/core/src/apps/nexa/sign_tx.py @@ -66,7 +66,8 @@ async def sign_input(ctx, msg: Signable, keychain) -> bytes: if msg.address_n not in addresses: addresses.append(msg.address_n) await confirm_blind_sign_common(ctx, address, msg.raw_message) - assert msg.raw_message[-4:] == b"\x00\x00\x00\x00", "Invalid sighash type." + if msg.raw_message[-4:] != b"\x00\x00\x00\x00": + raise wire.DataError("Invalid sighash type.") sig_hash = sha256(sha256(msg.raw_message).digest()).digest() signature = schnorr_bch.sign(node.private_key(), sig_hash) return signature diff --git a/core/src/apps/polkadot/codec/base.py b/core/src/apps/polkadot/codec/base.py index 0bf0fcfcec..b75ff0fad7 100644 --- a/core/src/apps/polkadot/codec/base.py +++ b/core/src/apps/polkadot/codec/base.py @@ -85,7 +85,8 @@ def __init__(self, data: ScaleBytes, sub_type: str = None, runtime_config=None): # self.build_type_mapping() if data: - assert type(data) == ScaleBytes + if type(data) is not ScaleBytes: + raise TypeError("data must be ScaleBytes") if runtime_config: self.runtime_config = runtime_config diff --git a/core/src/apps/ripple/serialize.py b/core/src/apps/ripple/serialize.py index 8f8c8a0ca5..44dcbb83d7 100644 --- a/core/src/apps/ripple/serialize.py +++ b/core/src/apps/ripple/serialize.py @@ -74,19 +74,24 @@ def write(w: Writer, field: RippleField, value: int | bytes | str | None) -> Non return write_type(w, field) if field.type == FIELD_TYPE_INT16: - assert isinstance(value, int) + if not isinstance(value, int): + raise TypeError("Expected int value") w.extend(value.to_bytes(2, "big")) elif field.type == FIELD_TYPE_INT32: - assert isinstance(value, int) + if not isinstance(value, int): + raise TypeError("Expected int value") w.extend(value.to_bytes(4, "big")) elif field.type == FIELD_TYPE_AMOUNT: - assert isinstance(value, int) + if not isinstance(value, int): + raise TypeError("Expected int value") w.extend(serialize_amount(value)) elif field.type == FIELD_TYPE_ACCOUNT: - assert isinstance(value, str) + if not isinstance(value, str): + raise TypeError("Expected str value") write_bytes_varint(w, helpers.decode_address(value)) elif field.type == FIELD_TYPE_VL: - assert isinstance(value, (bytes, bytearray)) + if not isinstance(value, (bytes, bytearray)): + raise TypeError("Expected bytes value") write_bytes_varint(w, value) else: raise ValueError("Unknown field type") diff --git a/core/src/apps/solana/spl/ata_program.py b/core/src/apps/solana/spl/ata_program.py index 13faf25fe0..9f33d56071 100644 --- a/core/src/apps/solana/spl/ata_program.py +++ b/core/src/apps/solana/spl/ata_program.py @@ -39,8 +39,12 @@ async def parse(ctx: wire.Context, accounts: list[PublicKey], data: bytes) -> No raise wire.DataError( f"Instruction type {instruction_type} is not supported" ) - assert accounts[4] == constents.SYS_PROGRAM_ID - assert accounts[5] == constents.SPL_TOKEN_PROGRAM_ID + if len(accounts) < 6: + raise wire.DataError("Invalid associated token account instruction") + if accounts[4] != constents.SYS_PROGRAM_ID: + raise wire.DataError("Invalid system program") + if accounts[5] != constents.SPL_TOKEN_PROGRAM_ID: + raise wire.DataError("Invalid SPL token program") params = CreateAssociatedTokenAccountParams( funding_account=accounts[0], associated_token_account=accounts[1], diff --git a/core/src/apps/stellar/consts.py b/core/src/apps/stellar/consts.py index 2e9d069dd0..3fe6d2eb7f 100644 --- a/core/src/apps/stellar/consts.py +++ b/core/src/apps/stellar/consts.py @@ -106,8 +106,7 @@ def get_op_code(msg: protobuf.MessageType) -> int: - wire = msg.MESSAGE_WIRE_TYPE - if wire not in op_codes: + wire_type = msg.MESSAGE_WIRE_TYPE + if not isinstance(wire_type, int) or wire_type not in op_codes: raise ValueError("Stellar: op code unknown") - assert isinstance(wire, int) - return op_codes[wire] + return op_codes[wire_type] diff --git a/core/src/apps/tezos/sign_tx.py b/core/src/apps/tezos/sign_tx.py index ff53a65988..f165f7165c 100644 --- a/core/src/apps/tezos/sign_tx.py +++ b/core/src/apps/tezos/sign_tx.py @@ -211,7 +211,8 @@ def _get_operation_bytes(w: Writer, msg: TezosSignTx) -> None: elif parameters_manager.cancel_delegate is not None: _encode_manager_delegation_remove(w) elif parameters_manager.transfer is not None: - assert parameters_manager.transfer.destination is not None + if parameters_manager.transfer.destination is None: + raise wire.DataError("Missing manager transfer destination") if ( parameters_manager.transfer.destination.tag == TezosContractType.Implicit diff --git a/core/src/apps/ton/sign_data.py b/core/src/apps/ton/sign_data.py new file mode 100644 index 0000000000..1dd8db9e0b --- /dev/null +++ b/core/src/apps/ton/sign_data.py @@ -0,0 +1,178 @@ +from typing import TYPE_CHECKING + +from trezor import wire +from trezor.crypto import hashlib +from trezor.crypto.curve import ed25519 +from trezor.enums import TonSignDataType, TonWalletVersion, TonWorkChain +from trezor.lvglui.scrs import lv +from trezor.messages import TonSignData, TonSignedData +from trezor.ui.layouts import confirm_ton_sign_request + +from apps.common import paths, seed +from apps.common.keychain import Keychain, auto_keychain + +from . import ICON, PRIMARY_COLOR +from .tonsdk import utils as ton_utils +from .tonsdk.boc._builder import begin_cell +from .tonsdk.boc._cell import Cell +from .tonsdk.contract.wallet import Wallets, WalletVersionEnum +from .tonsdk.utils._address import Address as TonAddress + +if TYPE_CHECKING: + from trezor.wire import Context + + +@auto_keychain(__name__) +async def sign_data( + ctx: Context, msg: TonSignData, keychain: Keychain +) -> TonSignedData: + await paths.validate_path(ctx, keychain, msg.address_n) + + node = keychain.derive(msg.address_n) + public_key = seed.remove_ed25519_prefix(node.public_key()) + workchain = -1 if msg.workchain == TonWorkChain.MASTERCHAIN else 0 + + if msg.wallet_version == TonWalletVersion.V4R2: + wallet_version = WalletVersionEnum.v4r2 + else: + raise wire.DataError("Invalid wallet version.") + + wallet = Wallets.ALL[wallet_version]( + public_key=public_key, + wallet_id=msg.wallet_id, + wc=workchain, + ) + address = wallet.address.to_string( + is_user_friendly=True, + is_url_safe=True, + is_bounceable=msg.is_bounceable, + is_test_only=msg.is_testnet_only, + ) + _validate_message(msg, address) + + ctx.primary_color, ctx.icon_path = lv.color_hex(PRIMARY_COLOR), ICON + + if msg.type in (TonSignDataType.TEXT, TonSignDataType.BINARY): + if msg.type == TonSignDataType.TEXT: + message = msg.payload.decode() + else: + from binascii import hexlify + + message = hexlify(msg.payload).decode() + await confirm_ton_sign_request( + ctx, + message, + address, + msg.appdomain, + clear_sign=msg.type == TonSignDataType.TEXT, + ) + payload_kind = b"txt" if msg.type == TonSignDataType.TEXT else b"bin" + digest = _build_bytes_digest( + payload_kind, + msg.payload, + msg.appdomain, + msg.timestamp, + wallet.address, + ) + elif msg.type == TonSignDataType.CELL: + try: + payload_cell = Cell.one_from_boc(msg.payload) + except Exception as exc: + raise wire.DataError("Invalid TON CELL payload.") from exc + schema = msg.schema + if schema is None: + raise wire.DataError("Schema is required for CELL payloads.") + from binascii import b2a_base64 + + await confirm_ton_sign_request( + ctx, + b2a_base64(msg.payload).decode(), + address, + msg.appdomain, + clear_sign=False, + ) + digest = _build_cell_digest( + payload_cell, + schema, + msg.appdomain, + msg.timestamp, + wallet.address, + ) + else: + raise wire.DataError("Invalid TON sign data type.") + signature = ed25519.sign(node.private_key(), digest) + return TonSignedData(signature=signature, digest=digest if __debug__ else None) + + +def _build_bytes_digest( + payload_kind: bytes, + payload: bytes, + appdomain: str, + timestamp: int, + address: TonAddress, +) -> bytes: + address_buffer = address.wc.to_bytes(4, "big") + address.get_hash_part() + domain_bytes = appdomain.encode("utf-8") + message = ( + b"\xff\xff" + + b"ton-connect/sign-data/" + + address_buffer + + len(domain_bytes).to_bytes(4, "big") + + domain_bytes + + timestamp.to_bytes(8, "big") + + payload_kind + + len(payload).to_bytes(4, "big") + + payload + ) + return hashlib.sha256(message).digest() + + +def _encode_appdomain_cell(appdomain: str) -> str: + labels = [label for label in appdomain.lower().split(".") if label] + tep81_domain = "\0".join(reversed(labels)) + "\0" + return tep81_domain + + +def _build_cell_digest( + payload_cell: Cell, + schema: str, + appdomain: str, + timestamp: int, + address: TonAddress, +) -> bytes: + return ( + begin_cell() + .store_uint(0x75569022, 32) + .store_uint(ton_utils.crc32(schema.encode("utf-8")), 32) + .store_uint(timestamp, 64) + .store_address(address) + .store_string_ref_tail(_encode_appdomain_cell(appdomain)) + .store_ref(payload_cell) + .end_cell() + .bytes_hash() + ) + + +def _validate_message(msg: TonSignData, address: str) -> None: + if msg.from_address is not None and msg.from_address != address: + raise wire.DataError("Invalid signer address provided.") + + if msg.type in (TonSignDataType.TEXT, TonSignDataType.BINARY): + if msg.schema is not None: + raise wire.DataError("Schema is only allowed for CELL payloads.") + + if msg.type == TonSignDataType.TEXT: + try: + msg.payload.decode("utf-8") + except UnicodeError: + raise wire.DataError("Invalid UTF-8 text payload.") from None + return + + if msg.type == TonSignDataType.CELL: + if msg.schema is None: + raise wire.DataError("Schema is required for CELL payloads.") + if len(msg.appdomain) > 126: + raise wire.DataError("Domain too long.") + return + + raise wire.DataError("Invalid TON sign data type.") diff --git a/core/src/apps/ton/sign_proof.py b/core/src/apps/ton/sign_proof.py index 9b70546b17..4db38c087f 100644 --- a/core/src/apps/ton/sign_proof.py +++ b/core/src/apps/ton/sign_proof.py @@ -51,17 +51,15 @@ async def sign_proof( # from trezor.ui.layouts import confirm_ton_connect # await confirm_ton_connect(ctx, msg.appdomain.decode("UTF-8"), address, msg.comment.decode("UTF-8")) - from trezor.ui.layouts import confirm_ton_signverify + from trezor.ui.layouts import confirm_ton_sign_request - if msg.appdomain is None: - raise ValueError("Domain cannot be None") - await confirm_ton_signverify( + if not msg.appdomain: + raise ValueError("Domain cannot be Empty") + await confirm_ton_sign_request( ctx, - "TON", msg.comment.decode("UTF-8") if msg.comment else "", address, msg.appdomain.decode("UTF-8"), - verify=False, ) ton_proof_prefix = "ton-proof-item-v2/" diff --git a/core/src/apps/ton/tonsdk/boc/_builder.py b/core/src/apps/ton/tonsdk/boc/_builder.py index 7219d2e140..816451b6b7 100755 --- a/core/src/apps/ton/tonsdk/boc/_builder.py +++ b/core/src/apps/ton/tonsdk/boc/_builder.py @@ -3,6 +3,8 @@ class Builder: + SNAKE_DATA_CHUNK_BYTES = 127 + def __init__(self): self.bits = BitString(1023) self.refs = [] @@ -57,6 +59,28 @@ def store_bytes(self, value): self.bits.write_bytes(value) return self + def store_string_tail(self, value): + if isinstance(value, str): + value = value.encode("utf-8") + elif isinstance(value, bytearray): + value = bytes(value) + elif not isinstance(value, bytes): + raise TypeError("store_string_tail expects str or bytes-like input") + if not value: + return self + + chunk_size = self.SNAKE_DATA_CHUNK_BYTES + self.store_bytes(value[:chunk_size]) + + if len(value) <= chunk_size: + return self + + tail = begin_cell().store_string_tail(value[chunk_size:]).end_cell() + return self.store_ref(tail) + + def store_string_ref_tail(self, value): + return self.store_ref(begin_cell().store_string_tail(value).end_cell()) + def store_bit_string(self, value): self.bits.write_bit_string(value) return self diff --git a/core/src/apps/ton/tonsdk/boc/_cell.py b/core/src/apps/ton/tonsdk/boc/_cell.py index 2c0fc78542..2ae020a5f1 100755 --- a/core/src/apps/ton/tonsdk/boc/_cell.py +++ b/core/src/apps/ton/tonsdk/boc/_cell.py @@ -318,7 +318,7 @@ def parse_boc_header(serialized_boc): def deserialize_boc(serialized_boc): - if type(serialized_boc) == str: + if type(serialized_boc) is str: serialized_boc = unhexlify(serialized_boc) header = parse_boc_header(serialized_boc) diff --git a/core/src/apps/ton/tonsdk/boc/_dict_builder.py b/core/src/apps/ton/tonsdk/boc/_dict_builder.py index bde02959ec..8a85f49403 100755 --- a/core/src/apps/ton/tonsdk/boc/_dict_builder.py +++ b/core/src/apps/ton/tonsdk/boc/_dict_builder.py @@ -8,18 +8,24 @@ def __init__(self, key_size: int): self.items = {} self.ended = False + def _ensure_not_ended(self): + if self.ended: + raise RuntimeError("Already ended") + def store_cell(self, index, value: Cell): - assert self.ended is False, "Already ended" - if type(index) == bytes: + self._ensure_not_ended() + if type(index) is bytes: index = int(index.hex(), 16) - assert type(index) == int, "Invalid index type" - assert not (index in self.items), f"Item {index} already exist" + if type(index) is not int: + raise TypeError("Invalid index type") + if index in self.items: + raise ValueError(f"Item {index} already exists") self.items[index] = value return self def store_ref(self, index, value: Cell): - assert self.ended is False, "Already ended" + self._ensure_not_ended() cell = Cell() cell.refs.append(value) @@ -27,7 +33,7 @@ def store_ref(self, index, value: Cell): return self def end_dict(self) -> Cell: - assert self.ended is False, "Already ended" + self._ensure_not_ended() self.ended = True if not self.items: return Cell() # ? @@ -38,8 +44,9 @@ def default_serializer(src, dest): return serialize_dict(self.items, self.key_size, default_serializer) def end_cell(self) -> Cell: - assert self.ended is False, "Already ended" - assert self.items, "Dict is empty" + self._ensure_not_ended() + if not self.items: + raise ValueError("Dict is empty") return self.end_dict() diff --git a/core/src/apps/ton/tonsdk/boc/dict/serialize_dict.py b/core/src/apps/ton/tonsdk/boc/dict/serialize_dict.py index 883ead95a0..231d7d37a7 100755 --- a/core/src/apps/ton/tonsdk/boc/dict/serialize_dict.py +++ b/core/src/apps/ton/tonsdk/boc/dict/serialize_dict.py @@ -23,7 +23,8 @@ def remove_prefix_map(src, length): def fork_map(src): - assert len(src) > 0, "Internal inconsistency" + if not src: + raise RuntimeError("Internal inconsistency") left = {} right = {} for k in src: @@ -32,13 +33,16 @@ def fork_map(src): else: right[k[1:]] = src[k] - assert len(left) > 0, "Internal inconsistency. Left empty." - assert len(right) > 0, "Internal inconsistency. Left empty." + if not left: + raise RuntimeError("Internal inconsistency. Left empty.") + if not right: + raise RuntimeError("Internal inconsistency. Right empty.") return left, right def build_node(src): - assert len(src) > 0, "Internal inconsistency" + if not src: + raise RuntimeError("Internal inconsistency") if len(src) == 1: return {"type": "leaf", "value": list(src.values())[0]} @@ -47,7 +51,8 @@ def build_node(src): def build_edge(src): - assert len(src) > 0, "Internal inconsistency" + if not src: + raise RuntimeError("Internal inconsistency") label = find_common_prefix(list(src.keys())) return {"label": label, "node": build_node(remove_prefix_map(src, len(label)))} diff --git a/core/src/apps/ton/tonsdk/contract/token/nft/nft_utils.py b/core/src/apps/ton/tonsdk/contract/token/nft/nft_utils.py index b23b80599a..603cda049f 100644 --- a/core/src/apps/ton/tonsdk/contract/token/nft/nft_utils.py +++ b/core/src/apps/ton/tonsdk/contract/token/nft/nft_utils.py @@ -24,7 +24,8 @@ def create_offchain_uri_cell(uri): def parse_offchain_uri_cell(cell): - assert cell.bits[0] == OFFCHAIN_CONTENT_PREFIX, "Invalid offchain uri cell" + if len(cell.bits) == 0 or cell.bits[0] != OFFCHAIN_CONTENT_PREFIX: + raise ValueError("Invalid offchain uri cell") length = 0 c = cell while c: diff --git a/core/src/apps/ton/tonsdk/utils/__init__.py b/core/src/apps/ton/tonsdk/utils/__init__.py index ff6a31b33a..c090bae84c 100755 --- a/core/src/apps/ton/tonsdk/utils/__init__.py +++ b/core/src/apps/ton/tonsdk/utils/__init__.py @@ -3,6 +3,7 @@ compare_bytes, concat_bytes, crc16, + crc32, crc32c, int_to_hex, move_to_end, @@ -17,6 +18,7 @@ "tree_walk", "crc32c", "crc16", + "crc32", "read_n_bytes_uint_from_array", "compare_bytes", "int_to_hex", diff --git a/core/src/apps/ton/tonsdk/utils/_utils.py b/core/src/apps/ton/tonsdk/utils/_utils.py index e2f52ef356..b9585bd01c 100755 --- a/core/src/apps/ton/tonsdk/utils/_utils.py +++ b/core/src/apps/ton/tonsdk/utils/_utils.py @@ -68,6 +68,25 @@ def crc32c(bytes_array): return bytes(arr) +def crc32(bytes_arr) -> int: + POLY = 0xEDB88320 + + crc = 0xFFFFFFFF + + for n in range(len(bytes_arr)): + crc ^= bytes_arr[n] + crc = (crc >> 1) ^ POLY if crc & 1 else crc >> 1 + crc = (crc >> 1) ^ POLY if crc & 1 else crc >> 1 + crc = (crc >> 1) ^ POLY if crc & 1 else crc >> 1 + crc = (crc >> 1) ^ POLY if crc & 1 else crc >> 1 + crc = (crc >> 1) ^ POLY if crc & 1 else crc >> 1 + crc = (crc >> 1) ^ POLY if crc & 1 else crc >> 1 + crc = (crc >> 1) ^ POLY if crc & 1 else crc >> 1 + crc = (crc >> 1) ^ POLY if crc & 1 else crc >> 1 + + return crc ^ 0xFFFFFFFF + + def crc16(data): POLY = 0x1021 reg = 0 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 f24acfcbd1..ca313bab1a 100644 --- a/core/src/apps/ur_registry/chains/base_sign_request.py +++ b/core/src/apps/ur_registry/chains/base_sign_request.py @@ -174,7 +174,8 @@ def get_address_n(self) -> list[int]: async def common_check(self): key_path: CryptoKeyPath | None = self.derivation_path - assert key_path is not None + if key_path is None: + raise Exception("Missing derivation path") if not key_path.source_fingerprint: raise Exception("Missing source_fingerprint") diff --git a/core/src/apps/ur_registry/chains/bitcoin/psbt/psbt.py b/core/src/apps/ur_registry/chains/bitcoin/psbt/psbt.py index 2404d68d5b..5e961aeb69 100644 --- a/core/src/apps/ur_registry/chains/bitcoin/psbt/psbt.py +++ b/core/src/apps/ur_registry/chains/bitcoin/psbt/psbt.py @@ -800,8 +800,10 @@ def get_txout(self) -> CTxOut: :returns: The CTxOut """ - assert self.amount is not None - assert len(self.script) != 0 + if self.amount is None: + raise Exception("Output amount is required") + if len(self.script) == 0: + raise Exception("Output script is required") return CTxOut(self.amount, self.script) @@ -1067,7 +1069,8 @@ def serialize(self) -> bytes: r += serialize_HDKeypath(self.xpub, ser_compact_size(PSBT.PSBT_GLOBAL_XPUB)) if self.version >= 2: - assert self.tx_version is not None + if self.tx_version is None: + raise Exception("PSBT_GLOBAL_TX_VERSION is required in PSBTv2") r += ser_string(ser_compact_size(PSBT.PSBT_GLOBAL_TX_VERSION)) r += ser_string(struct.pack(" CTransaction: if not self.tx.is_null(): return self.tx - assert self.tx_version is not None + if self.tx_version is None: + raise Exception("PSBT_GLOBAL_TX_VERSION is required in PSBTv2") tx = CTransaction() tx.nVersion = self.tx_version self.nLockTime = self.compute_lock_time() for psbt_in in self.inputs: - assert psbt_in.prev_txid is not None - assert psbt_in.prev_out is not None - assert psbt_in.sequence is not None + if psbt_in.prev_txid is None: + raise Exception("Previous TXID is required in PSBTv2") + if psbt_in.prev_out is None: + raise Exception("Previous output's index is required in PSBTv2") + if psbt_in.sequence is None: + raise Exception("Input sequence is required in PSBTv2") txin = CTxIn( COutPoint(uint256_from_str(psbt_in.prev_txid), psbt_in.prev_out), @@ -1211,7 +1218,8 @@ def get_unsigned_tx(self) -> CTransaction: tx.vin.append(txin) for psbt_out in self.outputs: - assert psbt_out.amount is not None + if psbt_out.amount is None: + raise Exception("PSBT_OUTPUT_AMOUNT is required in PSBTv2") txout = CTxOut(psbt_out.amount, psbt_out.script) tx.vout.append(txout) diff --git a/core/src/apps/ur_registry/chains/bitcoin/transaction.py b/core/src/apps/ur_registry/chains/bitcoin/transaction.py index 83a47c3174..df57d4c683 100644 --- a/core/src/apps/ur_registry/chains/bitcoin/transaction.py +++ b/core/src/apps/ur_registry/chains/bitcoin/transaction.py @@ -255,9 +255,12 @@ async def run(self): [] ) # Note down which inputs whose signatures we're going to ignore for input_num, psbt_in in enumerate(psbt.inputs): - assert psbt_in.prev_txid is not None - assert psbt_in.prev_out is not None - assert psbt_in.sequence is not None + if psbt_in.prev_txid is None: + raise wire.DataError("Missing previous transaction ID") + if psbt_in.prev_out is None: + raise wire.DataError("Missing previous output index") + if psbt_in.sequence is None: + raise wire.DataError("Missing input sequence") txinputtype = TxInputType( prev_hash=bytes(reversed(psbt_in.prev_txid)), @@ -473,7 +476,8 @@ async def run(self): self.signatures: List[bytes | None] = [None] * len(inputs) # Sign the transaction - assert psbt.tx_version is not None + if psbt.tx_version is None: + raise wire.DataError("Missing transaction version") # if __debug__: # utils.mem_trace(__name__, 3) mods = utils.unimport_begin() @@ -498,8 +502,10 @@ async def run(self): utils.unimport_end(mods) # pyright: on await wire.QR_CONTEXT.interact_stop() # signal finshed - assert messages.TxRequest.is_type_of(res) - assert res.request_type == RequestType.TXFINISHED + if not messages.TxRequest.is_type_of(res): + raise RuntimeError("Invalid transaction request") + if res.request_type != RequestType.TXFINISHED: + raise RuntimeError("Transaction signing did not finish") # if __debug__: # utils.mem_trace(__name__, 5) self._retrieval_signatures(res) @@ -513,7 +519,8 @@ async def run(self): for pubkey in psbt_in.hd_keypaths.keys(): fp = psbt_in.hd_keypaths[pubkey].fingerprint if fp == master_fp and pubkey not in psbt_in.partial_sigs: - assert sig is not None, "signature should not be None" + if sig is None: + raise RuntimeError("Signature missing") psbt_in.partial_sigs[pubkey] = sig + b"\x01" if __debug__: import binascii @@ -528,7 +535,8 @@ async def run(self): ) if len(psbt_in.tap_internal_key) > 0 and len(psbt_in.tap_key_sig) == 0: # Assume key path sig - assert sig is not None, "signature should not be None" + if sig is None: + raise RuntimeError("Signature missing") psbt_in.tap_key_sig = sig if __debug__: import binascii @@ -556,7 +564,8 @@ async def run(self): async def interact(self): - assert self.tx is not None, "transaction should not be None" + if self.tx is None: + raise RuntimeError("Transaction missing") current_tx = TransactionType( inputs=self.inputs, outputs=self.outputs, @@ -579,10 +588,12 @@ async def interact(self): if __debug__: print(f"tx request type: {res.request_type}") - assert res.details is not None, "device did not provide details" + if res.details is None: + raise RuntimeError("Device did not provide details") if res.request_type in (RequestType.TXINPUT,): - assert res.details.request_index is not None + if res.details.request_index is None: + raise RuntimeError("Device did not provide request index") # pyright: off msg = messages.TxAckInput( tx=messages.TxAckInputWrapper( @@ -622,7 +633,8 @@ async def interact(self): ) # pyright: on elif res.request_type == RequestType.TXOUTPUT: - assert res.details.request_index is not None + if res.details.request_index is None: + raise RuntimeError("Device did not provide request index") msg = messages.TxAckOutput( tx=messages.TxAckOutputWrapper( output=messages.TxOutput( @@ -662,9 +674,8 @@ def _retrieval_signatures(self, res: messages.TxRequest) -> None: if __debug__: print(f"got signature at index: {res.serialized.signature_index}") if res.serialized.signature_index is not None: - assert ( - res.serialized.signature is not None - ), "signature should not be None" + if res.serialized.signature is None: + raise RuntimeError("Signature missing") idx = res.serialized.signature_index sig = res.serialized.signature if self.signatures[idx] is not None: diff --git a/core/src/apps/ur_registry/chains/ethereum/eip1559_transaction.py b/core/src/apps/ur_registry/chains/ethereum/eip1559_transaction.py index 91fb606d4f..776cef9c00 100644 --- a/core/src/apps/ur_registry/chains/ethereum/eip1559_transaction.py +++ b/core/src/apps/ur_registry/chains/ethereum/eip1559_transaction.py @@ -110,9 +110,8 @@ async def interact(self): break try: if messages.EthereumTxRequestOneKey.is_type_of(response): - assert ( - FeeMarketEIP1559Transaction.CALL_DATA is not None - ), "CALL_DATA is None" + if FeeMarketEIP1559Transaction.CALL_DATA is None: + raise RuntimeError("CALL_DATA is None") request_data_length = response.data_length response = messages.EthereumTxAckOneKey( data_chunk=FeeMarketEIP1559Transaction.CALL_DATA[ diff --git a/core/src/apps/ur_registry/chains/ethereum/eth_sign_request.py b/core/src/apps/ur_registry/chains/ethereum/eth_sign_request.py index 18daef8443..9d066ade3c 100644 --- a/core/src/apps/ur_registry/chains/ethereum/eth_sign_request.py +++ b/core/src/apps/ur_registry/chains/ethereum/eth_sign_request.py @@ -192,7 +192,8 @@ def get_address_n(self) -> list[int]: async def common_check(self): key_path: CryptoKeyPath | None = self.derivation_path - assert key_path is not None + if key_path is None: + raise Exception("Missing derivation path") if not key_path.source_fingerprint: raise Exception("Missing source_fingerprint") diff --git a/core/src/apps/ur_registry/chains/ethereum/legacy_transaction.py b/core/src/apps/ur_registry/chains/ethereum/legacy_transaction.py index ce47ad43bf..811d629f30 100644 --- a/core/src/apps/ur_registry/chains/ethereum/legacy_transaction.py +++ b/core/src/apps/ur_registry/chains/ethereum/legacy_transaction.py @@ -15,7 +15,8 @@ def __init__(self, req: EthSignRequest): self.qr = None self.encoder = None - # Format: rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s]) + # Format: rlp([nonce, gasPrice, gasLimit, to, value, data]) + # or rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0]) for EIP-155 @staticmethod def fromSerializedTx(serialized, chainId, address_n): tx = decode(serialized) @@ -99,9 +100,8 @@ async def interact(self): break try: if messages.EthereumTxRequestOneKey.is_type_of(response): - assert ( - EthereumSignTxTransacion.CALL_DATA is not None - ), "CALL_DATA is None" + if EthereumSignTxTransacion.CALL_DATA is None: + raise RuntimeError("CALL_DATA is None") request_data_length = response.data_length response = messages.EthereumTxAckOneKey( data_chunk=EthereumSignTxTransacion.CALL_DATA[ 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 79090f040d..c7e31c42b9 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 @@ -116,7 +116,8 @@ async def run(self): root_fingerprint, ) keys.append(hdkey) - assert root_fingerprint is not None, "Root fingerprint should not be None" + if root_fingerprint is None: + raise ValueError("Root fingerprint should not be None") name = helpers.reveal_name(wire.QR_CONTEXT, root_fingerprint) cma = CryptoMultiAccounts( root_fingerprint, diff --git a/core/src/apps/ur_registry/chains/hardware_requests/hardware_call.py b/core/src/apps/ur_registry/chains/hardware_requests/hardware_call.py index 963f896151..be7b29a4fa 100644 --- a/core/src/apps/ur_registry/chains/hardware_requests/hardware_call.py +++ b/core/src/apps/ur_registry/chains/hardware_requests/hardware_call.py @@ -72,7 +72,8 @@ async def common_check(self): resp = await bitcoin_get_public_key.get_public_key(QR_CONTEXT, btc_pubkey_msg) # pyright: on expected_fingerprint = self.get_xfp() - assert resp.root_fingerprint is not None, "Root fingerprint should not be None" + if resp.root_fingerprint is None: + raise ValueError("Root fingerprint should not be None") xfp = hexlify(int.to_bytes(resp.root_fingerprint, 4, "big")).decode() if xfp != expected_fingerprint: raise MismatchError( diff --git a/core/src/apps/ur_registry/crypto_multi_accounts.py b/core/src/apps/ur_registry/crypto_multi_accounts.py index e76bf38bb5..31fa0c12d8 100644 --- a/core/src/apps/ur_registry/crypto_multi_accounts.py +++ b/core/src/apps/ur_registry/crypto_multi_accounts.py @@ -194,9 +194,8 @@ async def generate_crypto_multi_accounts(ctx: wire.Context) -> UREncoder: ] ) - assert ( - btc_legacy_pub.root_fingerprint is not None - ), "Root fingerprint should not be None" + if btc_legacy_pub.root_fingerprint is None: + raise ValueError("Root fingerprint should not be None") name = helpers.reveal_name(ctx, btc_legacy_pub.root_fingerprint) cma = CryptoMultiAccounts( diff --git a/core/src/apps/ur_registry/helpers.py b/core/src/apps/ur_registry/helpers.py index 1e71119bc6..8192014e01 100644 --- a/core/src/apps/ur_registry/helpers.py +++ b/core/src/apps/ur_registry/helpers.py @@ -26,7 +26,8 @@ def generate_HDKey( note: str | None = None, ) -> CryptoHDKey: hdkey = CryptoHDKey() - assert root_fingerprint is not None, "Root fingerprint should not be None" + if root_fingerprint is None: + raise ValueError("Root fingerprint should not be None") hdkey.new_extended_key( False, pubkey.node.public_key, @@ -96,9 +97,8 @@ def generate_hdkey_ETHStandard( ctx, pubkey: PublicKey, eth_only: bool = True ) -> CryptoHDKey: if eth_only: - assert ( - pubkey.root_fingerprint is not None - ), "Root fingerprint should not be None" + if pubkey.root_fingerprint is None: + raise ValueError("Root fingerprint should not be None") name = reveal_name(ctx, pubkey.root_fingerprint, True) else: name = None diff --git a/core/src/apps/ur_registry/ur_py/ur/bytewords.py b/core/src/apps/ur_registry/ur_py/ur/bytewords.py index a530776ce9..57c8439839 100755 --- a/core/src/apps/ur_registry/ur_py/ur/bytewords.py +++ b/core/src/apps/ur_registry/ur_py/ur/bytewords.py @@ -136,7 +136,7 @@ def encode(style, bytes): elif style == Bytewords_Style_minimal: return encode_minimal(bytes) else: - assert False + raise ValueError("Invalid Bytewords style.") @staticmethod def decode(style, str): @@ -147,4 +147,4 @@ def decode(style, str): elif style == Bytewords_Style_minimal: return decode(str, 0, 2) else: - assert False + raise ValueError("Invalid Bytewords style.") diff --git a/core/src/apps/ur_registry/ur_py/ur/fountain_decoder.py b/core/src/apps/ur_registry/ur_py/ur/fountain_decoder.py index 9d547bb331..5df1d982e8 100755 --- a/core/src/apps/ur_registry/ur_py/ur/fountain_decoder.py +++ b/core/src/apps/ur_registry/ur_py/ur/fountain_decoder.py @@ -270,7 +270,7 @@ def result_description(self): elif self.is_failure(): return "Exception: {}".format(self.result) else: - assert False + raise RuntimeError("Invalid fountain decoder result state") def print_part(self, p): if __debug__: diff --git a/core/src/apps/ur_registry/ur_py/ur/fountain_encoder.py b/core/src/apps/ur_registry/ur_py/ur/fountain_encoder.py index 4a4024ec5a..5e622668a1 100755 --- a/core/src/apps/ur_registry/ur_py/ur/fountain_encoder.py +++ b/core/src/apps/ur_registry/ur_py/ur/fountain_encoder.py @@ -92,7 +92,8 @@ def description(self): class FountainEncoder: def __init__(self, message, max_fragment_len, first_seq_num=0, min_fragment_len=10): - assert len(message) <= MAX_UINT32 + if len(message) > MAX_UINT32: + raise ValueError("Message length exceeds maximum") self.message_len = len(message) self.checksum = crc32_int(message) self.fragment_len = FountainEncoder.find_nominal_fragment_length( @@ -103,9 +104,12 @@ def __init__(self, message, max_fragment_len, first_seq_num=0, min_fragment_len= @staticmethod def find_nominal_fragment_length(message_len, min_fragment_len, max_fragment_len): - assert message_len > 0 - assert min_fragment_len > 0 - assert max_fragment_len >= min_fragment_len + if message_len <= 0: + raise ValueError("Message length must be positive") + if min_fragment_len <= 0: + raise ValueError("Minimum fragment length must be positive") + if max_fragment_len < min_fragment_len: + raise ValueError("Maximum fragment length must not be smaller than minimum") max_fragment_count = message_len // min_fragment_len fragment_len = None @@ -114,7 +118,8 @@ def find_nominal_fragment_length(message_len, min_fragment_len, max_fragment_len if fragment_len <= max_fragment_len: break - assert fragment_len is not None + if fragment_len is None: + raise ValueError("Unable to determine fragment length") return fragment_len @staticmethod diff --git a/core/src/apps/ur_registry/ur_py/ur/random_sampler.py b/core/src/apps/ur_registry/ur_py/ur/random_sampler.py index 34fb93b629..cbb10b677a 100755 --- a/core/src/apps/ur_registry/ur_py/ur/random_sampler.py +++ b/core/src/apps/ur_registry/ur_py/ur/random_sampler.py @@ -9,11 +9,13 @@ class RandomSampler: def __init__(self, probs): for p in probs: - assert p > 0 + if p <= 0: + raise ValueError("Probabilities must be positive") # Normalize given probabilities total = sum(probs) - assert total > 0 + if total <= 0: + raise ValueError("Probability total must be positive") n = len(probs) diff --git a/core/src/apps/ur_registry/ur_py/ur/utils.py b/core/src/apps/ur_registry/ur_py/ur/utils.py index 49fe0072a6..f1f604e72f 100755 --- a/core/src/apps/ur_registry/ur_py/ur/utils.py +++ b/core/src/apps/ur_registry/ur_py/ur/utils.py @@ -69,7 +69,8 @@ def join_bytes(list_of_ba): def xor_into(target, source): count = len(target) - assert count == len(source) # Must be the same length + if count != len(source): + raise ValueError("Byte arrays must be the same length") for i in range(count): target[i] ^= source[i] diff --git a/core/src/apps/webauthn/remove_resident_credential.py b/core/src/apps/webauthn/remove_resident_credential.py index ba1694c7a2..42e7022ead 100644 --- a/core/src/apps/webauthn/remove_resident_credential.py +++ b/core/src/apps/webauthn/remove_resident_credential.py @@ -41,6 +41,7 @@ async def remove_resident_credential( if not await confirm_webauthn(ctx, ConfirmRemoveCredential(cred)): raise wire.ActionCancelled - assert cred.index is not None + if cred.index is None: + raise wire.ProcessError("Invalid credential index.") storage.resident_credentials.delete(cred.index) return Success(message="Credential removed") diff --git a/core/src/apps/workflow_handlers.py b/core/src/apps/workflow_handlers.py index 6ab9708d39..e07d068f23 100644 --- a/core/src/apps/workflow_handlers.py +++ b/core/src/apps/workflow_handlers.py @@ -250,6 +250,8 @@ def find_message_handler_module(msg_type: int) -> str: return "apps.ton.sign_message" if msg_type == MessageType.TonSignProof: return "apps.ton.sign_proof" + if msg_type == MessageType.TonSignData: + return "apps.ton.sign_data" # tron if msg_type == MessageType.TronGetAddress: diff --git a/core/src/apps/zcash/f4jumble.py b/core/src/apps/zcash/f4jumble.py index 44fbed8983..91390d3811 100644 --- a/core/src/apps/zcash/f4jumble.py +++ b/core/src/apps/zcash/f4jumble.py @@ -35,7 +35,8 @@ def H_round(i: int, left: memoryview, right: memoryview) -> None: def f4jumble(message: memoryview) -> None: - assert 48 <= len(message) <= 4194368 + if not 48 <= len(message) <= 4194368: + raise ValueError("Invalid F4Jumble message length") left_length = min(HASH_LENGTH, len(message) // 2) left = message[:left_length] @@ -47,7 +48,8 @@ def f4jumble(message: memoryview) -> None: def f4unjumble(message: memoryview) -> None: - assert 48 <= len(message) <= 4194368 + if not 48 <= len(message) <= 4194368: + raise ValueError("Invalid F4Jumble message length") left_length = min(HASH_LENGTH, len(message) // 2) left = message[:left_length] diff --git a/core/src/apps/zcash/hasher.py b/core/src/apps/zcash/hasher.py index fcfd47f514..d65cdf9b9d 100644 --- a/core/src/apps/zcash/hasher.py +++ b/core/src/apps/zcash/hasher.py @@ -9,6 +9,7 @@ from trezor.crypto.hashlib import blake2b from trezor.utils import HashWriter, empty_bytearray +from trezor.wire import DataError from apps.bitcoin.common import SigHashType from apps.bitcoin.writers import ( @@ -45,7 +46,8 @@ def __init__(self, tx: SignTx | PrevTx): self.sapling = SaplingHasher() self.orchard = OrchardHasher() - assert tx.branch_id is not None # checked in sanitize_sign_tx + if tx.branch_id is None: + raise DataError("Missing branch ID") tx_hash_person = empty_bytearray(16) write_bytes_fixed(tx_hash_person, b"ZcashTxHash_", 12) write_uint32(tx_hash_person, tx.branch_id) @@ -124,9 +126,12 @@ class HeaderHasher: def __init__(self, tx: SignTx | PrevTx): h = HashWriter(blake2b(outlen=32, personal=b"ZTxIdHeadersHash")) - assert tx.version_group_id is not None - assert tx.branch_id is not None # checked in sanitize_* - assert tx.expiry is not None + if tx.version_group_id is None: + raise DataError("Missing version group ID") + if tx.branch_id is None: + raise DataError("Missing branch ID") + if tx.expiry is None: + raise DataError("Missing expiry") write_uint32(h, tx.version | (1 << 31)) # T.1a write_uint32(h, tx.version_group_id) # T.1b @@ -210,8 +215,8 @@ def sig_digest( """ if self.empty: - assert txi is None - assert script_pubkey is None + if txi is not None or script_pubkey is not None: + raise RuntimeError("Unexpected transparent input") return self.digest() h = HashWriter(blake2b(outlen=32, personal=b"ZTxIdTranspaHash")) @@ -241,7 +246,8 @@ def _txin_sig_digest( h = HashWriter(blake2b(outlen=32, personal=b"Zcash___TxInHash")) if txi is not None: - assert script_pubkey is not None + if script_pubkey is None: + raise RuntimeError("Missing script pubkey") write_prevout(h, txi) # 2.Sg.i write_uint64(h, txi.amount) # 2.Sg.ii diff --git a/core/src/apps/zcash/signer.py b/core/src/apps/zcash/signer.py index faf5ae929a..2a12955e1e 100644 --- a/core/src/apps/zcash/signer.py +++ b/core/src/apps/zcash/signer.py @@ -100,9 +100,12 @@ def write_tx_header( self, w: Writer, tx: SignTx | PrevTx, witness_marker: bool ) -> None: # defined in ZIP-225 (see https://zips.z.cash/zip-0225) - assert tx.version_group_id is not None - assert tx.branch_id is not None # checked in sanitize_* - assert tx.expiry is not None + if tx.version_group_id is None: + raise DataError("Missing version group ID") + if tx.branch_id is None: + raise DataError("Missing branch ID") + if tx.expiry is None: + raise DataError("Missing expiry") write_uint32_le(w, tx.version | OVERWINTERED) # nVersion | fOverwintered write_uint32_le(w, tx.version_group_id) # nVersionGroupId @@ -120,7 +123,8 @@ def write_tx_footer(self, w: Writer, tx: SignTx | PrevTx) -> None: def output_derive_script(self, txo: TxOutput) -> bytes: # unified addresses if txo.address is not None and txo.address[0] == "u": - assert txo.script_type is OutputScriptType.PAYTOADDRESS + if txo.script_type is not OutputScriptType.PAYTOADDRESS: + raise DataError("Invalid output script type") receivers = unified_addresses.decode(txo.address, self.coin) if Typecode.P2PKH in receivers: diff --git a/core/src/apps/zcash/unified_addresses.py b/core/src/apps/zcash/unified_addresses.py index b820cca1ab..bd9df76c87 100644 --- a/core/src/apps/zcash/unified_addresses.py +++ b/core/src/apps/zcash/unified_addresses.py @@ -48,15 +48,18 @@ def prefix(coin: CoinInfo) -> str: def padding(hrp: str) -> bytes: - assert len(hrp) <= 16 + if len(hrp) > 16: + raise ValueError("Address prefix is too long") return hrp.encode() + bytes(16 - len(hrp)) def encode(receivers: dict[Typecode, bytes], coin: CoinInfo) -> str: # multiple transparent receivers forbidden - assert not (Typecode.P2PKH in receivers and Typecode.P2SH in receivers) + if Typecode.P2PKH in receivers and Typecode.P2SH in receivers: + raise DataError("Multiple transparent receivers are forbidden") # at least one shielded address must be present - assert Typecode.SAPLING in receivers or Typecode.ORCHARD in receivers + if Typecode.SAPLING not in receivers and Typecode.ORCHARD not in receivers: + raise DataError("Unified address requires a shielded receiver") length = 16 # 16 bytes for padding for receiver_bytes in receivers.values(): @@ -87,9 +90,8 @@ def decode(addr_str: str, coin: CoinInfo) -> dict[int, bytes]: hrp, data, encoding = bech32_decode(addr_str, 1000) except ValueError: raise DataError("Bech32m decoding failed.") - assert hrp is not None # to satisfy typecheckers - assert data is not None # to satisfy typecheckers - assert encoding is not None # to satisfy typecheckers + if hrp is None or data is None or encoding is None: + raise DataError("Bech32m decoding failed.") if hrp != prefix(coin): raise DataError("Unexpected address prefix.") if encoding != Encoding.BECH32M: diff --git a/core/src/trezor/enums/MessageType.py b/core/src/trezor/enums/MessageType.py index 6f41ddb4b4..8f3d3e7997 100644 --- a/core/src/trezor/enums/MessageType.py +++ b/core/src/trezor/enums/MessageType.py @@ -392,6 +392,8 @@ TonSignProof = 11905 TonSignedProof = 11906 TonTxAck = 11907 + TonSignData = 11908 + TonSignedData = 11909 ScdoGetAddress = 12001 ScdoAddress = 12002 ScdoSignTx = 12003 diff --git a/core/src/trezor/enums/TonSignDataType.py b/core/src/trezor/enums/TonSignDataType.py new file mode 100644 index 0000000000..7bf7fe8f63 --- /dev/null +++ b/core/src/trezor/enums/TonSignDataType.py @@ -0,0 +1,7 @@ +# Automatically generated by pb2py +# fmt: off +# isort:skip_file + +TEXT = 0 +BINARY = 1 +CELL = 2 diff --git a/core/src/trezor/enums/__init__.py b/core/src/trezor/enums/__init__.py index 30caf210cb..9829615517 100644 --- a/core/src/trezor/enums/__init__.py +++ b/core/src/trezor/enums/__init__.py @@ -399,6 +399,8 @@ class MessageType(IntEnum): TonSignProof = 11905 TonSignedProof = 11906 TonTxAck = 11907 + TonSignData = 11908 + TonSignedData = 11909 ScdoGetAddress = 12001 ScdoAddress = 12002 ScdoSignTx = 12003 @@ -786,6 +788,11 @@ class TonWorkChain(IntEnum): BASECHAIN = 0 MASTERCHAIN = 1 + class TonSignDataType(IntEnum): + TEXT = 0 + BINARY = 1 + CELL = 2 + class TronMessageType(IntEnum): V1 = 1 V2 = 2 diff --git a/core/src/trezor/lvglui/scrs/template.py b/core/src/trezor/lvglui/scrs/template.py index 026b821fdd..15c1ea49f9 100644 --- a/core/src/trezor/lvglui/scrs/template.py +++ b/core/src/trezor/lvglui/scrs/template.py @@ -3127,19 +3127,12 @@ def __init__( class TonMessage(FullSizeWindow): def __init__( - self, - title, - address, - message, - domain, - primary_color, - icon_path, - verify: bool = False, + self, title, address, message, domain, primary_color, icon_path, clear_sign ): super().__init__( title, None, - _(i18n_keys.BUTTON__VERIFY) if verify else _(i18n_keys.BUTTON__SIGN), + _(i18n_keys.BUTTON__SIGN), _(i18n_keys.BUTTON__CANCEL), anim_dir=2, primary_color=primary_color, @@ -3153,13 +3146,25 @@ def __init__( self.long_message = True else: self.message = message + if not clear_sign: + self.warning_banner = Banner( + self.content_area, + 2, + _(i18n_keys.SECURITY__SOLANA_RAW_SIGNING_TX_WARNING), + ) + self.warning_banner.align_to(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 40) self.item_message = CardItem( self.content_area, _(i18n_keys.LIST_KEY__MESSAGE__COLON), self.message, "A:/res/group-icon-data.png", ) - self.item_message.align_to(self.title, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 40) + self.item_message.align_to( + self.title if clear_sign else self.warning_banner, + lv.ALIGN.OUT_BOTTOM_LEFT, + 0, + 40, + ) if self.long_message: self.show_full_message = NormalButton( self.item_message.content, _(i18n_keys.BUTTON__VIEW_DATA) diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py index ecf9aa7dc4..e1ac688dfe 100644 --- a/core/src/trezor/messages.py +++ b/core/src/trezor/messages.py @@ -69,6 +69,7 @@ def __getattr__(name: str) -> Any: from trezor.enums import StellarSignerType # noqa: F401 from trezor.enums import TezosBallotType # noqa: F401 from trezor.enums import TezosContractType # noqa: F401 + from trezor.enums import TonSignDataType # noqa: F401 from trezor.enums import TonWalletVersion # noqa: F401 from trezor.enums import TonWorkChain # noqa: F401 from trezor.enums import TronMessageType # noqa: F401 @@ -9483,6 +9484,58 @@ def __init__( def is_type_of(cls, msg: Any) -> TypeGuard["TonSignedProof"]: return isinstance(msg, cls) + class TonSignData(protobuf.MessageType): + address_n: "list[int]" + type: "TonSignDataType" + payload: "bytes" + schema: "str | None" + appdomain: "str" + timestamp: "int" + from_address: "str | None" + wallet_version: "TonWalletVersion" + wallet_id: "int" + workchain: "TonWorkChain" + is_bounceable: "bool" + is_testnet_only: "bool" + + def __init__( + self, + *, + type: "TonSignDataType", + payload: "bytes", + appdomain: "str", + timestamp: "int", + address_n: "list[int] | None" = None, + schema: "str | None" = None, + from_address: "str | None" = None, + wallet_version: "TonWalletVersion | None" = None, + wallet_id: "int | None" = None, + workchain: "TonWorkChain | None" = None, + is_bounceable: "bool | None" = None, + is_testnet_only: "bool | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["TonSignData"]: + return isinstance(msg, cls) + + class TonSignedData(protobuf.MessageType): + signature: "bytes | None" + digest: "bytes | None" + + def __init__( + self, + *, + signature: "bytes | None" = None, + digest: "bytes | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["TonSignedData"]: + return isinstance(msg, cls) + class TronGetAddress(protobuf.MessageType): address_n: "list[int]" show_display: "bool | None" diff --git a/core/src/trezor/qr.py b/core/src/trezor/qr.py index abd5e797df..00b060190c 100644 --- a/core/src/trezor/qr.py +++ b/core/src/trezor/qr.py @@ -259,6 +259,7 @@ async def camear_scan(): await callback_obj.transition_to( callback_obj.SCAN_STATE_IDLE ) + gc.collect() continue await loop.sleep(5) diff --git a/core/src/trezor/ui/layouts/lvgl/__init__.py b/core/src/trezor/ui/layouts/lvgl/__init__.py index b9994f71e7..30794d65b2 100644 --- a/core/src/trezor/ui/layouts/lvgl/__init__.py +++ b/core/src/trezor/ui/layouts/lvgl/__init__.py @@ -83,7 +83,7 @@ "show_error_no_interact", "confirm_ton_transfer", "confirm_ton_connect", - "confirm_ton_signverify", + "confirm_ton_sign_request", "confirm_unknown_token_transfer", "confirm_neo_token_transfer", "confirm_neo_vote", @@ -2479,35 +2479,29 @@ async def confirm_ton_connect( ) -async def confirm_ton_signverify( +async def confirm_ton_sign_request( ctx: wire.GenericContext, - coin: str, message: str, address: str, domain: str, - verify: bool, + clear_sign: bool = True, ) -> None: - if verify: - header = _(i18n_keys.TITLE__VERIFY_STR_MESSAGE).format(coin) - br_type = "verify_message" - else: - header = _(i18n_keys.TITLE__SIGN_STR_MESSAGE).format(coin) - br_type = "sign_message" + from trezor.lvglui.scrs.template import TonMessage await raise_if_cancelled( interact( ctx, TonMessage( - header, + _(i18n_keys.TITLE__SIGN_STR_MESSAGE).format("Ton"), address, message, domain, ctx.primary_color, ctx.icon_path, - verify, + clear_sign, ), - br_type, + "ton_sign_request", ButtonRequestType.Other, ) ) diff --git a/python/requirements-optional.txt b/python/requirements-optional.txt index d43d8be0a9..b006d1e2dc 100644 --- a/python/requirements-optional.txt +++ b/python/requirements-optional.txt @@ -1,4 +1,5 @@ hidapi >= 0.7.99.post20 web3 >= 4.8 Pillow -stellar-sdk>=4.0.0,<6.0.0 +stellar-sdk>=10.0.0,<14.0.0 +xdrlib3 diff --git a/python/src/trezorlib/cli/ton.py b/python/src/trezorlib/cli/ton.py index 5d9d9f3950..3ee134a2e7 100644 --- a/python/src/trezorlib/cli/ton.py +++ b/python/src/trezorlib/cli/ton.py @@ -22,14 +22,23 @@ from trezorlib import ton, tools from trezorlib.cli import with_client, ChoiceType from trezorlib import messages +import base64 + if TYPE_CHECKING: from ..client import TrezorClient -PATH_HELP = "BIP-32 path, e.g. m/44'/607'/0'/0'" +PATH_HELP = "BIP-32 path, e.g. m/44'/607'/0'" WORKCHAIN = { "base": messages.TonWorkChain.BASECHAIN, "master": messages.TonWorkChain.MASTERCHAIN, } + +SIGN_DATA_TYPE = { + "text": messages.TonSignDataType.TEXT, + "binary": messages.TonSignDataType.BINARY, + "cell": messages.TonSignDataType.CELL, +} + WALLET_VERSION = { # "v3r1": messages.TonWalletVersion.V3R1, # "v3r2": messages.TonWalletVersion.V3R2, @@ -186,4 +195,65 @@ def sign_proof(client: "TrezorClient", test_only ).signature.hex() - return {"signature": f"0x{signature}"} \ No newline at end of file + return {"signature": f"0x{signature}"} + + +@cli.command() +@click.option("-n", "--address", required=True, help=PATH_HELP) +@click.option("-t", "--data-type", type=ChoiceType(SIGN_DATA_TYPE), required=True) +@click.option("-p", "--payload", required=True, type=str) +@click.option("-d", "--appdomain", required=True, type=str) +@click.option("-ts", "--timestamp", required=True, type=int) +@click.option("-s", "--schema", type=str) +@click.option("-a", "--from-address", type=str) +@click.option("-v", "--wallet-version", type=ChoiceType(WALLET_VERSION), default="v4r2") +@click.option("-i", "--wallet-id", type=int, default=698983191) +@click.option("-w", "--workchain", type=ChoiceType(WORKCHAIN), default="base") +@click.option("-b", "--bounceable", is_flag=True) +@click.option("--test-only", is_flag=True) +@with_client +def sign_data( + client: "TrezorClient", + address: str, + data_type: messages.TonSignDataType, + payload: str, + appdomain: str, + timestamp: int, + schema: str, + from_address: str, + wallet_version: messages.TonWalletVersion, + wallet_id: int, + workchain: messages.TonWorkChain, + bounceable: bool, + test_only: bool, +): + """Sign Ton SignData payload.""" + address_n = tools.parse_path(address) + + if data_type == messages.TonSignDataType.TEXT: + payload_bytes = payload.encode("utf-8") + elif data_type == messages.TonSignDataType.BINARY: + payload_bytes = bytes.fromhex(payload) + else: + payload_bytes = base64.b64decode(payload) + + resp = ton.sign_data( + client, + address_n, + data_type, + payload_bytes, + appdomain, + timestamp, + schema, + from_address, + wallet_version, + wallet_id, + workchain, + bounceable, + test_only + ) + + signature_hex = resp.signature.hex() if resp.signature is not None else "" + digest_hex = resp.digest.hex() if resp.digest is not None else "" + + return {"signature": signature_hex, "digest": digest_hex} diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py index 6819cf93ab..be73713e32 100644 --- a/python/src/trezorlib/messages.py +++ b/python/src/trezorlib/messages.py @@ -408,6 +408,8 @@ class MessageType(IntEnum): TonSignProof = 11905 TonSignedProof = 11906 TonTxAck = 11907 + TonSignData = 11908 + TonSignedData = 11909 ScdoGetAddress = 12001 ScdoAddress = 12002 ScdoSignTx = 12003 @@ -853,6 +855,12 @@ class TonWorkChain(IntEnum): MASTERCHAIN = 1 +class TonSignDataType(IntEnum): + TEXT = 0 + BINARY = 1 + CELL = 2 + + class TronMessageType(IntEnum): V1 = 1 V2 = 2 @@ -11763,6 +11771,70 @@ def __init__( self.signature = signature +class TonSignData(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 11908 + FIELDS = { + 1: protobuf.Field("address_n", "uint32", repeated=True, required=False), + 2: protobuf.Field("type", "TonSignDataType", repeated=False, required=True), + 3: protobuf.Field("payload", "bytes", repeated=False, required=True), + 4: protobuf.Field("schema", "string", repeated=False, required=False), + 5: protobuf.Field("appdomain", "string", repeated=False, required=True), + 6: protobuf.Field("timestamp", "uint64", repeated=False, required=True), + 7: protobuf.Field("from_address", "string", repeated=False, required=False), + 8: protobuf.Field("wallet_version", "TonWalletVersion", repeated=False, required=False), + 9: protobuf.Field("wallet_id", "uint32", repeated=False, required=False), + 10: protobuf.Field("workchain", "TonWorkChain", repeated=False, required=False), + 11: protobuf.Field("is_bounceable", "bool", repeated=False, required=False), + 12: protobuf.Field("is_testnet_only", "bool", repeated=False, required=False), + } + + def __init__( + self, + *, + type: "TonSignDataType", + payload: "bytes", + appdomain: "str", + timestamp: "int", + address_n: Optional[Sequence["int"]] = None, + schema: Optional["str"] = None, + from_address: Optional["str"] = None, + wallet_version: Optional["TonWalletVersion"] = TonWalletVersion.V4R2, + wallet_id: Optional["int"] = 698983191, + workchain: Optional["TonWorkChain"] = TonWorkChain.BASECHAIN, + is_bounceable: Optional["bool"] = False, + is_testnet_only: Optional["bool"] = False, + ) -> None: + self.address_n: Sequence["int"] = address_n if address_n is not None else [] + self.type = type + self.payload = payload + self.appdomain = appdomain + self.timestamp = timestamp + self.schema = schema + self.from_address = from_address + self.wallet_version = wallet_version + self.wallet_id = wallet_id + self.workchain = workchain + self.is_bounceable = is_bounceable + self.is_testnet_only = is_testnet_only + + +class TonSignedData(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 11909 + FIELDS = { + 1: protobuf.Field("signature", "bytes", repeated=False, required=False), + 2: protobuf.Field("digest", "bytes", repeated=False, required=False), + } + + def __init__( + self, + *, + signature: Optional["bytes"] = None, + digest: Optional["bytes"] = None, + ) -> None: + self.signature = signature + self.digest = digest + + class TronGetAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 10501 FIELDS = { diff --git a/python/src/trezorlib/ton.py b/python/src/trezorlib/ton.py index c4308a0873..d0e68180e0 100644 --- a/python/src/trezorlib/ton.py +++ b/python/src/trezorlib/ton.py @@ -129,4 +129,38 @@ def sign_proof(client: "TrezorClient", bounceable=bounceable, is_test_only=test_only, ) - ) \ No newline at end of file + ) + + +@expect(messages.TonSignedData) +def sign_data( + client: "TrezorClient", + n: "Address", + type: messages.TonSignDataType, + payload: bytes, + appdomain: str, + timestamp: int, + schema: str = None, + from_address: str = None, + version: messages.TonWalletVersion = messages.TonWalletVersion.V4R2, + wallet_id: int = 698983191, + workchain: messages.TonWorkChain = messages.TonWorkChain.BASECHAIN, + bounceable: bool = False, + test_only: bool = False, +): + return client.call( + messages.TonSignData( + address_n=n, + type=type, + payload=payload, + schema=schema, + appdomain=appdomain, + timestamp=timestamp, + from_address=from_address, + wallet_version=version, + wallet_id=wallet_id, + workchain=workchain, + is_bounceable=bounceable, + is_testnet_only=test_only, + ) + )