2018-09-06 16:28:56 +00:00
|
|
|
/*
|
2019-06-17 18:27:55 +00:00
|
|
|
* This file is part of the Trezor project, https://trezor.io/
|
2018-09-06 16:28:56 +00:00
|
|
|
*
|
|
|
|
* Copyright (C) 2018 Pavol Rusnak <stick@satoshilabs.com>
|
|
|
|
*
|
|
|
|
* This library is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
|
|
* along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2022-01-25 21:54:31 +00:00
|
|
|
static bool fsm_ethereumCheckPath(uint32_t address_n_count,
|
|
|
|
const uint32_t *address_n, bool pubkey_export,
|
2023-02-03 13:50:29 +00:00
|
|
|
const EthereumNetworkInfo *network) {
|
|
|
|
if (ethereum_path_check(address_n_count, address_n, pubkey_export, network)) {
|
2022-01-25 21:54:31 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config_getSafetyCheckLevel() == SafetyCheckLevel_Strict) {
|
|
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Forbidden key path"));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return fsm_layoutPathWarning();
|
|
|
|
}
|
|
|
|
|
2023-02-03 13:50:29 +00:00
|
|
|
static const EthereumDefinitionsDecoded *get_definitions(
|
|
|
|
bool has_definitions, const EthereumDefinitions *definitions,
|
|
|
|
uint64_t chain_id, const char *to) {
|
|
|
|
const EncodedNetwork *encoded_network = NULL;
|
|
|
|
const EncodedToken *encoded_token = NULL;
|
|
|
|
if (has_definitions && definitions) {
|
|
|
|
if (definitions->has_encoded_network) {
|
|
|
|
encoded_network = &definitions->encoded_network;
|
|
|
|
}
|
|
|
|
if (definitions->has_encoded_token) {
|
|
|
|
encoded_token = &definitions->encoded_token;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ethereum_get_definitions(encoded_network, encoded_token, chain_id,
|
|
|
|
SLIP44_UNKNOWN, to);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const EthereumNetworkInfo *get_network_definition_only(
|
|
|
|
bool has_encoded_network, const EncodedNetwork *encoded_network,
|
|
|
|
const uint32_t slip44) {
|
|
|
|
const EncodedNetwork *en = NULL;
|
|
|
|
if (has_encoded_network) {
|
|
|
|
en = encoded_network;
|
|
|
|
}
|
|
|
|
|
|
|
|
const EthereumDefinitionsDecoded *defs =
|
|
|
|
ethereum_get_definitions(en, NULL, CHAIN_ID_UNKNOWN, slip44, NULL);
|
|
|
|
|
|
|
|
return defs ? defs->network : NULL;
|
|
|
|
}
|
|
|
|
|
2019-03-29 16:10:31 +00:00
|
|
|
void fsm_msgEthereumGetPublicKey(const EthereumGetPublicKey *msg) {
|
|
|
|
RESP_INIT(EthereumPublicKey);
|
|
|
|
|
|
|
|
CHECK_INITIALIZED
|
|
|
|
|
|
|
|
CHECK_PIN
|
|
|
|
|
|
|
|
// we use Bitcoin-like format for ETH
|
|
|
|
const CoinInfo *coin = fsm_getCoin(true, "Bitcoin");
|
|
|
|
if (!coin) return;
|
|
|
|
|
2023-03-22 10:57:05 +00:00
|
|
|
// Only allow m/44' and m/45' subtrees. This allows usage with _any_ SLIP-44
|
|
|
|
// (Ethereum or otherwise), plus the Casa multisig subtree. Anything else must
|
|
|
|
// go through (a) GetPublicKey or (b) a dedicated coin-specific message.
|
|
|
|
if (!msg->address_n_count || (msg->address_n[0] != (44 | PATH_HARDENED) &&
|
|
|
|
msg->address_n[0] != (45 | PATH_HARDENED))) {
|
|
|
|
fsm_sendFailure(FailureType_Failure_DataError,
|
|
|
|
_("Invalid path for EthereumGetPublicKey"));
|
|
|
|
layoutHome();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-29 16:10:31 +00:00
|
|
|
const char *curve = coin->curve_name;
|
|
|
|
uint32_t fingerprint;
|
2020-12-29 16:37:37 +00:00
|
|
|
HDNode *node = fsm_getDerivedNode(curve, msg->address_n, msg->address_n_count,
|
|
|
|
&fingerprint);
|
2019-03-29 16:10:31 +00:00
|
|
|
if (!node) return;
|
2021-07-09 14:33:26 +00:00
|
|
|
|
|
|
|
if (hdnode_fill_public_key(node) != 0) {
|
|
|
|
fsm_sendFailure(FailureType_Failure_ProcessError,
|
|
|
|
_("Failed to derive public key"));
|
|
|
|
layoutHome();
|
|
|
|
return;
|
|
|
|
}
|
2019-03-29 16:10:31 +00:00
|
|
|
|
|
|
|
if (msg->has_show_display && msg->show_display) {
|
|
|
|
layoutPublicKey(node->public_key);
|
|
|
|
if (!protectButton(ButtonRequestType_ButtonRequest_PublicKey, true)) {
|
|
|
|
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
|
|
|
layoutHome();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
resp->node.depth = node->depth;
|
|
|
|
resp->node.fingerprint = fingerprint;
|
|
|
|
resp->node.child_num = node->child_num;
|
|
|
|
resp->node.chain_code.size = 32;
|
|
|
|
memcpy(resp->node.chain_code.bytes, node->chain_code, 32);
|
|
|
|
resp->node.has_private_key = false;
|
|
|
|
resp->node.public_key.size = 33;
|
|
|
|
memcpy(resp->node.public_key.bytes, node->public_key, 33);
|
|
|
|
|
|
|
|
hdnode_serialize_public(node, fingerprint, coin->xpub_magic, resp->xpub,
|
|
|
|
sizeof(resp->xpub));
|
|
|
|
|
|
|
|
msg_write(MessageType_MessageType_EthereumPublicKey, resp);
|
|
|
|
layoutHome();
|
2019-01-14 15:27:59 +00:00
|
|
|
}
|
|
|
|
|
2021-10-21 15:23:59 +00:00
|
|
|
void fsm_msgEthereumSignTx(const EthereumSignTx *msg) {
|
2019-03-29 16:10:31 +00:00
|
|
|
CHECK_INITIALIZED
|
2018-05-03 14:47:37 +00:00
|
|
|
|
2019-03-29 16:10:31 +00:00
|
|
|
CHECK_PIN
|
2018-05-03 14:47:37 +00:00
|
|
|
|
2023-02-03 13:50:29 +00:00
|
|
|
const EthereumDefinitionsDecoded *defs =
|
|
|
|
get_definitions(msg->has_definitions, &msg->definitions, msg->chain_id,
|
|
|
|
msg->has_to ? msg->to : NULL);
|
|
|
|
|
|
|
|
if (!defs || !fsm_ethereumCheckPath(msg->address_n_count, msg->address_n,
|
|
|
|
false, defs->network)) {
|
2022-01-25 21:54:31 +00:00
|
|
|
layoutHome();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-29 16:10:31 +00:00
|
|
|
const HDNode *node = fsm_getDerivedNode(SECP256K1_NAME, msg->address_n,
|
|
|
|
msg->address_n_count, NULL);
|
|
|
|
if (!node) return;
|
2018-05-03 14:47:37 +00:00
|
|
|
|
2023-02-03 13:50:29 +00:00
|
|
|
ethereum_signing_init(msg, node, defs);
|
2018-05-03 14:47:37 +00:00
|
|
|
}
|
|
|
|
|
2021-10-21 15:23:59 +00:00
|
|
|
void fsm_msgEthereumSignTxEIP1559(const EthereumSignTxEIP1559 *msg) {
|
|
|
|
CHECK_INITIALIZED
|
|
|
|
|
|
|
|
CHECK_PIN
|
|
|
|
|
2023-02-03 13:50:29 +00:00
|
|
|
const EthereumDefinitionsDecoded *defs =
|
|
|
|
get_definitions(msg->has_definitions, &msg->definitions, msg->chain_id,
|
|
|
|
msg->has_to ? msg->to : NULL);
|
|
|
|
|
|
|
|
if (!defs || !fsm_ethereumCheckPath(msg->address_n_count, msg->address_n,
|
|
|
|
false, defs->network)) {
|
2022-01-25 21:54:31 +00:00
|
|
|
layoutHome();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-10-21 15:23:59 +00:00
|
|
|
const HDNode *node = fsm_getDerivedNode(SECP256K1_NAME, msg->address_n,
|
|
|
|
msg->address_n_count, NULL);
|
|
|
|
if (!node) return;
|
|
|
|
|
2023-02-03 13:50:29 +00:00
|
|
|
ethereum_signing_init_eip1559(msg, node, defs);
|
2018-05-03 14:47:37 +00:00
|
|
|
}
|
|
|
|
|
2021-10-21 15:23:59 +00:00
|
|
|
void fsm_msgEthereumTxAck(const EthereumTxAck *msg) {
|
2022-02-17 09:44:40 +00:00
|
|
|
CHECK_UNLOCKED
|
|
|
|
|
2021-10-21 15:23:59 +00:00
|
|
|
ethereum_signing_txack(msg);
|
2021-08-10 08:56:13 +00:00
|
|
|
}
|
|
|
|
|
2019-03-29 16:10:31 +00:00
|
|
|
void fsm_msgEthereumGetAddress(const EthereumGetAddress *msg) {
|
|
|
|
RESP_INIT(EthereumAddress);
|
|
|
|
|
|
|
|
CHECK_INITIALIZED
|
|
|
|
|
|
|
|
CHECK_PIN
|
|
|
|
|
2023-02-03 13:50:29 +00:00
|
|
|
uint32_t slip44 = (msg->address_n_count > 1)
|
|
|
|
? (msg->address_n[1] & PATH_UNHARDEN_MASK)
|
|
|
|
: SLIP44_UNKNOWN;
|
|
|
|
|
|
|
|
const EthereumNetworkInfo *network = get_network_definition_only(
|
|
|
|
msg->has_encoded_network, (const EncodedNetwork *)&msg->encoded_network,
|
|
|
|
slip44);
|
|
|
|
|
|
|
|
if (!network || !fsm_ethereumCheckPath(msg->address_n_count, msg->address_n,
|
|
|
|
false, network)) {
|
2022-01-25 21:54:31 +00:00
|
|
|
layoutHome();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-29 16:10:31 +00:00
|
|
|
const HDNode *node = fsm_getDerivedNode(SECP256K1_NAME, msg->address_n,
|
|
|
|
msg->address_n_count, NULL);
|
|
|
|
if (!node) return;
|
|
|
|
|
|
|
|
uint8_t pubkeyhash[20];
|
|
|
|
|
2022-01-25 21:54:31 +00:00
|
|
|
if (!hdnode_get_ethereum_pubkeyhash(node, pubkeyhash)) {
|
|
|
|
layoutHome();
|
|
|
|
return;
|
|
|
|
}
|
2019-03-29 16:10:31 +00:00
|
|
|
bool rskip60 = false;
|
2021-08-31 09:29:30 +00:00
|
|
|
uint64_t chain_id = 0;
|
2019-03-29 16:10:31 +00:00
|
|
|
// constants from trezor-common/defs/ethereum/networks.json
|
|
|
|
switch (slip44) {
|
|
|
|
case 137:
|
|
|
|
rskip60 = true;
|
|
|
|
chain_id = 30;
|
|
|
|
break;
|
|
|
|
case 37310:
|
|
|
|
rskip60 = true;
|
|
|
|
chain_id = 31;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
resp->has_address = true;
|
2022-01-12 20:00:54 +00:00
|
|
|
ethereum_address_checksum(pubkeyhash, resp->address, rskip60, chain_id);
|
2019-03-29 16:10:31 +00:00
|
|
|
// ethereum_address_checksum adds trailing zero
|
|
|
|
|
|
|
|
if (msg->has_show_display && msg->show_display) {
|
|
|
|
char desc[16];
|
|
|
|
strlcpy(desc, "Address:", sizeof(desc));
|
|
|
|
|
|
|
|
if (!fsm_layoutAddress(resp->address, desc, false, 0, msg->address_n,
|
2021-01-13 22:41:13 +00:00
|
|
|
msg->address_n_count, true, NULL, 0, 0, NULL)) {
|
2019-03-29 16:10:31 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
msg_write(MessageType_MessageType_EthereumAddress, resp);
|
|
|
|
layoutHome();
|
2018-05-03 14:47:37 +00:00
|
|
|
}
|
|
|
|
|
2019-03-29 16:10:31 +00:00
|
|
|
void fsm_msgEthereumSignMessage(const EthereumSignMessage *msg) {
|
|
|
|
RESP_INIT(EthereumMessageSignature);
|
2018-05-03 14:47:37 +00:00
|
|
|
|
2019-03-29 16:10:31 +00:00
|
|
|
CHECK_INITIALIZED
|
2018-05-03 14:47:37 +00:00
|
|
|
|
2021-11-06 10:07:36 +00:00
|
|
|
CHECK_PIN
|
|
|
|
|
2023-02-03 13:50:29 +00:00
|
|
|
uint32_t slip44 = (msg->address_n_count > 1)
|
|
|
|
? (msg->address_n[1] & PATH_UNHARDEN_MASK)
|
|
|
|
: SLIP44_UNKNOWN;
|
|
|
|
|
|
|
|
const EthereumNetworkInfo *network = get_network_definition_only(
|
|
|
|
msg->has_encoded_network, (const EncodedNetwork *)&msg->encoded_network,
|
|
|
|
slip44);
|
|
|
|
|
|
|
|
if (!network || !fsm_ethereumCheckPath(msg->address_n_count, msg->address_n,
|
|
|
|
false, network)) {
|
2022-01-25 21:54:31 +00:00
|
|
|
layoutHome();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-06 10:07:36 +00:00
|
|
|
const HDNode *node = fsm_getDerivedNode(SECP256K1_NAME, msg->address_n,
|
|
|
|
msg->address_n_count, NULL);
|
|
|
|
if (!node) return;
|
|
|
|
|
|
|
|
uint8_t pubkeyhash[20] = {0};
|
|
|
|
if (!hdnode_get_ethereum_pubkeyhash(node, pubkeyhash)) {
|
2022-01-25 21:54:31 +00:00
|
|
|
layoutHome();
|
2021-11-06 10:07:36 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-12 20:00:54 +00:00
|
|
|
ethereum_address_checksum(pubkeyhash, resp->address, false, 0);
|
2021-11-06 10:07:36 +00:00
|
|
|
// ethereum_address_checksum adds trailing zero
|
|
|
|
|
|
|
|
layoutVerifyAddress(NULL, resp->address);
|
|
|
|
if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) {
|
|
|
|
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
|
|
|
layoutHome();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-07 20:02:49 +00:00
|
|
|
if (!fsm_layoutSignMessage(msg->message.bytes, msg->message.size)) {
|
2019-03-29 16:10:31 +00:00
|
|
|
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
|
|
|
layoutHome();
|
|
|
|
return;
|
|
|
|
}
|
2018-05-03 14:47:37 +00:00
|
|
|
|
2019-03-29 16:10:31 +00:00
|
|
|
ethereum_message_sign(msg, node, resp);
|
|
|
|
layoutHome();
|
2018-05-03 14:47:37 +00:00
|
|
|
}
|
|
|
|
|
2019-03-29 16:10:31 +00:00
|
|
|
void fsm_msgEthereumVerifyMessage(const EthereumVerifyMessage *msg) {
|
|
|
|
if (ethereum_message_verify(msg) != 0) {
|
|
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Invalid signature"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t pubkeyhash[20];
|
|
|
|
if (!ethereum_parse(msg->address, pubkeyhash)) {
|
|
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Invalid address"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
layoutVerifyAddress(NULL, msg->address);
|
|
|
|
if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) {
|
|
|
|
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
|
|
|
layoutHome();
|
|
|
|
return;
|
|
|
|
}
|
2021-11-07 20:02:49 +00:00
|
|
|
|
|
|
|
if (!fsm_layoutVerifyMessage(msg->message.bytes, msg->message.size)) {
|
2019-03-29 16:10:31 +00:00
|
|
|
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
|
|
|
layoutHome();
|
|
|
|
return;
|
|
|
|
}
|
2021-11-07 20:02:49 +00:00
|
|
|
|
2022-02-04 17:35:21 +00:00
|
|
|
layoutDialogSwipe(&bmp_icon_ok, NULL, _("Continue"), NULL, NULL,
|
|
|
|
_("The signature is valid."), NULL, NULL, NULL, NULL);
|
|
|
|
if (!protectButton(ButtonRequestType_ButtonRequest_Other, true)) {
|
|
|
|
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
|
|
|
layoutHome();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-29 16:10:31 +00:00
|
|
|
fsm_sendSuccess(_("Message verified"));
|
|
|
|
|
|
|
|
layoutHome();
|
2018-09-06 16:28:56 +00:00
|
|
|
}
|
2021-11-29 23:37:23 +00:00
|
|
|
|
|
|
|
void fsm_msgEthereumSignTypedHash(const EthereumSignTypedHash *msg) {
|
|
|
|
RESP_INIT(EthereumTypedDataSignature);
|
|
|
|
|
|
|
|
CHECK_INITIALIZED
|
|
|
|
|
|
|
|
CHECK_PIN
|
|
|
|
|
2021-12-22 23:36:05 +00:00
|
|
|
if (msg->domain_separator_hash.size != 32 ||
|
|
|
|
(msg->has_message_hash && msg->message_hash.size != 32)) {
|
2021-11-29 23:37:23 +00:00
|
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Invalid hash length"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-02-03 13:50:29 +00:00
|
|
|
uint32_t slip44 = (msg->address_n_count > 1)
|
|
|
|
? (msg->address_n[1] & PATH_UNHARDEN_MASK)
|
|
|
|
: SLIP44_UNKNOWN;
|
|
|
|
|
|
|
|
const EthereumNetworkInfo *network = get_network_definition_only(
|
|
|
|
msg->has_encoded_network, (const EncodedNetwork *)&msg->encoded_network,
|
|
|
|
slip44);
|
|
|
|
|
|
|
|
if (!network || !fsm_ethereumCheckPath(msg->address_n_count, msg->address_n,
|
|
|
|
false, network)) {
|
2022-01-25 21:54:31 +00:00
|
|
|
layoutHome();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-29 23:37:23 +00:00
|
|
|
layoutDialogSwipe(&bmp_icon_warning, _("Abort"), _("Continue"), NULL,
|
|
|
|
_("Unable to show"), _("EIP-712 data."), NULL,
|
|
|
|
_("Sign at your own risk."), NULL, NULL);
|
|
|
|
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
|
|
|
|
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
|
|
|
layoutHome();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const HDNode *node = fsm_getDerivedNode(SECP256K1_NAME, msg->address_n,
|
|
|
|
msg->address_n_count, NULL);
|
|
|
|
if (!node) return;
|
|
|
|
|
|
|
|
uint8_t pubkeyhash[20] = {0};
|
|
|
|
if (!hdnode_get_ethereum_pubkeyhash(node, pubkeyhash)) {
|
2022-01-25 21:54:31 +00:00
|
|
|
layoutHome();
|
2021-11-29 23:37:23 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-12 20:00:54 +00:00
|
|
|
ethereum_address_checksum(pubkeyhash, resp->address, false, 0);
|
2021-11-29 23:37:23 +00:00
|
|
|
// ethereum_address_checksum adds trailing zero
|
|
|
|
|
|
|
|
layoutVerifyAddress(NULL, resp->address);
|
|
|
|
if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) {
|
|
|
|
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
|
|
|
layoutHome();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
layoutConfirmHash(&bmp_icon_warning, _("EIP-712 domain hash"),
|
|
|
|
msg->domain_separator_hash.bytes, 32);
|
|
|
|
if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) {
|
|
|
|
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
|
|
|
layoutHome();
|
|
|
|
return;
|
|
|
|
}
|
2021-12-22 23:36:05 +00:00
|
|
|
|
|
|
|
// No message hash when setting primaryType="EIP712Domain"
|
|
|
|
// https://ethereum-magicians.org/t/eip-712-standards-clarification-primarytype-as-domaintype/3286
|
|
|
|
if (msg->has_message_hash) {
|
|
|
|
layoutConfirmHash(&bmp_icon_warning, _("EIP-712 message hash"),
|
|
|
|
msg->message_hash.bytes, 32);
|
|
|
|
if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) {
|
|
|
|
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
|
|
|
layoutHome();
|
|
|
|
return;
|
|
|
|
}
|
2021-11-29 23:37:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ethereum_typed_hash_sign(msg, node, resp);
|
|
|
|
layoutHome();
|
|
|
|
}
|