diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dfed55e41..5d3bb29d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,22 +52,26 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha || github.sha }} - - name: Install clang-format - run: sudo apt-get install -y clang-format-14 - - name: Check code formatting run: | + # Use pre-installed clang-format (ubuntu-latest ships with it) + CF=$(command -v clang-format-14 || command -v clang-format || true) + if [ -z "$CF" ]; then + echo "::warning::clang-format not found, skipping lint" + exit 0 + fi + echo "Using: $CF ($($CF --version | head -1))" FAILED=0 for f in $(find include/keepkey lib/firmware lib/board lib/transport/src \ -name '*.c' -o -name '*.h' 2>/dev/null | grep -v generated | grep -v '.pb.'); do - if ! clang-format-14 --style=file --dry-run --Werror "$f" 2>/dev/null; then + if ! $CF --style=file --dry-run --Werror "$f" 2>/dev/null; then echo "::warning file=$f::Formatting differs from .clang-format" FAILED=1 fi done if [ "$FAILED" = "1" ]; then echo "" - echo "Run: clang-format -i to fix formatting" + echo "Run: $CF -i to fix formatting" echo "Note: treating as warning until codebase is reformatted" fi # Warn-only until existing code is reformatted @@ -339,6 +343,91 @@ jobs: bin/*.elf retention-days: 90 + build-arm-firmware-btc-only: + needs: [check-submodules, secret-scan] + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Init submodules + run: | + git submodule update --init deps/crypto/trezor-firmware + git submodule update --init deps/device-protocol + git submodule update --init --recursive deps/python-keepkey + git submodule update --init deps/googletest + git submodule update --init deps/qrenc/QR-Code-generator + git submodule update --init deps/sca-hardening/SecAESSTM32 + + - name: Cache base image + id: cache-base + uses: actions/cache@v4 + with: + path: /tmp/base-image.tar + key: base-image-${{ env.BASE_IMAGE }} + + - name: Pull and cache base image + if: steps.cache-base.outputs.cache-hit != 'true' + run: | + docker pull ${{ env.BASE_IMAGE }} + docker save ${{ env.BASE_IMAGE }} -o /tmp/base-image.tar + + - name: Load base image from cache + if: steps.cache-base.outputs.cache-hit == 'true' + run: docker load -i /tmp/base-image.tar + + - name: Extract firmware version + id: version + run: | + FW_VERSION=$(sed -n '/^project/,/)/p' CMakeLists.txt | grep -oP '\d+\.\d+\.\d+') + GIT_SHORT=$(git rev-parse --short HEAD) + echo "fw_version=${FW_VERSION}" >> "$GITHUB_OUTPUT" + echo "git_short=${GIT_SHORT}" >> "$GITHUB_OUTPUT" + echo "Firmware version: ${FW_VERSION} (${GIT_SHORT}) [BTC-only]" + + - name: Cross-compile BTC-only firmware for ARM + run: | + docker run --rm \ + -v ${{ github.workspace }}:/root/keepkey-firmware:z \ + ${{ env.BASE_IMAGE }} /bin/sh -c "\ + mkdir /root/build && cd /root/build && \ + cmake -C /root/keepkey-firmware/cmake/caches/device.cmake /root/keepkey-firmware \ + -DCOIN_SUPPORT=BTC \ + -DVARIANTS=NoObsoleteVariants \ + -DCMAKE_BUILD_TYPE=MinSizeRel \ + -DCMAKE_COLOR_MAKEFILE=ON && \ + make && \ + mkdir -p /root/keepkey-firmware/bin-btc && \ + cp bin/*.bin /root/keepkey-firmware/bin-btc/ && \ + cp bin/*.elf /root/keepkey-firmware/bin-btc/ && \ + chmod -R a+rw /root/keepkey-firmware/bin-btc" + + - name: Rename firmware artifacts + run: | + cd bin-btc + for f in *.bin; do + [ -f "$f" ] || continue + mv "$f" "firmware.keepkey.btc-only.v${{ steps.version.outputs.fw_version }}-${{ steps.version.outputs.git_short }}-${f}" + done + for f in *.elf; do + [ -f "$f" ] || continue + mv "$f" "firmware.keepkey.btc-only.v${{ steps.version.outputs.fw_version }}-${{ steps.version.outputs.git_short }}-${f}" + done + ls -lh + echo "::notice::BTC-only firmware v${{ steps.version.outputs.fw_version }} built successfully" + + - name: Upload BTC-only firmware artifacts + uses: actions/upload-artifact@v4 + with: + name: firmware-btc-only-v${{ steps.version.outputs.fw_version }}-${{ steps.version.outputs.git_short }} + path: | + bin-btc/*.bin + bin-btc/*.elf + retention-days: 90 + # ═══════════════════════════════════════════════════════════ # STAGE 3: TEST — run only after builds succeed # ═══════════════════════════════════════════════════════════ @@ -431,7 +520,7 @@ jobs: # ═══════════════════════════════════════════════════════════ publish-emulator: - needs: [unit-tests, python-integration-tests, build-arm-firmware] + needs: [unit-tests, python-integration-tests, build-arm-firmware, build-arm-firmware-btc-only] if: >- github.event_name == 'workflow_dispatch' && github.event.inputs.publish_emulator == 'true' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1064027ee..18772761d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,13 +71,15 @@ jobs: if: steps.cache-base.outputs.cache-hit == 'true' run: docker load -i /tmp/base-image.tar - - name: Cross-compile firmware + - name: Cross-compile firmware (BTC-only) run: | docker run --rm \ -v ${{ github.workspace }}:/root/keepkey-firmware:z \ ${{ env.BASE_IMAGE }} /bin/sh -c "\ mkdir /root/build && cd /root/build && \ cmake -C /root/keepkey-firmware/cmake/caches/device.cmake /root/keepkey-firmware \ + -DCOIN_SUPPORT=BTC \ + -DVARIANTS=NoObsoleteVariants \ -DCMAKE_BUILD_TYPE=MinSizeRel \ -DCMAKE_COLOR_MAKEFILE=ON && \ make && \ @@ -147,9 +149,11 @@ jobs: if: steps.cache-base.outputs.cache-hit == 'true' run: docker load -i /tmp/base-image.tar - - name: Build and test emulator + - name: Build and test emulator (BTC-only) run: | - docker build -t kkemu-release -f scripts/emulator/Dockerfile . + docker build \ + --build-arg coinsupport="-DCOIN_SUPPORT=BTC" \ + -t kkemu-release -f scripts/emulator/Dockerfile . docker run --rm --entrypoint /bin/sh kkemu-release \ -c "make xunit; RC=\$?; exit \$RC" @@ -178,9 +182,9 @@ jobs: cat > release-body.md < +#include +#include + +/** + * Derive a child BIP-39 mnemonic via BIP-85. + * + * Path: m/83696968'/39'/0'/'/' + * + * @param word_count Number of words: 12, 18, or 24. + * @param index Child index (0-based). + * @param mnemonic Output buffer (must be at least 241 bytes). + * @param mnemonic_len Size of the output buffer. + * @return true on success, false on error. + */ +bool bip85_derive_mnemonic(uint32_t word_count, uint32_t index, + char *mnemonic, size_t mnemonic_len); + +#endif diff --git a/include/keepkey/firmware/coins.def b/include/keepkey/firmware/coins.def index de2643175..3d7a3a972 100644 --- a/include/keepkey/firmware/coins.def +++ b/include/keepkey/firmware/coins.def @@ -2,6 +2,7 @@ //coin_name coin_shortcut address_type maxfee_kb p2sh signed_message_header bip44_account_path forkid/chain_id decimals contract_address xpub_magic segwit force_bip143 curve_name cashaddr_prefix bech32_prefix decred xpub_magic_segwit_p2sh xpub_mmagic_segwit_native nanoaddr_prefix taproot X(true, "Bitcoin", true, "BTC", true, 0, true, 100000, true, 5, true, "Bitcoin Signed Message:\n", true, 0x80000000, false, 0, true, 8, false, NO_CONTRACT, true, 76067358, true, true, true, false, true, SECP256K1_STRING, false, "", true, "bc", false, false, true, 77429938, true, 78792518, false, "", true, true ) X(true, "Testnet", true, "TEST", true, 111, true, 10000000, true, 196, true, "Bitcoin Signed Message:\n", true, 0x80000001, false, 0, true, 8, false, NO_CONTRACT, true, 70617039, true, true, true, false, true, SECP256K1_STRING, false, "", true, "tb", false, false, true, 71979618, true, 73342198, false, "", true, true ) +#ifndef BITCOIN_ONLY X(true, "BitcoinCash", true, "BCH", true, 0, true, 500000, true, 5, true, "Bitcoin Signed Message:\n", true, 0x80000091, true, 0, true, 8, false, NO_CONTRACT, true, 76067358, true, false, true, true, true, SECP256K1_STRING, true, "bitcoincash", false, "", false, false, false, 0, false, 0, false, "", true, false ) X(true, "Namecoin", true, "NMC", true, 52, true, 10000000, true, 5, true, "Namecoin Signed Message:\n", true, 0x80000007, false, 0, true, 8, false, NO_CONTRACT, true, 27108450, true, false, true, false, true, SECP256K1_STRING, false, "", false, "", false, false, false, 0, false, 0, false, "", true, false ) X(true, "Litecoin", true, "LTC", true, 48, true, 1000000, true, 50, true, "Litecoin Signed Message:\n", true, 0x80000002, false, 0, true, 8, false, NO_CONTRACT, true, 27108450, true, true, true, false, true, SECP256K1_STRING, false, "", true, "ltc", false, false, true, 28471030, true, 78792518, false, "", true, false ) @@ -46,6 +47,6 @@ X(true, "Terra", true, "LUNA", false, NA, false, NA, false, N X(true, "Kava", true, "KAVA", false, NA, false, NA, false, NA, false, {0}, true, 0x800001cb, false, 0, true, 6, false, NO_CONTRACT, false, 0, false, false, false, false, true, SECP256K1_STRING, false, "", false, "kava", false, false, false, 0, false, 0, false, "", true, false ) X(true, "Secret", true, "SCRT", false, NA, false, NA, false, NA, false, {0}, true, 0x80000211, false, 0, true, 6, false, NO_CONTRACT, false, 0, false, false, false, false, true, SECP256K1_STRING, false, "", false, "secret", false, false, false, 0, false, 0, false, "", true, false ) X(true, "MAYAChain", true, "CACAO", false, NA, false, NA, false, NA, false, {0}, true, 0x800003a3, false, 0, true, 10, false, NO_CONTRACT, false, 0, false, false, false, false, true, SECP256K1_STRING, false, "", false, "maya", false, false, false, 0, false, 0, false, "", true, false ) - +#endif // BITCOIN_ONLY #undef X #undef NO_CONTRACT diff --git a/include/keepkey/firmware/coins.h b/include/keepkey/firmware/coins.h index b94e36cca..5c6c64d1e 100644 --- a/include/keepkey/firmware/coins.h +++ b/include/keepkey/firmware/coins.h @@ -44,14 +44,20 @@ enum { CONCAT(CoinIndex, __COUNTER__), #include "keepkey/firmware/coins.def" +#ifdef BITCOIN_ONLY +// For full-featured keepkey, this is defined in ethereum_tokens.h. For bitcoin only keepkey, need to +// define it here because ethereum_tokens.h is not included in any file +#define TOKENS_COUNT 0 +#else #define X(INDEX, NAME, SYMBOL, DECIMALS, CONTRACT_ADDRESS) \ CONCAT(CoinIndex, __COUNTER__), #include "keepkey/firmware/tokens.def" - +#endif CoinIndexLast, CoinIndexFirst = 0 }; + #define COINS_COUNT ((int)CoinIndexLast - (int)CoinIndexFirst) #define NODE_STRING_LENGTH 50 diff --git a/include/keepkey/firmware/fsm.h b/include/keepkey/firmware/fsm.h index 19e911106..ea8699b78 100644 --- a/include/keepkey/firmware/fsm.h +++ b/include/keepkey/firmware/fsm.h @@ -77,6 +77,10 @@ void fsm_msgWordAck(WordAck *msg); void fsm_msgCharacterAck(CharacterAck *msg); void fsm_msgApplyPolicies(ApplyPolicies *msg); +// BIP-85 (chain-agnostic, available in all builds) +void fsm_msgGetBip85Mnemonic(const GetBip85Mnemonic *msg); + +#ifndef BITCOIN_ONLY // ethereum void fsm_msgEthereumGetAddress(EthereumGetAddress *msg); void fsm_msgEthereumSignTx(EthereumSignTx *msg); @@ -118,6 +122,15 @@ void fsm_msgMayachainGetAddress(const MayachainGetAddress *msg); void fsm_msgMayachainSignTx(const MayachainSignTx *msg); void fsm_msgMayachainMsgAck(const MayachainMsgAck *msg); +void fsm_msgSolanaGetAddress(const SolanaGetAddress *msg); +void fsm_msgSolanaSignTx(const SolanaSignTx *msg); +void fsm_msgSolanaSignMessage(const SolanaSignMessage *msg); +void fsm_msgTronGetAddress(const TronGetAddress *msg); +void fsm_msgTronSignTx(TronSignTx *msg); +void fsm_msgTonGetAddress(const TonGetAddress *msg); +void fsm_msgTonSignTx(TonSignTx *msg); +#endif // BITCOIN_ONLY + #if DEBUG_LINK // void fsm_msgDebugLinkDecision(DebugLinkDecision *msg); void fsm_msgDebugLinkGetState(DebugLinkGetState *msg); diff --git a/include/keepkey/firmware/solana.h b/include/keepkey/firmware/solana.h new file mode 100644 index 000000000..7cc3e8761 --- /dev/null +++ b/include/keepkey/firmware/solana.h @@ -0,0 +1,44 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2024 KeepKey + * + * 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 . + */ + +#ifndef KEEPKEY_FIRMWARE_SOLANA_H +#define KEEPKEY_FIRMWARE_SOLANA_H + +#include "trezor/crypto/bip32.h" +#include "messages-solana.pb.h" + +#define SOLANA_ADDRESS_SIZE 50 // Base58-encoded Ed25519 public key (typically 44 chars + null) +#define SOLANA_SIGNATURE_SIZE 64 // Ed25519 signature size + +// Convert Ed25519 public key to Solana Base58 address +bool solana_publicKeyToAddress(const uint8_t public_key[32], char *address, + size_t address_size); + +// Sign Solana transaction (returns false on failure) +bool solana_signTx(const HDNode *node, const SolanaSignTx *msg, + SolanaSignedTx *resp); + +// Sign Solana message (off-chain signature) +void solana_signMessage(const HDNode *node, const uint8_t *message, + size_t message_len, uint8_t *signature_out); + +// Display message to user for confirmation +bool solana_confirmMessage(const uint8_t *message, size_t message_len); + +#endif diff --git a/include/keepkey/firmware/solana_tx.h b/include/keepkey/firmware/solana_tx.h new file mode 100644 index 000000000..a5bbe16a8 --- /dev/null +++ b/include/keepkey/firmware/solana_tx.h @@ -0,0 +1,104 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2024 KeepKey + * + * 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 . + */ + +#ifndef KEEPKEY_FIRMWARE_SOLANA_TX_H +#define KEEPKEY_FIRMWARE_SOLANA_TX_H + +#include +#include +#include + +// Known Solana program IDs +#define SOLANA_SYSTEM_PROGRAM_ID "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +#define SOLANA_TOKEN_PROGRAM_ID "\x06\xdd\xf6\xe1\xd7\x65\xa1\x93\xd9\xcb\xe1\x46\xce\xeb\x79\xac\x1c\xb4\x85\xed\x5f\x5b\x37\x91\x3a\x8c\xf5\x85\x7e\xff\x00\xa9" +#define SOLANA_STAKE_PROGRAM_ID "\x06\xa7\xd5\x17\x18\xc7\x74\xc9\x28\x56\x63\x98\x69\x1d\x5e\xb6\x8b\x5e\xb8\xa3\x9b\x4b\x6d\x5c\x73\x55\x5b\x21\x00\x00\x00\x00" + +// Solana instruction types +typedef enum { + SOLANA_INSTRUCTION_UNKNOWN = 0, + SOLANA_INSTRUCTION_SYSTEM_TRANSFER = 1, + SOLANA_INSTRUCTION_SYSTEM_CREATE_ACCOUNT = 2, + SOLANA_INSTRUCTION_TOKEN_TRANSFER = 3, + SOLANA_INSTRUCTION_TOKEN_TRANSFER_CHECKED = 4, + SOLANA_INSTRUCTION_TOKEN_APPROVE = 5, + SOLANA_INSTRUCTION_STAKE_DELEGATE = 6, + SOLANA_INSTRUCTION_STAKE_WITHDRAW = 7 +} SolanaInstructionType; + +// Parsed instruction data +typedef struct { + SolanaInstructionType type; + uint8_t program_id[32]; + uint8_t num_accounts; + uint8_t account_indices[16]; // Max accounts per instruction + const uint8_t *data; + size_t data_len; +} SolanaInstruction; + +// Parsed transaction +typedef struct { + uint8_t num_signatures; + uint8_t num_required_signatures; + uint8_t num_readonly_signed; + uint8_t num_readonly_unsigned; + uint16_t num_accounts; + uint8_t account_keys[16][32]; // 16 accounts (512 bytes) + uint8_t recent_blockhash[32]; + uint8_t num_instructions; + SolanaInstruction instructions[8]; // 8 instructions +} SolanaParsedTransaction; + +// System Transfer instruction data +typedef struct { + uint64_t lamports; +} SolanaSystemTransfer; + +// Token Transfer instruction data +typedef struct { + uint64_t amount; + uint8_t decimals; // For TransferChecked +} SolanaTokenTransfer; + +// Read Solana compact-u16 varint +bool read_compact_u16(const uint8_t **data, size_t *remaining, uint16_t *out); + +// Parse a raw Solana transaction +bool solana_parseTransaction(const uint8_t *raw_tx, size_t tx_size, + SolanaParsedTransaction *parsed); + +// Identify instruction type +SolanaInstructionType solana_identifyInstruction(const uint8_t *program_id, + const uint8_t *data, + size_t data_len); + +// Parse specific instruction types +bool solana_parseSystemTransfer(const uint8_t *data, size_t len, + SolanaSystemTransfer *transfer); + +bool solana_parseTokenTransfer(const uint8_t *data, size_t len, + SolanaTokenTransfer *transfer); + +// Display transaction to user +bool solana_confirmTransaction(const SolanaParsedTransaction *tx, + const uint8_t *signer_pubkey); + +// Format lamports to SOL string (1 SOL = 1,000,000,000 lamports) +void solana_formatLamports(uint64_t lamports, char *out, size_t out_len); + +#endif // KEEPKEY_FIRMWARE_SOLANA_TX_H diff --git a/include/keepkey/firmware/ton.h b/include/keepkey/firmware/ton.h new file mode 100644 index 000000000..3bcd8224e --- /dev/null +++ b/include/keepkey/firmware/ton.h @@ -0,0 +1,68 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2024 KeepKey + * + * 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 . + */ + +#ifndef KEEPKEY_FIRMWARE_TON_H +#define KEEPKEY_FIRMWARE_TON_H + +#include "trezor/crypto/bip32.h" +#include "trezor/crypto/ed25519-donna/ed25519.h" + +#include "messages-ton.pb.h" + +// TON address length (Base64 URL-safe encoded, typically 48 chars) +#define TON_ADDRESS_MAX_LEN 64 +#define TON_RAW_ADDRESS_MAX_LEN 128 + +// TON decimals (1 TON = 1,000,000,000 nanoTON) +#define TON_DECIMALS 9 + +/** + * Generate TON address from Ed25519 public key + * @param public_key Ed25519 public key (32 bytes) + * @param bounceable Bounceable flag for address encoding + * @param testnet Testnet flag for address encoding + * @param workchain Workchain ID (-1 or 0) + * @param address Output buffer for user-friendly Base64 encoded address + * @param address_len Length of address output buffer + * @param raw_address Output buffer for raw address format (workchain:hash) + * @param raw_address_len Length of raw_address output buffer + * @return true on success, false on failure + */ +bool ton_get_address(const ed25519_public_key public_key, bool bounceable, + bool testnet, int32_t workchain, char *address, + size_t address_len, char *raw_address, + size_t raw_address_len); + +/** + * Format TON amount (nanoTON) for display + * @param buf Output buffer + * @param len Length of output buffer + * @param amount Amount in nanoTON (1 TON = 1,000,000,000 nanoTON) + */ +void ton_formatAmount(char *buf, size_t len, uint64_t amount); + +/** + * Sign a TON transaction + * @param node HD node containing private key + * @param msg TonSignTx request message + * @param resp TonSignedTx response message (will be filled with signature) + */ +bool ton_signTx(const HDNode *node, const TonSignTx *msg, TonSignedTx *resp); + +#endif diff --git a/include/keepkey/firmware/tron.h b/include/keepkey/firmware/tron.h new file mode 100644 index 000000000..22abb5c26 --- /dev/null +++ b/include/keepkey/firmware/tron.h @@ -0,0 +1,60 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2024 KeepKey + * + * 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 . + */ + +#ifndef KEEPKEY_FIRMWARE_TRON_H +#define KEEPKEY_FIRMWARE_TRON_H + +#include "trezor/crypto/bip32.h" + +#include "messages-tron.pb.h" + +// TRON address length (Base58Check, typically 34 chars starting with 'T') +#define TRON_ADDRESS_MAX_LEN 64 + +// TRON decimals (1 TRX = 1,000,000 SUN) +#define TRON_DECIMALS 6 + +/** + * Generate TRON address from secp256k1 public key + * @param public_key secp256k1 public key (33 bytes compressed) + * @param address Output buffer for Base58Check encoded address + * @param address_len Length of output buffer + * @return true on success, false on failure + */ +bool tron_getAddress(const uint8_t public_key[33], char *address, + size_t address_len); + +/** + * Format TRON amount (SUN) for display + * @param buf Output buffer + * @param len Length of output buffer + * @param amount Amount in SUN (1 TRX = 1,000,000 SUN) + */ +void tron_formatAmount(char *buf, size_t len, uint64_t amount); + +/** + * Sign a TRON transaction + * @param node HD node containing private key + * @param msg TronSignTx request message + * @param resp TronSignedTx response message (will be filled with signature) + */ +bool tron_signTx(const HDNode *node, const TronSignTx *msg, + TronSignedTx *resp); + +#endif diff --git a/include/keepkey/transport/interface.h b/include/keepkey/transport/interface.h index ffebf205d..c81bd463c 100644 --- a/include/keepkey/transport/interface.h +++ b/include/keepkey/transport/interface.h @@ -26,6 +26,7 @@ #include "messages-nano.pb.h" #undef delete +#ifndef BITCOIN_ONLY #include "messages-ethereum.pb.h" #include "messages-binance.pb.h" #include "messages-cosmos.pb.h" @@ -35,6 +36,10 @@ #include "messages-tendermint.pb.h" #include "messages-thorchain.pb.h" #include "messages-mayachain.pb.h" +#include "messages-solana.pb.h" +#include "messages-tron.pb.h" +#include "messages-ton.pb.h" +#endif // BITCOIN_ONLY #include "types.pb.h" #include "trezor_transport.h" diff --git a/include/keepkey/transport/messages-solana.options b/include/keepkey/transport/messages-solana.options new file mode 100644 index 000000000..645d5cfac --- /dev/null +++ b/include/keepkey/transport/messages-solana.options @@ -0,0 +1,17 @@ +SolanaGetAddress.address_n max_count:8 +SolanaGetAddress.coin_name max_size:21 + +SolanaAddress.address max_size:64 + +SolanaSignTx.address_n max_count:8 +SolanaSignTx.coin_name max_size:21 +SolanaSignTx.raw_tx max_size:2048 + +SolanaSignedTx.signature max_size:64 + +SolanaSignMessage.address_n max_count:8 +SolanaSignMessage.coin_name max_size:21 +SolanaSignMessage.message max_size:1024 + +SolanaMessageSignature.public_key max_size:32 +SolanaMessageSignature.signature max_size:64 diff --git a/include/keepkey/transport/messages-ton.options b/include/keepkey/transport/messages-ton.options new file mode 100644 index 000000000..18c064289 --- /dev/null +++ b/include/keepkey/transport/messages-ton.options @@ -0,0 +1,12 @@ +TonGetAddress.address_n max_count:8 +TonGetAddress.coin_name max_size:21 + +TonAddress.address max_size:50 +TonAddress.raw_address max_size:70 + +TonSignTx.address_n max_count:8 +TonSignTx.coin_name max_size:21 +TonSignTx.raw_tx max_size:1024 +TonSignTx.to_address max_size:50 + +TonSignedTx.signature max_size:64 diff --git a/include/keepkey/transport/messages-tron.options b/include/keepkey/transport/messages-tron.options new file mode 100644 index 000000000..80215e8cf --- /dev/null +++ b/include/keepkey/transport/messages-tron.options @@ -0,0 +1,14 @@ +TronGetAddress.address_n max_count:8 +TronGetAddress.coin_name max_size:21 + +TronAddress.address max_size:35 + +TronSignTx.address_n max_count:8 +TronSignTx.coin_name max_size:21 +TronSignTx.raw_data max_size:2048 +TronSignTx.ref_block_bytes max_size:4 +TronSignTx.ref_block_hash max_size:32 +TronSignTx.contract_type max_size:64 +TronSignTx.to_address max_size:35 + +TronSignedTx.signature max_size:65 diff --git a/include/keepkey/transport/messages.options b/include/keepkey/transport/messages.options index 41e0c377c..ad08bd6ba 100644 --- a/include/keepkey/transport/messages.options +++ b/include/keepkey/transport/messages.options @@ -131,3 +131,5 @@ FlashHash.challenge max_size:32 FlashWrite.data max_size:1024 FlashHashResponse.data max_size:32 + +Bip85Mnemonic.mnemonic max_size:241 diff --git a/include/keepkey/variant/keepkey.h b/include/keepkey/variant/keepkey.h index 345008a8a..93c8d0c47 100644 --- a/include/keepkey/variant/keepkey.h +++ b/include/keepkey/variant/keepkey.h @@ -1,16 +1,51 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2025 markrypto + * + * 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 . + */ + #ifndef KEEPKEY_VARIANT_KEEPKEY_H #define KEEPKEY_VARIANT_KEEPKEY_H #include "keepkey/board/variant.h" -#define VARIANTINFO_KEEPKEY \ - .version = 1, .name = "KeepKey", .logo = &kk_logo, \ - .logo_reversed = &kk_logo_reversed, \ - .screensaver_timeout = ONE_SEC * 60 * 10, .screensaver = &kk_screensaver, +#ifdef BITCOIN_ONLY +// use the bitcoin-only logo +#define VARIANTINFO_KEEPKEY \ +.version = 1, .name = "KeepKeyBTC", .logo = &kkbtc_logo, \ +.logo_reversed = &kkbtc_logo_reversed, \ +.screensaver_timeout = ONE_SEC * 60 * 10, .screensaver = &kkbtc_screensaver, + +extern const VariantInfo variant_keepkey; +extern const VariantAnimation kkbtc_logo; +extern const VariantAnimation kkbtc_logo_reversed; +extern const VariantAnimation kkbtc_screensaver; + +#else + +#define VARIANTINFO_KEEPKEY \ +.version = 1, .name = "KeepKey", .logo = &kk_logo, \ +.logo_reversed = &kk_logo_reversed, \ +.screensaver_timeout = ONE_SEC * 60 * 10, .screensaver = &kk_screensaver, extern const VariantInfo variant_keepkey; extern const VariantAnimation kk_logo; extern const VariantAnimation kk_logo_reversed; extern const VariantAnimation kk_screensaver; +#endif // BITCOIN_ONLY + #endif diff --git a/lib/board/signatures.c b/lib/board/signatures.c index c6b88026c..7cff0421b 100644 --- a/lib/board/signatures.c +++ b/lib/board/signatures.c @@ -20,7 +20,9 @@ #include "trezor/crypto/sha2.h" #include "trezor/crypto/ecdsa.h" #include "trezor/crypto/secp256k1.h" +#include "trezor/crypto/memzero.h" #include "keepkey/board/memory.h" +#include "keepkey/board/memcmp_s.h" #include "keepkey/board/signatures.h" #include "keepkey/board/pubkeys.h" @@ -68,23 +70,51 @@ int signatures_ok(void) { return KEY_EXPIRED; } /* Expired signing key */ + /* F3 hardening: double-compute SHA-256, compare in constant time */ + uint8_t firmware_fingerprint2[32]; sha256_Raw((uint8_t *)FLASH_APP_START, codelen, firmware_fingerprint); + asm volatile("" ::: "memory"); + sha256_Raw((uint8_t *)FLASH_APP_START, codelen, firmware_fingerprint2); - if (ecdsa_verify_digest(&secp256k1, pubkey[sigindex1 - 1], - (uint8_t *)FLASH_META_SIG1, - firmware_fingerprint) != 0) { /* Failure */ + if (memcmp_s(firmware_fingerprint, firmware_fingerprint2, 32) != 0) { + memzero(firmware_fingerprint, sizeof(firmware_fingerprint)); + memzero(firmware_fingerprint2, sizeof(firmware_fingerprint2)); return SIG_FAIL; } + memzero(firmware_fingerprint2, sizeof(firmware_fingerprint2)); - if (ecdsa_verify_digest(&secp256k1, pubkey[sigindex2 - 1], - (uint8_t *)FLASH_META_SIG2, - firmware_fingerprint) != 0) { /* Failure */ + /* F3 hardening: infective aggregation — accumulate all three ECDSA + * results instead of early-returning on each. Forces attacker to + * corrupt all three verify calls, not just skip one branch. */ + volatile int verify_acc = 0; + volatile int verify_sentinel = 0; + + verify_acc |= ecdsa_verify_digest(&secp256k1, pubkey[sigindex1 - 1], + (uint8_t *)FLASH_META_SIG1, + firmware_fingerprint); + verify_sentinel++; + asm volatile("" ::: "memory"); + + verify_acc |= ecdsa_verify_digest(&secp256k1, pubkey[sigindex2 - 1], + (uint8_t *)FLASH_META_SIG2, + firmware_fingerprint); + verify_sentinel++; + asm volatile("" ::: "memory"); + + verify_acc |= ecdsa_verify_digest(&secp256k1, pubkey[sigindex3 - 1], + (uint8_t *)FLASH_META_SIG3, + firmware_fingerprint); + verify_sentinel++; + asm volatile("" ::: "memory"); + + memzero(firmware_fingerprint, sizeof(firmware_fingerprint)); + + /* All three verifies must have executed and all must have passed */ + if (verify_sentinel != 3) { return SIG_FAIL; } - if (ecdsa_verify_digest(&secp256k1, pubkey[sigindex3 - 1], - (uint8_t *)FLASH_META_SIG3, - firmware_fingerprint) != 0) { /* Failure */ + if (verify_acc != 0) { return SIG_FAIL; } diff --git a/lib/board/variant.c b/lib/board/variant.c index d996858ab..f6dab58d2 100644 --- a/lib/board/variant.c +++ b/lib/board/variant.c @@ -118,7 +118,11 @@ const VariantInfo *__attribute__((weak)) variant_getInfo(void) { case MODEL_KEEPKEY: return &variant_keepkey; case MODEL_SALT: +#ifndef BITCOIN_ONLY return &variant_salt; +#else + return &variant_keepkey; +#endif case MODEL_FOX: return &variant_keepkey; case MODEL_KASPERSKY: @@ -152,7 +156,11 @@ const VariantAnimation *variant_getLogo(bool reversed) { const char *variant_getName(void) { #ifdef EMULATOR - return "Emulator"; + #ifdef BITCOIN_ONLY + return "EmulatorBTC"; + #else + return "Emulator"; + #endif #else if (name) { return name; diff --git a/lib/firmware/CMakeLists.txt b/lib/firmware/CMakeLists.txt index e08ccfc69..4a472488f 100644 --- a/lib/firmware/CMakeLists.txt +++ b/lib/firmware/CMakeLists.txt @@ -2,44 +2,54 @@ set(sources app_confirm.c app_layout.c authenticator.c - binance.c coins.c crypto.c - eip712.c - eos.c - eos-contracts/eosio.system.c - eos-contracts/eosio.token.c - ethereum.c - ethereum_contracts.c - ethereum_contracts/makerdao.c - ethereum_contracts/saproxy.c - ethereum_contracts/zxappliquid.c - ethereum_contracts/thortx.c - ethereum_contracts/zxliquidtx.c - ethereum_contracts/zxtransERC20.c - ethereum_contracts/zxswap.c - ethereum_tokens.c fsm.c home_sm.c - mayachain.c - nano.c - osmosis.c passphrase_sm.c pin_sm.c policy.c recovery_cipher.c reset.c - ripple.c - ripple_base58.c signing.c - signtx_tendermint.c storage.c - tendermint.c - thorchain.c tiny-json.c transaction.c txin_check.c - u2f.c) + u2f.c + bip85.c) + +if(NOT "${COIN_SUPPORT}" STREQUAL "BTC") + list(APPEND sources + binance.c + eip712.c + eos.c + eos-contracts/eosio.system.c + eos-contracts/eosio.token.c + ethereum.c + ethereum_contracts.c + ethereum_contracts/makerdao.c + ethereum_contracts/saproxy.c + ethereum_contracts/zxappliquid.c + ethereum_contracts/thortx.c + ethereum_contracts/zxliquidtx.c + ethereum_contracts/zxtransERC20.c + ethereum_contracts/zxswap.c + ethereum_tokens.c + mayachain.c + nano.c + osmosis.c + ripple.c + ripple_base58.c + signtx_tendermint.c + tendermint.c + thorchain.c + solana.c + solana_msg.c + solana_tx.c + tron.c + ton.c) +endif() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_revision.h.in" "${CMAKE_CURRENT_BINARY_DIR}/scm_revision.h" @ONLY) @@ -51,14 +61,18 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR}) add_library(kkfirmware ${sources}) -add_dependencies(kkfirmware kktransport.pb trezorcrypto qrcodegenerator ethereum_tokens.def) -set(ETHEREUM_TOKENS ${CMAKE_BINARY_DIR}/include/keepkey/firmware/ethereum_tokens) -set(UNISWAP_TOKENS ${CMAKE_BINARY_DIR}/include/keepkey/firmware/uniswap_tokens) +add_dependencies(kkfirmware kktransport.pb trezorcrypto qrcodegenerator) + +if(NOT "${COIN_SUPPORT}" STREQUAL "BTC") + set(ETHEREUM_TOKENS ${CMAKE_BINARY_DIR}/include/keepkey/firmware/ethereum_tokens) + set(UNISWAP_TOKENS ${CMAKE_BINARY_DIR}/include/keepkey/firmware/uniswap_tokens) -add_custom_target(ethereum_tokens.def - COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/include/keepkey/firmware - COMMAND python3 ${CMAKE_SOURCE_DIR}/deps/python-keepkey/keepkeylib/eth/ethereum_tokens.py ${ETHEREUM_TOKENS}.def - COMMAND python3 ${CMAKE_SOURCE_DIR}/deps/python-keepkey/keepkeylib/eth/uniswap_tokens.py ${UNISWAP_TOKENS}.def) + add_custom_target(ethereum_tokens.def + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/include/keepkey/firmware + COMMAND python3 ${CMAKE_SOURCE_DIR}/deps/python-keepkey/keepkeylib/eth/ethereum_tokens.py ${ETHEREUM_TOKENS}.def + COMMAND python3 ${CMAKE_SOURCE_DIR}/deps/python-keepkey/keepkeylib/eth/uniswap_tokens.py ${UNISWAP_TOKENS}.def) + add_dependencies(kkfirmware ethereum_tokens.def) +endif() add_library(kkfirmware.keepkey variant/keepkey/resources.c) diff --git a/lib/firmware/bip85.c b/lib/firmware/bip85.c new file mode 100644 index 000000000..6ac559b52 --- /dev/null +++ b/lib/firmware/bip85.c @@ -0,0 +1,98 @@ +#include "keepkey/firmware/bip85.h" +#include "keepkey/firmware/storage.h" +#include "trezor/crypto/bip32.h" +#include "trezor/crypto/bip39.h" +#include "trezor/crypto/curves.h" +#include "trezor/crypto/hmac.h" +#include "trezor/crypto/memzero.h" + +#include + +/* + * BIP-85: Deterministic Entropy From BIP32 Keychains + * + * For BIP-39 mnemonic derivation: + * path = m / 83696968' / 39' / 0' / ' / ' + * k = derived_node.private_key (32 bytes) + * hmac = HMAC-SHA512(key="bip-entropy-from-k", msg=k) + * entropy = hmac[0 : entropy_bytes] + * 12 words -> 16 bytes, 18 words -> 24 bytes, 24 words -> 32 bytes + * mnemonic = bip39_from_entropy(entropy) + */ + +/* BIP-85 application number for deriving entropy from a key */ +static const uint8_t BIP85_HMAC_KEY[] = "bip-entropy-from-k"; +#define BIP85_HMAC_KEY_LEN 18 + +bool bip85_derive_mnemonic(uint32_t word_count, uint32_t index, + char *mnemonic, size_t mnemonic_len) { + /* Reject index >= 0x80000000 to avoid hardened-bit collision */ + if (index & 0x80000000) { + return false; + } + + /* Validate word count and compute entropy length */ + int entropy_bytes; + switch (word_count) { + case 12: entropy_bytes = 16; break; + case 18: entropy_bytes = 24; break; + case 24: entropy_bytes = 32; break; + default: return false; + } + + /* BIP-85 derivation path: m/83696968'/39'/0'/'/' */ + uint32_t address_n[5]; + address_n[0] = 0x80000000 | 83696968; /* purpose (hardened) */ + address_n[1] = 0x80000000 | 39; /* BIP-39 app (hardened) */ + address_n[2] = 0x80000000 | 0; /* English language (hardened) */ + address_n[3] = 0x80000000 | word_count; /* word count (hardened) */ + address_n[4] = 0x80000000 | index; /* child index (hardened) */ + + /* Get the master node from storage (respects passphrase) */ + static CONFIDENTIAL HDNode node; + if (!storage_getRootNode(SECP256K1_NAME, true, &node)) { + memzero(&node, sizeof(node)); + return false; + } + + /* Derive to the BIP-85 path */ + for (int i = 0; i < 5; i++) { + if (hdnode_private_ckd(&node, address_n[i]) == 0) { + memzero(&node, sizeof(node)); + return false; + } + } + + /* HMAC-SHA512(key="bip-entropy-from-k", msg=private_key) */ + static CONFIDENTIAL uint8_t hmac_out[64]; + hmac_sha512(BIP85_HMAC_KEY, BIP85_HMAC_KEY_LEN, + node.private_key, 32, hmac_out); + + /* We no longer need the derived node */ + memzero(&node, sizeof(node)); + + /* Truncate HMAC output to the required entropy length */ + static CONFIDENTIAL uint8_t entropy[32]; + memcpy(entropy, hmac_out, entropy_bytes); + memzero(hmac_out, sizeof(hmac_out)); + + /* Convert entropy to BIP-39 mnemonic */ + const char *words = mnemonic_from_data(entropy, entropy_bytes); + memzero(entropy, sizeof(entropy)); + + if (!words) { + return false; + } + + /* Copy to output buffer */ + size_t words_len = strlen(words); + if (words_len >= mnemonic_len) { + mnemonic_clear(); + return false; + } + + memcpy(mnemonic, words, words_len + 1); + mnemonic_clear(); + + return true; +} diff --git a/lib/firmware/coins.c b/lib/firmware/coins.c index a2c859fff..257439e9c 100644 --- a/lib/firmware/coins.c +++ b/lib/firmware/coins.c @@ -85,6 +85,7 @@ const CoinType coins[COINS_COUNT] = { TAPROOT}, #include "keepkey/firmware/coins.def" +#ifndef BITCOIN_ONLY #define X(INDEX, NAME, SYMBOL, DECIMALS, CONTRACT_ADDRESS) \ { \ true, \ @@ -130,6 +131,7 @@ const CoinType coins[COINS_COUNT] = { false, false, /* has_taproot, taproot*/ \ }, #include "keepkey/firmware/tokens.def" +#endif // BITCOIN_ONLY }; _Static_assert(sizeof(coins) / sizeof(coins[0]) == COINS_COUNT, diff --git a/lib/firmware/fsm.c b/lib/firmware/fsm.c index 0d779f448..4b9ab620c 100644 --- a/lib/firmware/fsm.c +++ b/lib/firmware/fsm.c @@ -17,6 +17,7 @@ * along with this library. If not, see . */ + #include "scm_revision.h" #include "variant.h" #include "u2f_knownapps.h" @@ -36,28 +37,16 @@ #include "keepkey/firmware/app_layout.h" #include "keepkey/firmware/authenticator.h" #include "keepkey/firmware/coins.h" -#include "keepkey/firmware/cosmos.h" -#include "keepkey/firmware/binance.h" #include "keepkey/firmware/crypto.h" -#include "keepkey/firmware/eos.h" -#include "keepkey/firmware/eos-contracts.h" -#include "keepkey/firmware/ethereum.h" -#include "keepkey/firmware/ethereum_tokens.h" #include "keepkey/firmware/fsm.h" #include "keepkey/firmware/home_sm.h" -#include "keepkey/firmware/mayachain.h" -#include "keepkey/firmware/osmosis.h" #include "keepkey/firmware/passphrase_sm.h" #include "keepkey/firmware/pin_sm.h" #include "keepkey/firmware/policy.h" #include "keepkey/firmware/recovery_cipher.h" #include "keepkey/firmware/reset.h" -#include "keepkey/firmware/ripple.h" #include "keepkey/firmware/signing.h" -#include "keepkey/firmware/signtx_tendermint.h" #include "keepkey/firmware/storage.h" -#include "keepkey/firmware/tendermint.h" -#include "keepkey/firmware/thorchain.h" #include "keepkey/firmware/transaction.h" #include "keepkey/firmware/txin_check.h" #include "keepkey/firmware/u2f.h" @@ -75,6 +64,29 @@ #include "trezor/crypto/secp256k1.h" #include "messages.pb.h" + +#include + +#include "keepkey/firmware/bip85.h" + +#ifndef BITCOIN_ONLY +#include "keepkey/firmware/cosmos.h" +#include "keepkey/firmware/binance.h" +#include "keepkey/firmware/eos.h" +#include "keepkey/firmware/eos-contracts.h" +#include "keepkey/firmware/ethereum.h" +#include "keepkey/firmware/ethereum_tokens.h" +#include "keepkey/firmware/mayachain.h" +#include "keepkey/firmware/osmosis.h" +#include "keepkey/firmware/ripple.h" +#include "keepkey/firmware/signtx_tendermint.h" +#include "keepkey/firmware/tendermint.h" +#include "keepkey/firmware/thorchain.h" +#include "keepkey/firmware/solana.h" +#include "keepkey/firmware/solana_tx.h" +#include "keepkey/firmware/tron.h" +#include "keepkey/firmware/ton.h" + #include "messages-ethereum.pb.h" #include "messages-binance.pb.h" #include "messages-cosmos.pb.h" @@ -84,8 +96,10 @@ #include "messages-ripple.pb.h" #include "messages-thorchain.pb.h" #include "messages-mayachain.pb.h" - -#include +#include "messages-solana.pb.h" +#include "messages-tron.pb.h" +#include "messages-ton.pb.h" +#endif // BITCOIN_ONLY #define _(X) (X) @@ -272,10 +286,12 @@ void fsm_msgClearSession(ClearSession *msg) { #include "fsm_msg_common.h" #include "fsm_msg_coin.h" +#include "fsm_msg_debug.h" +#include "fsm_msg_crypto.h" +#include "fsm_msg_bip85.h" +#ifndef BITCOIN_ONLY #include "fsm_msg_ethereum.h" #include "fsm_msg_nano.h" -#include "fsm_msg_crypto.h" -#include "fsm_msg_debug.h" #include "fsm_msg_eos.h" #include "fsm_msg_cosmos.h" #include "fsm_msg_osmosis.h" @@ -284,3 +300,7 @@ void fsm_msgClearSession(ClearSession *msg) { #include "fsm_msg_tendermint.h" #include "fsm_msg_thorchain.h" #include "fsm_msg_mayachain.h" +#include "fsm_msg_solana.h" +#include "fsm_msg_tron.h" +#include "fsm_msg_ton.h" +#endif // BITCOIN_ONLY diff --git a/lib/firmware/fsm_msg_bip85.h b/lib/firmware/fsm_msg_bip85.h new file mode 100644 index 000000000..95fcd2c71 --- /dev/null +++ b/lib/firmware/fsm_msg_bip85.h @@ -0,0 +1,67 @@ +void fsm_msgGetBip85Mnemonic(const GetBip85Mnemonic *msg) { + CHECK_INITIALIZED + + /* Validate word count (required field, always present in nanopb) */ + if (msg->word_count != 12 && msg->word_count != 18 && msg->word_count != 24) { + fsm_sendFailure(FailureType_Failure_SyntaxError, + "word_count must be 12, 18, or 24"); + layoutHome(); + return; + } + + /* Reject index >= 0x80000000 (hardened-bit collision) */ + if (msg->index & 0x80000000) { + fsm_sendFailure(FailureType_Failure_SyntaxError, + "index must be less than 2147483648"); + layoutHome(); + return; + } + + CHECK_PIN + + /* User confirmation — make clear a secret is being exported */ + char desc[80]; + snprintf(desc, sizeof(desc), + "Export %lu-word child seed at index %lu to host?", + (unsigned long)msg->word_count, (unsigned long)msg->index); + + if (!confirm(ButtonRequestType_ButtonRequest_Other, + "BIP-85 Export Seed", "%s", desc)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, + "BIP-85 derivation cancelled"); + layoutHome(); + return; + } + + layout_simple_message("Deriving child seed..."); + + /* Derive the mnemonic */ + static CONFIDENTIAL char mnemonic_buf[241]; + if (!bip85_derive_mnemonic(msg->word_count, msg->index, + mnemonic_buf, sizeof(mnemonic_buf))) { + memzero(mnemonic_buf, sizeof(mnemonic_buf)); + fsm_sendFailure(FailureType_Failure_Other, + "BIP-85 derivation failed"); + layoutHome(); + return; + } + + /* Second confirmation before sending secret to host */ + if (!confirm(ButtonRequestType_ButtonRequest_Other, + "Send to Computer?", + "The child seed will be sent to the connected computer. Continue?")) { + memzero(mnemonic_buf, sizeof(mnemonic_buf)); + fsm_sendFailure(FailureType_Failure_ActionCancelled, + "BIP-85 export cancelled"); + layoutHome(); + return; + } + + /* Send response */ + RESP_INIT(Bip85Mnemonic); + strlcpy(resp->mnemonic, mnemonic_buf, sizeof(resp->mnemonic)); + memzero(mnemonic_buf, sizeof(mnemonic_buf)); + + msg_write(MessageType_MessageType_Bip85Mnemonic, resp); + layoutHome(); +} diff --git a/lib/firmware/fsm_msg_common.h b/lib/firmware/fsm_msg_common.h index 33cd3e10d..5bf1a6747 100644 --- a/lib/firmware/fsm_msg_common.h +++ b/lib/firmware/fsm_msg_common.h @@ -2,9 +2,11 @@ void fsm_msgInitialize(Initialize *msg) { (void)msg; recovery_cipher_abort(); signing_abort(); +#ifndef BITCOIN_ONLY ethereum_signing_abort(); tendermint_signAbort(); eos_signingAbort(); +#endif session_clear(false); // do not clear PIN layoutHome(); fsm_msgGetFeatures(0); @@ -149,9 +151,12 @@ void fsm_msgGetCoinTable(GetCoinTable *msg) { for (size_t i = 0; i < msg->end - msg->start; i++) { if (msg->start + i < COINS_COUNT) { resp->table[i] = coins[msg->start + i]; - } else if (msg->start + i - COINS_COUNT < TOKENS_COUNT) { + } +#ifndef BITCOIN_ONLY + else if (msg->start + i - COINS_COUNT < TOKENS_COUNT) { coinFromToken(&resp->table[i], &tokens[msg->start + i - COINS_COUNT]); } +#endif // BITCOIN_ONLY } } @@ -545,9 +550,11 @@ void fsm_msgCancel(Cancel *msg) { (void)msg; recovery_cipher_abort(); signing_abort(); +#ifndef BITCOIN_ONLY ethereum_signing_abort(); tendermint_signAbort(); eos_signingAbort(); +#endif // BITCOIN_ONLY fsm_sendFailure(FailureType_Failure_ActionCancelled, "Aborted"); } diff --git a/lib/firmware/fsm_msg_solana.h b/lib/firmware/fsm_msg_solana.h new file mode 100644 index 000000000..f163da372 --- /dev/null +++ b/lib/firmware/fsm_msg_solana.h @@ -0,0 +1,227 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2024 KeepKey + * + * 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 . + */ + +#include "keepkey/firmware/solana_tx.h" + +void fsm_msgSolanaGetAddress(const SolanaGetAddress *msg) { + RESP_INIT(SolanaAddress); + + CHECK_INITIALIZED + + CHECK_PIN + + // Validate BIP44 path: m/44'/501'/... + if (msg->address_n_count < 2 || + msg->address_n[0] != (0x80000000 | 44) || + msg->address_n[1] != (0x80000000 | 501)) { + fsm_sendFailure(FailureType_Failure_Other, + _("Invalid Solana derivation path (expected m/44'/501'/...)")); + layoutHome(); + return; + } + + // Derive Ed25519 key for Solana (uses Ed25519 curve, not secp256k1) + HDNode *node = fsm_getDerivedNode("ed25519", msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + + // Get Ed25519 public key + uint8_t public_key[32]; + ed25519_publickey(node->private_key, public_key); + + // Convert to Solana address (Base58 encoding) + char address[SOLANA_ADDRESS_SIZE]; + if (!solana_publicKeyToAddress(public_key, address, sizeof(address))) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_Other, + _("Solana address derivation failed")); + layoutHome(); + return; + } + + // Copy address to response + resp->has_address = true; + strlcpy(resp->address, address, sizeof(resp->address)); + + // Show address on display if requested + if (msg->has_show_display && msg->show_display) { + char node_str[NODE_STRING_LENGTH]; + + // Format BIP32 path as string (no coin table entry needed) + if (!bip32_path_to_string(node_str, sizeof(node_str), msg->address_n, + msg->address_n_count)) { + memset(node_str, 0, sizeof(node_str)); + } + + // Confirm address display with user + if (!confirm_ethereum_address(node_str, address)) { + memzero(public_key, sizeof(public_key)); + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_ActionCancelled, + _("Show address cancelled")); + layoutHome(); + return; + } + } + + // Clean up and send response + memzero(public_key, sizeof(public_key)); + memzero(node, sizeof(*node)); + msg_write(MessageType_MessageType_SolanaAddress, resp); + layoutHome(); +} + +void fsm_msgSolanaSignTx(const SolanaSignTx *msg) { + RESP_INIT(SolanaSignedTx); + + CHECK_INITIALIZED + + CHECK_PIN + + // Validate BIP44 path: m/44'/501'/... + if (msg->address_n_count < 2 || + msg->address_n[0] != (0x80000000 | 44) || + msg->address_n[1] != (0x80000000 | 501)) { + fsm_sendFailure(FailureType_Failure_Other, + _("Invalid Solana derivation path (expected m/44'/501'/...)")); + layoutHome(); + return; + } + + // Validate transaction data + if (!msg->has_raw_tx || msg->raw_tx.size == 0) { + fsm_sendFailure(FailureType_Failure_SyntaxError, + _("No transaction data provided")); + layoutHome(); + return; + } + + // Derive Ed25519 key for Solana + HDNode *node = fsm_getDerivedNode("ed25519", msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + + // Get public key for signer identification + uint8_t public_key[32]; + ed25519_publickey(node->private_key, public_key); + + // Parse transaction to display details to user + SolanaParsedTransaction parsed_tx; + if (!solana_parseTransaction(msg->raw_tx.bytes, msg->raw_tx.size, &parsed_tx)) { + memzero(public_key, sizeof(public_key)); + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_SyntaxError, + _("Failed to parse transaction")); + layoutHome(); + return; + } + + // Confirm transaction with user (shows parsed details) + if (!solana_confirmTransaction(&parsed_tx, public_key)) { + memzero(public_key, sizeof(public_key)); + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled"); + layoutHome(); + return; + } + + // Sign the transaction + if (!solana_signTx(node, msg, resp)) { + memzero(public_key, sizeof(public_key)); + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_Other, + _("Failed to sign transaction")); + layoutHome(); + return; + } + + // Clean up and send response + memzero(public_key, sizeof(public_key)); + memzero(node, sizeof(*node)); + msg_write(MessageType_MessageType_SolanaSignedTx, resp); + layoutHome(); +} + +void fsm_msgSolanaSignMessage(const SolanaSignMessage *msg) { + RESP_INIT(SolanaMessageSignature); + + CHECK_INITIALIZED + + CHECK_PIN + + // Validate BIP44 path: m/44'/501'/... + if (msg->address_n_count < 2 || + msg->address_n[0] != (0x80000000 | 44) || + msg->address_n[1] != (0x80000000 | 501)) { + fsm_sendFailure(FailureType_Failure_Other, + _("Invalid Solana derivation path (expected m/44'/501'/...)")); + layoutHome(); + return; + } + + // Validate message data + if (!msg->has_message || msg->message.size == 0) { + fsm_sendFailure(FailureType_Failure_SyntaxError, + _("No message provided")); + layoutHome(); + return; + } + + // Derive Ed25519 key for Solana + HDNode *node = fsm_getDerivedNode("ed25519", msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + + // Get public key + uint8_t public_key[32]; + ed25519_publickey(node->private_key, public_key); + + // Confirm message signing with user (shows message preview) + // Default to showing confirmation when client doesn't set the field + if (!msg->has_show_display || msg->show_display) { + if (!solana_confirmMessage(msg->message.bytes, msg->message.size)) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_ActionCancelled, + _("Message signing cancelled")); + layoutHome(); + return; + } + } + + // Sign the message + uint8_t signature[SOLANA_SIGNATURE_SIZE]; + solana_signMessage(node, msg->message.bytes, msg->message.size, signature); + + // Copy public key and signature to response + resp->has_public_key = true; + resp->public_key.size = 32; + memcpy(resp->public_key.bytes, public_key, 32); + + resp->has_signature = true; + resp->signature.size = SOLANA_SIGNATURE_SIZE; + memcpy(resp->signature.bytes, signature, SOLANA_SIGNATURE_SIZE); + + // Clean up sensitive data + memzero(signature, sizeof(signature)); + memzero(public_key, sizeof(public_key)); + memzero(node, sizeof(*node)); + + msg_write(MessageType_MessageType_SolanaMessageSignature, resp); + layoutHome(); +} diff --git a/lib/firmware/fsm_msg_ton.h b/lib/firmware/fsm_msg_ton.h new file mode 100644 index 000000000..528cb0404 --- /dev/null +++ b/lib/firmware/fsm_msg_ton.h @@ -0,0 +1,154 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2024 KeepKey + * + * 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 . + */ + +void fsm_msgTonGetAddress(const TonGetAddress *msg) { + RESP_INIT(TonAddress); + + CHECK_INITIALIZED + + CHECK_PIN + + // Validate path: m/44'/607'/... (all hardened for TON) + if (msg->address_n_count < 2 || + msg->address_n[0] != (0x80000000 | 44) || + msg->address_n[1] != (0x80000000 | 607)) { + fsm_sendFailure(FailureType_Failure_Other, + _("Invalid TON path (expected m/44'/607'/...)")); + layoutHome(); + return; + } + + // Derive node using Ed25519 curve + HDNode *node = fsm_getDerivedNode(ED25519_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + hdnode_fill_public_key(node); + + // Extract TON-specific parameters with defaults + bool bounceable = msg->has_bounceable ? msg->bounceable : true; + bool testnet = msg->has_testnet ? msg->testnet : false; + int32_t workchain = msg->has_workchain ? msg->workchain : 0; + + // Get TON address from public key (Base64 URL-safe encoding) + char address[MAX_ADDR_SIZE]; + char raw_address[MAX_ADDR_SIZE]; + if (!ton_get_address(&node->public_key[1], bounceable, testnet, workchain, + address, sizeof(address), raw_address, + sizeof(raw_address))) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_Other, _("Can't encode address")); + layoutHome(); + return; + } + + resp->has_address = true; + strlcpy(resp->address, address, sizeof(resp->address)); + resp->has_raw_address = true; + strlcpy(resp->raw_address, raw_address, sizeof(resp->raw_address)); + + // Show address on display if requested + if (msg->has_show_display && msg->show_display) { + char node_str[NODE_STRING_LENGTH]; + if (!bip32_path_to_string(node_str, sizeof(node_str), msg->address_n, + msg->address_n_count)) { + memset(node_str, 0, sizeof(node_str)); + } + + if (!confirm_ethereum_address(node_str, resp->address)) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_ActionCancelled, + _("Show address cancelled")); + layoutHome(); + return; + } + } + + memzero(node, sizeof(*node)); + msg_write(MessageType_MessageType_TonAddress, resp); + layoutHome(); +} + +void fsm_msgTonSignTx(TonSignTx *msg) { + RESP_INIT(TonSignedTx); + + CHECK_INITIALIZED + + CHECK_PIN + + // Validate path: m/44'/607'/... + if (msg->address_n_count < 2 || + msg->address_n[0] != (0x80000000 | 44) || + msg->address_n[1] != (0x80000000 | 607)) { + fsm_sendFailure(FailureType_Failure_Other, + _("Invalid TON path (expected m/44'/607'/...)")); + layoutHome(); + return; + } + + // Derive node using Ed25519 curve + HDNode *node = fsm_getDerivedNode(ED25519_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + hdnode_fill_public_key(node); + + if (!msg->has_raw_tx || msg->raw_tx.size == 0) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_Other, + _("Missing transaction data")); + layoutHome(); + return; + } + + bool needs_confirm = true; + + // Display transaction details if available + if (needs_confirm && msg->has_to_address && msg->has_amount) { + char amount_str[32]; + ton_formatAmount(amount_str, sizeof(amount_str), msg->amount); + + if (!confirm(ButtonRequestType_ButtonRequest_ConfirmOutput, + "Send", "Send %s TON to %s?", + amount_str, msg->to_address)) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled"); + layoutHome(); + return; + } + } + + if (!confirm(ButtonRequestType_ButtonRequest_SignTx, "Transaction", + "Really sign this TON transaction?")) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled"); + layoutHome(); + return; + } + + // Sign the transaction with Ed25519 + if (!ton_signTx(node, msg, resp)) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_Other, _("TON signing failed")); + layoutHome(); + return; + } + + memzero(node, sizeof(*node)); + msg_write(MessageType_MessageType_TonSignedTx, resp); + layoutHome(); +} diff --git a/lib/firmware/fsm_msg_tron.h b/lib/firmware/fsm_msg_tron.h new file mode 100644 index 000000000..7e54ebb30 --- /dev/null +++ b/lib/firmware/fsm_msg_tron.h @@ -0,0 +1,144 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2024 KeepKey + * + * 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 . + */ + +void fsm_msgTronGetAddress(const TronGetAddress *msg) { + RESP_INIT(TronAddress); + + CHECK_INITIALIZED + + CHECK_PIN + + // Validate path: m/44'/195'/... + if (msg->address_n_count < 3 || + msg->address_n[0] != (0x80000000 | 44) || + msg->address_n[1] != (0x80000000 | 195)) { + fsm_sendFailure(FailureType_Failure_Other, + _("Invalid TRON path (expected m/44'/195'/...)")); + layoutHome(); + return; + } + + // Derive node using secp256k1 curve + HDNode *node = fsm_getDerivedNode(SECP256K1_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + hdnode_fill_public_key(node); + + // Get TRON address from public key (Base58Check with prefix 'T') + char address[MAX_ADDR_SIZE]; + if (!tron_getAddress(node->public_key, address, sizeof(address))) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_Other, _("Address derivation failed")); + layoutHome(); + return; + } + + resp->has_address = true; + strlcpy(resp->address, address, sizeof(resp->address)); + + // Show address on display if requested + if (msg->has_show_display && msg->show_display) { + char node_str[NODE_STRING_LENGTH]; + if (!bip32_path_to_string(node_str, sizeof(node_str), msg->address_n, + msg->address_n_count)) { + memset(node_str, 0, sizeof(node_str)); + } + + if (!confirm_ethereum_address(node_str, resp->address)) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_ActionCancelled, + _("Show address cancelled")); + layoutHome(); + return; + } + } + + memzero(node, sizeof(*node)); + msg_write(MessageType_MessageType_TronAddress, resp); + layoutHome(); +} + +void fsm_msgTronSignTx(TronSignTx *msg) { + RESP_INIT(TronSignedTx); + + CHECK_INITIALIZED + + CHECK_PIN + + // Validate path: m/44'/195'/... + if (msg->address_n_count < 3 || + msg->address_n[0] != (0x80000000 | 44) || + msg->address_n[1] != (0x80000000 | 195)) { + fsm_sendFailure(FailureType_Failure_Other, + _("Invalid TRON path (expected m/44'/195'/...)")); + layoutHome(); + return; + } + + // Derive node using secp256k1 curve + HDNode *node = fsm_getDerivedNode(SECP256K1_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + hdnode_fill_public_key(node); + + if (!msg->has_raw_data || msg->raw_data.size == 0) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_Other, + _("Missing transaction data")); + layoutHome(); + return; + } + + bool needs_confirm = true; + + // Display transaction details if available + if (needs_confirm && msg->has_to_address && msg->has_amount) { + char amount_str[32]; + tron_formatAmount(amount_str, sizeof(amount_str), msg->amount); + + if (!confirm(ButtonRequestType_ButtonRequest_ConfirmOutput, + "Send", "Send %s TRX to %s?", + amount_str, msg->to_address)) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled"); + layoutHome(); + return; + } + } + + if (!confirm(ButtonRequestType_ButtonRequest_SignTx, "Transaction", + "Really sign this TRON transaction?")) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled"); + layoutHome(); + return; + } + + // Sign the transaction with secp256k1 + if (!tron_signTx(node, msg, resp)) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_Other, _("TRON signing failed")); + layoutHome(); + return; + } + + memzero(node, sizeof(*node)); + msg_write(MessageType_MessageType_TronSignedTx, resp); + layoutHome(); +} diff --git a/lib/firmware/messagemap.def b/lib/firmware/messagemap.def index e8e374386..0a1abd067 100644 --- a/lib/firmware/messagemap.def +++ b/lib/firmware/messagemap.def @@ -33,6 +33,10 @@ MSG_IN(MessageType_MessageType_RecoveryDevice, RecoveryDevice, fsm_msgRecoveryDevice) MSG_IN(MessageType_MessageType_CharacterAck, CharacterAck, fsm_msgCharacterAck) MSG_IN(MessageType_MessageType_ApplyPolicies, ApplyPolicies, fsm_msgApplyPolicies) + + MSG_IN(MessageType_MessageType_GetBip85Mnemonic, GetBip85Mnemonic, fsm_msgGetBip85Mnemonic) + +#ifndef BITCOIN_ONLY MSG_IN(MessageType_MessageType_EthereumGetAddress, EthereumGetAddress, fsm_msgEthereumGetAddress) MSG_IN(MessageType_MessageType_EthereumSignTx, EthereumSignTx, fsm_msgEthereumSignTx) MSG_IN(MessageType_MessageType_EthereumTxAck, EthereumTxAck, fsm_msgEthereumTxAck) @@ -73,6 +77,16 @@ MSG_IN(MessageType_MessageType_MayachainSignTx, MayachainSignTx, fsm_msgMayachainSignTx) MSG_IN(MessageType_MessageType_MayachainMsgAck, MayachainMsgAck, fsm_msgMayachainMsgAck) + MSG_IN(MessageType_MessageType_SolanaGetAddress, SolanaGetAddress, fsm_msgSolanaGetAddress) + MSG_IN(MessageType_MessageType_SolanaSignTx, SolanaSignTx, fsm_msgSolanaSignTx) + MSG_IN(MessageType_MessageType_SolanaSignMessage, SolanaSignMessage, fsm_msgSolanaSignMessage) + + MSG_IN(MessageType_MessageType_TronGetAddress, TronGetAddress, fsm_msgTronGetAddress) + MSG_IN(MessageType_MessageType_TronSignTx, TronSignTx, fsm_msgTronSignTx) + + MSG_IN(MessageType_MessageType_TonGetAddress, TonGetAddress, fsm_msgTonGetAddress) + MSG_IN(MessageType_MessageType_TonSignTx, TonSignTx, fsm_msgTonSignTx) +#endif // BITCOIN_ONLY /* Normal Out Messages */ MSG_OUT(MessageType_MessageType_Success, Success, NO_PROCESS_FUNC) MSG_OUT(MessageType_MessageType_Failure, Failure, NO_PROCESS_FUNC) @@ -95,6 +109,10 @@ MSG_OUT(MessageType_MessageType_PassphraseRequest, PassphraseRequest, NO_PROCESS_FUNC) MSG_OUT(MessageType_MessageType_WordRequest, WordRequest, NO_PROCESS_FUNC) MSG_OUT(MessageType_MessageType_CharacterRequest, CharacterRequest, NO_PROCESS_FUNC) + + MSG_OUT(MessageType_MessageType_Bip85Mnemonic, Bip85Mnemonic, NO_PROCESS_FUNC) + +#ifndef BITCOIN_ONLY MSG_OUT(MessageType_MessageType_EthereumAddress, EthereumAddress, NO_PROCESS_FUNC) MSG_OUT(MessageType_MessageType_EthereumTxRequest, EthereumTxRequest, NO_PROCESS_FUNC) MSG_OUT(MessageType_MessageType_EthereumMessageSignature, EthereumMessageSignature, NO_PROCESS_FUNC) @@ -132,6 +150,16 @@ MSG_OUT(MessageType_MessageType_MayachainMsgRequest, MayachainMsgRequest, NO_PROCESS_FUNC) MSG_OUT(MessageType_MessageType_MayachainSignedTx, MayachainSignedTx, NO_PROCESS_FUNC) + MSG_OUT(MessageType_MessageType_SolanaAddress, SolanaAddress, NO_PROCESS_FUNC) + MSG_OUT(MessageType_MessageType_SolanaSignedTx, SolanaSignedTx, NO_PROCESS_FUNC) + MSG_OUT(MessageType_MessageType_SolanaMessageSignature, SolanaMessageSignature, NO_PROCESS_FUNC) + + MSG_OUT(MessageType_MessageType_TronAddress, TronAddress, NO_PROCESS_FUNC) + MSG_OUT(MessageType_MessageType_TronSignedTx, TronSignedTx, NO_PROCESS_FUNC) + + MSG_OUT(MessageType_MessageType_TonAddress, TonAddress, NO_PROCESS_FUNC) + MSG_OUT(MessageType_MessageType_TonSignedTx, TonSignedTx, NO_PROCESS_FUNC) +#endif // BITCOIN_ONLY #if DEBUG_LINK /* Debug Messages */ DEBUG_IN(MessageType_MessageType_DebugLinkDecision, DebugLinkDecision, NO_PROCESS_FUNC) diff --git a/lib/firmware/recovery_cipher.c b/lib/firmware/recovery_cipher.c index 9677db5c6..0c8d4775a 100644 --- a/lib/firmware/recovery_cipher.c +++ b/lib/firmware/recovery_cipher.c @@ -462,6 +462,24 @@ void recovery_character(const char *character) { } } } else { + /* Per-word BIP39 validation: reject immediately if the decoded word + * doesn't match any entry in the wordlist. */ + if (enforce_wordlist && strlen(decoded_word) > 0) { + static CONFIDENTIAL char check_word[CURRENT_WORD_BUF]; + strlcpy(check_word, decoded_word, sizeof(check_word)); + bool valid = attempt_auto_complete(check_word); + memzero(check_word, sizeof(check_word)); + if (!valid) { + memzero(coded_word, sizeof(coded_word)); + memzero(decoded_word, sizeof(decoded_word)); + recovery_cipher_abort(); + fsm_sendFailure(FailureType_Failure_SyntaxError, + "Word not found in BIP39 wordlist"); + layoutHome(); + return; + } + } + memzero(coded_word, sizeof(coded_word)); memzero(decoded_word, sizeof(decoded_word)); @@ -575,7 +593,7 @@ void recovery_cipher_finalize(void) { } memzero(temp_word, sizeof(temp_word)); - if (!auto_completed && !enforce_wordlist) { + if (!auto_completed && enforce_wordlist) { if (!dry_run) { storage_reset(); } diff --git a/lib/firmware/signing.c b/lib/firmware/signing.c index ccc38c226..b6f3d4460 100644 --- a/lib/firmware/signing.c +++ b/lib/firmware/signing.c @@ -624,7 +624,7 @@ void signing_init(const SignTx *msg, const CoinType *_coin, branch_id = 0x5BA81B19; // Overwinter break; case 4: - branch_id = 0x76B809BB; // Sapling + branch_id = 0xC8E71055; // NU6 break; } } diff --git a/lib/firmware/solana.c b/lib/firmware/solana.c new file mode 100644 index 000000000..d0630b615 --- /dev/null +++ b/lib/firmware/solana.c @@ -0,0 +1,103 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2024 KeepKey + * + * 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 . + */ + +#include "keepkey/firmware/solana.h" +#include "keepkey/firmware/solana_tx.h" +#include "keepkey/firmware/fsm.h" +#include "trezor/crypto/ed25519-donna/ed25519.h" +#include "trezor/crypto/base58.h" +#include "trezor/crypto/memzero.h" + +#include + +/* + * Convert Ed25519 public key to Solana Base58 address + * + * Solana addresses are simply the Base58-encoded Ed25519 public key (32 bytes) + */ +bool solana_publicKeyToAddress(const uint8_t public_key[32], char *address, + size_t address_size) { + if (!public_key || !address || address_size < SOLANA_ADDRESS_SIZE) { + return false; + } + + // Solana uses plain Base58 encoding (NO checksum) for addresses + // The public key is 32 bytes, which encodes to ~43-44 Base58 characters + size_t len = address_size; + if (!b58enc(address, &len, public_key, 32)) { + return false; + } + + return len > 0; +} + +/* + * Sign Solana transaction + * + * Signs the MESSAGE portion of the raw transaction bytes with Ed25519. + * Solana transaction wire format: [sig_count (compact-u16)][sig_count × 64-byte signatures][message] + * Only the message portion is signed — the signature envelope is NOT part of the signed data. + */ +bool solana_signTx(const HDNode *node, const SolanaSignTx *msg, + SolanaSignedTx *resp) { + if (!node || !msg || !resp) { + return false; + } + + // Validate we have transaction data + if (!msg->has_raw_tx || msg->raw_tx.size == 0) { + return false; + } + + // Extract message bytes by skipping the signature envelope. + const uint8_t *data = msg->raw_tx.bytes; + size_t remaining = msg->raw_tx.size; + + // Read num_signatures (compact-u16) + uint16_t num_sigs; + if (!read_compact_u16(&data, &remaining, &num_sigs)) return false; + + // Skip past the dummy signatures (64 bytes each) + size_t sigs_size = (size_t)num_sigs * 64; + if (remaining < sigs_size) return false; + data += sigs_size; + remaining -= sigs_size; + + // 'data' now points to the message, 'remaining' is the message length + + // Get Ed25519 public key + uint8_t public_key[32]; + ed25519_publickey(node->private_key, public_key); + + // Allocate buffer for signature (64 bytes for Ed25519) + uint8_t signature[SOLANA_SIGNATURE_SIZE]; + + // Sign ONLY the message bytes (not the signature envelope) + ed25519_sign(data, remaining, node->private_key, public_key, signature); + + // Copy signature to response + resp->has_signature = true; + resp->signature.size = SOLANA_SIGNATURE_SIZE; + memcpy(resp->signature.bytes, signature, SOLANA_SIGNATURE_SIZE); + + // Clean up sensitive data + memzero(public_key, sizeof(public_key)); + memzero(signature, sizeof(signature)); + return true; +} diff --git a/lib/firmware/solana_msg.c b/lib/firmware/solana_msg.c new file mode 100644 index 000000000..b90eb2cbc --- /dev/null +++ b/lib/firmware/solana_msg.c @@ -0,0 +1,110 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2024 KeepKey + * + * 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 . + */ + +#include "keepkey/firmware/solana.h" +#include "keepkey/firmware/fsm.h" +#include "keepkey/board/confirm_sm.h" +#include "trezor/crypto/ed25519-donna/ed25519.h" +#include "trezor/crypto/memzero.h" + +#include +#include + +/* + * Sign Solana message (off-chain signature) + * + * This is used for authentication, proof-of-ownership, dApp interactions, etc. + * Solana signs raw message bytes directly with Ed25519 (no prefix). + * This matches Phantom, Solflare, and other standard Solana wallets. + */ +void solana_signMessage(const HDNode *node, const uint8_t *message, + size_t message_len, uint8_t *signature_out) { + if (!node || !message || !signature_out) { + return; + } + + // Get Ed25519 public key + uint8_t public_key[32]; + ed25519_publickey(node->private_key, public_key); + + // Sign the raw message with Ed25519 (no prefix - this is Solana standard) + ed25519_sign(message, message_len, node->private_key, public_key, + signature_out); + + // Clean up sensitive data + memzero(public_key, sizeof(public_key)); +} + +/* + * Display message to user for confirmation + * Shows first N chars of message in readable format + */ +bool solana_confirmMessage(const uint8_t *message, size_t message_len) { + if (!message || message_len == 0) { + return false; + } + + // Prepare message preview (first 64 chars or less) + char preview[65]; + size_t preview_len = message_len < 64 ? message_len : 64; + + // Check if message is printable ASCII + bool is_printable = true; + for (size_t i = 0; i < preview_len; i++) { + if (message[i] < 32 || message[i] > 126) { + is_printable = false; + break; + } + } + + if (is_printable) { + // Show text preview + memcpy(preview, message, preview_len); + preview[preview_len] = '\0'; + + if (message_len > 64) { + // Truncate with ... + preview[61] = '.'; + preview[62] = '.'; + preview[63] = '.'; + preview[64] = '\0'; + } + + return confirm(ButtonRequestType_ButtonRequest_Other, + "Sign Message", + "Sign message:\n%s\n\n(%u bytes total)", + preview, (unsigned int)message_len); + } else { + // Show hex preview for binary data + char hex_preview[17]; + size_t hex_len = message_len < 8 ? message_len : 8; + + for (size_t i = 0; i < hex_len; i++) { + snprintf(hex_preview + (i * 2), 3, "%02x", message[i]); + } + hex_preview[hex_len * 2] = '\0'; + + return confirm(ButtonRequestType_ButtonRequest_Other, + "Sign Message", + "Sign binary message:\n0x%s%s\n\n(%u bytes)", + hex_preview, + message_len > 8 ? "..." : "", + (unsigned int)message_len); + } +} diff --git a/lib/firmware/solana_tx.c b/lib/firmware/solana_tx.c new file mode 100644 index 000000000..dbaed9815 --- /dev/null +++ b/lib/firmware/solana_tx.c @@ -0,0 +1,350 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2024 KeepKey + * + * 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 . + */ + +#include "keepkey/firmware/solana_tx.h" +#include "keepkey/firmware/solana.h" +#include "keepkey/board/confirm_sm.h" +#include "keepkey/board/layout.h" +#include "trezor/crypto/memzero.h" + +#include +#include + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +// Compact-u16 encoding used by Solana (little-endian varint, bit 7 = continuation) +bool read_compact_u16(const uint8_t **data, size_t *remaining, uint16_t *out) { + if (*remaining < 1) return false; + uint16_t val = 0; + int shift = 0; + for (int i = 0; i < 3; i++) { + if (*remaining < 1) return false; + uint8_t b = (*data)[0]; + (*data)++; (*remaining)--; + val |= (uint16_t)(b & 0x7F) << shift; + if ((b & 0x80) == 0) { *out = val; return true; } + shift += 7; + } + return false; // too many continuation bytes +} + +bool solana_parseTransaction(const uint8_t *raw_tx, size_t tx_size, + SolanaParsedTransaction *parsed) { + if (!raw_tx || !parsed || tx_size < 3) { + return false; + } + + memzero(parsed, sizeof(*parsed)); + + const uint8_t *data = raw_tx; + size_t remaining = tx_size; + + // Read number of signatures (compact-u16) + uint16_t num_sigs; + if (!read_compact_u16(&data, &remaining, &num_sigs)) return false; + parsed->num_signatures = num_sigs; + + // Skip signatures (64 bytes each) + size_t sigs_size = num_sigs * 64; + if (remaining < sigs_size) return false; + data += sigs_size; + remaining -= sigs_size; + + // Check for v0 versioned transaction (version byte = 0x80) + bool is_versioned = false; + if (remaining > 0 && data[0] == 0x80) { + is_versioned = true; + data++; // Skip version byte + remaining--; + } + + // Read message header (3 bytes) + if (remaining < 3) return false; + parsed->num_required_signatures = data[0]; + parsed->num_readonly_signed = data[1]; + parsed->num_readonly_unsigned = data[2]; + data += 3; + remaining -= 3; + + // Read account keys (store up to 16, but advance past ALL of them) + uint16_t num_accounts; + if (!read_compact_u16(&data, &remaining, &num_accounts)) return false; + parsed->num_accounts = MIN(num_accounts, 16); + + for (int i = 0; i < num_accounts; i++) { + if (remaining < 32) return false; + if (i < 16) { + memcpy(parsed->account_keys[i], data, 32); + } + data += 32; + remaining -= 32; + } + + // Read recent blockhash + if (remaining < 32) return false; + memcpy(parsed->recent_blockhash, data, 32); + data += 32; + remaining -= 32; + + // Read instructions (store up to 8, but parse ALL to advance data pointer) + uint16_t num_instructions; + if (!read_compact_u16(&data, &remaining, &num_instructions)) return false; + parsed->num_instructions = MIN(num_instructions, 8); + + for (int i = 0; i < num_instructions; i++) { + // Use a stack variable for instructions beyond our storage limit + SolanaInstruction overflow_instr; + SolanaInstruction *instr = (i < 8) ? &parsed->instructions[i] : &overflow_instr; + memzero(instr, sizeof(SolanaInstruction)); + + // Read program ID index + if (remaining < 1) return false; + uint8_t program_idx = data[0]; + data++; + remaining--; + + // Check if program_idx references an account we have stored + if (program_idx < parsed->num_accounts) { + memcpy(instr->program_id, parsed->account_keys[program_idx], 32); + } else if (program_idx < num_accounts || is_versioned) { + // Index beyond stored accounts (but valid in static list) or ALT reference + memzero(instr->program_id, 32); + instr->type = SOLANA_INSTRUCTION_UNKNOWN; + } else { + // Invalid index for legacy transactions + return false; + } + + // Read account indices (store up to 16, but advance past ALL of them) + uint16_t num_acct_indices; + if (!read_compact_u16(&data, &remaining, &num_acct_indices)) return false; + instr->num_accounts = MIN(num_acct_indices, 16); + + if (remaining < num_acct_indices) return false; + for (int j = 0; j < instr->num_accounts; j++) { + instr->account_indices[j] = data[j]; + } + data += num_acct_indices; + remaining -= num_acct_indices; + + // Read instruction data + uint16_t data_len; + if (!read_compact_u16(&data, &remaining, &data_len)) return false; + + if (remaining < data_len) return false; + instr->data = data; + instr->data_len = data_len; + data += data_len; + remaining -= data_len; + + // Identify instruction type + if (instr->type != SOLANA_INSTRUCTION_UNKNOWN) { + instr->type = solana_identifyInstruction(instr->program_id, + instr->data, instr->data_len); + } + } + + // For v0 transactions, skip Address Lookup Tables section + if (is_versioned && remaining > 0) { + // Read number of address table lookups + uint16_t num_alt; + if (!read_compact_u16(&data, &remaining, &num_alt)) return false; + + // Skip each lookup table entry + for (int i = 0; i < num_alt; i++) { + // Skip account key (32 bytes) + if (remaining < 32) return false; + data += 32; + remaining -= 32; + + // Skip writable indexes array + uint16_t num_writable; + if (!read_compact_u16(&data, &remaining, &num_writable)) return false; + if (remaining < num_writable) return false; + data += num_writable; + remaining -= num_writable; + + // Skip readonly indexes array + uint16_t num_readonly; + if (!read_compact_u16(&data, &remaining, &num_readonly)) return false; + if (remaining < num_readonly) return false; + data += num_readonly; + remaining -= num_readonly; + } + } + + return true; +} + +SolanaInstructionType solana_identifyInstruction(const uint8_t *program_id, + const uint8_t *data, + size_t data_len) { + if (!program_id || !data || data_len < 4) { + return SOLANA_INSTRUCTION_UNKNOWN; + } + + // System Program + if (memcmp(program_id, SOLANA_SYSTEM_PROGRAM_ID, 32) == 0) { + uint32_t discriminator = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); + + if (discriminator == 2 && data_len == 12) { + return SOLANA_INSTRUCTION_SYSTEM_TRANSFER; + } + if (discriminator == 0) { + return SOLANA_INSTRUCTION_SYSTEM_CREATE_ACCOUNT; + } + } + + // Token Program + if (memcmp(program_id, SOLANA_TOKEN_PROGRAM_ID, 32) == 0) { + if (data_len < 1) return SOLANA_INSTRUCTION_UNKNOWN; + + uint8_t instruction_type = data[0]; + + if (instruction_type == 3) { // Transfer + return SOLANA_INSTRUCTION_TOKEN_TRANSFER; + } + if (instruction_type == 12) { // TransferChecked + return SOLANA_INSTRUCTION_TOKEN_TRANSFER_CHECKED; + } + if (instruction_type == 4) { // Approve + return SOLANA_INSTRUCTION_TOKEN_APPROVE; + } + } + + // Stake Program + if (memcmp(program_id, SOLANA_STAKE_PROGRAM_ID, 32) == 0) { + if (data_len < 4) return SOLANA_INSTRUCTION_UNKNOWN; + + uint32_t instruction_type = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); + + if (instruction_type == 2) { + return SOLANA_INSTRUCTION_STAKE_DELEGATE; + } + if (instruction_type == 4) { + return SOLANA_INSTRUCTION_STAKE_WITHDRAW; + } + } + + return SOLANA_INSTRUCTION_UNKNOWN; +} + +bool solana_parseSystemTransfer(const uint8_t *data, size_t len, + SolanaSystemTransfer *transfer) { + if (!data || !transfer || len < 12) { + return false; + } + + // Skip discriminator (4 bytes), read lamports (8 bytes, little-endian) + const uint8_t *lamports_ptr = data + 4; + transfer->lamports = 0; + for (int i = 0; i < 8; i++) { + transfer->lamports |= ((uint64_t)lamports_ptr[i]) << (i * 8); + } + + return true; +} + +bool solana_parseTokenTransfer(const uint8_t *data, size_t len, + SolanaTokenTransfer *transfer) { + if (!data || !transfer || len < 9) { + return false; + } + + // Parse amount (bytes 1-8, little-endian) — common to both Transfer and TransferChecked + transfer->amount = 0; + for (int i = 0; i < 8; i++) { + transfer->amount |= ((uint64_t)data[1 + i]) << (i * 8); + } + + // TransferChecked (instruction_type 12): has extra decimals byte at offset 9 + if (len >= 10 && data[0] == 12) { + transfer->decimals = data[9]; + } else { + transfer->decimals = 9; // Default for SPL tokens + } + return true; +} + +void solana_formatLamports(uint64_t lamports, char *out, size_t out_len) { + if (!out || out_len < 30) return; + + // Convert lamports to SOL (1 SOL = 1,000,000,000 lamports) + uint64_t sol = lamports / 1000000000; + uint64_t remainder = lamports % 1000000000; + + if (remainder == 0) { + snprintf(out, out_len, "%llu SOL", (unsigned long long)sol); + } else { + // Remove trailing zeros + char frac[10]; + snprintf(frac, sizeof(frac), "%09llu", (unsigned long long)remainder); + int len = 9; + while (len > 0 && frac[len - 1] == '0') { + frac[--len] = '\0'; + } + snprintf(out, out_len, "%llu.%s SOL", (unsigned long long)sol, frac); + } +} + +bool solana_confirmTransaction(const SolanaParsedTransaction *tx, + const uint8_t *signer_pubkey) { + (void)signer_pubkey; // Reserved for future use in multi-sig validation + + if (!tx || tx->num_instructions == 0) { + return false; + } + + // For now, handle simple single-instruction transactions + const SolanaInstruction *instr = &tx->instructions[0]; + + if (instr->type == SOLANA_INSTRUCTION_SYSTEM_TRANSFER) { + SolanaSystemTransfer transfer; + if (!solana_parseSystemTransfer(instr->data, instr->data_len, &transfer)) { + return false; + } + + // Get recipient address (account index 1 for System Transfer) + if (instr->num_accounts < 2) return false; + uint8_t to_idx = instr->account_indices[1]; + if (to_idx >= tx->num_accounts) return false; + + char to_address[SOLANA_ADDRESS_SIZE]; + if (!solana_publicKeyToAddress(tx->account_keys[to_idx], + to_address, sizeof(to_address))) { + return false; + } + + char amount_str[32]; + solana_formatLamports(transfer.lamports, amount_str, sizeof(amount_str)); + + return confirm(ButtonRequestType_ButtonRequest_SignTx, + "Solana Transfer", + "Send %s to\n%s?", + amount_str, to_address); + } + + // Unknown or complex transaction - show warning + return confirm(ButtonRequestType_ButtonRequest_SignTx, + "Solana Transaction", + "Sign transaction with %d instruction(s)?", + tx->num_instructions); +} diff --git a/lib/firmware/ton.c b/lib/firmware/ton.c new file mode 100644 index 000000000..7dbc0181a --- /dev/null +++ b/lib/firmware/ton.c @@ -0,0 +1,254 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2024 KeepKey + * + * 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 . + */ + +#include "keepkey/firmware/ton.h" + +#include "trezor/crypto/ed25519-donna/ed25519.h" +#include "trezor/crypto/hasher.h" +#include "trezor/crypto/memzero.h" +#include "trezor/crypto/sha2.h" + +#include +#include + +// Base64 URL-safe alphabet (RFC 4648) +static const char base64_url_alphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +/** + * Encode data to Base64 URL-safe format (without padding) + */ +static bool base64_url_encode(const uint8_t *data, size_t data_len, char *out, + size_t out_len) { + size_t required_len = ((data_len + 2) / 3) * 4; + if (out_len < required_len + 1) { + return false; + } + + size_t i = 0, j = 0; + while (i < data_len) { + uint32_t octet_a = i < data_len ? data[i++] : 0; + uint32_t octet_b = i < data_len ? data[i++] : 0; + uint32_t octet_c = i < data_len ? data[i++] : 0; + + uint32_t triple = (octet_a << 16) | (octet_b << 8) | octet_c; + + out[j++] = base64_url_alphabet[(triple >> 18) & 0x3F]; + out[j++] = base64_url_alphabet[(triple >> 12) & 0x3F]; + out[j++] = base64_url_alphabet[(triple >> 6) & 0x3F]; + out[j++] = base64_url_alphabet[triple & 0x3F]; + } + + // Remove padding for URL-safe variant + size_t padding = (3 - (data_len % 3)) % 3; + j -= padding; + out[j] = '\0'; + + return true; +} + +/** + * Compute CRC16-XMODEM checksum for TON address + */ +static uint16_t ton_crc16(const uint8_t *data, size_t len) { + uint16_t crc = 0; + + for (size_t i = 0; i < len; i++) { + crc ^= (uint16_t)data[i] << 8; + for (int j = 0; j < 8; j++) { + if (crc & 0x8000) { + crc = (crc << 1) ^ 0x1021; + } else { + crc <<= 1; + } + } + } + + return crc; +} + +// Well-known v4r2 wallet contract code cell hash (constant) +// Source: https://github.com/ton-blockchain/wallet-contract (WalletV4R2) +static const uint8_t V4R2_CODE_HASH[32] = { + 0xfe, 0xb5, 0xff, 0x68, 0x20, 0xe2, 0xff, 0x0d, + 0x94, 0x83, 0xe7, 0xe0, 0xd6, 0x2c, 0x81, 0x7d, + 0x84, 0x67, 0x89, 0xfb, 0x4a, 0xe5, 0x80, 0xc8, + 0x78, 0x86, 0x6d, 0x95, 0x9d, 0xab, 0xd5, 0xc0}; +// Code cell depth in the cell tree +#define V4R2_CODE_DEPTH 7 +// Standard v4r2 wallet_id for mainnet workchain 0 +#define V4R2_WALLET_ID 698983191u // 0x29A9A317 + +/** + * Compute the v4r2 data cell representation hash. + * Data cell layout: seqno(32b=0) + wallet_id(32b) + pubkey(256b) + plugins(1b=0) + * Total: 321 bits, d1=0x00 (no refs), d2=0x51 (floor(321/8)+ceil(321/8)=40+41=81) + * Augmented data: 40 full bytes + 0x40 (plugin=0, completion=1, pad=000000) + */ +static void ton_data_cell_hash(const uint8_t *public_key, uint8_t *out) { + // repr = d1(1) + d2(1) + augmented_data(41) = 43 bytes + uint8_t repr[43]; + repr[0] = 0x00; // d1: no refs + repr[1] = 0x51; // d2: 81 decimal + + // seqno = 0 (4 bytes) + memset(repr + 2, 0, 4); + + // wallet_id = 698983191 = 0x29A9A317 (4 bytes big-endian) + repr[6] = (V4R2_WALLET_ID >> 24) & 0xFF; + repr[7] = (V4R2_WALLET_ID >> 16) & 0xFF; + repr[8] = (V4R2_WALLET_ID >> 8) & 0xFF; + repr[9] = (V4R2_WALLET_ID) & 0xFF; + + // public key (32 bytes) + memcpy(repr + 10, public_key, 32); + + // plugins = 0 (1 bit) + completion tag (1 bit) + padding (6 bits) = 0x40 + repr[42] = 0x40; + + sha256_Raw(repr, 43, out); +} + +/** + * Compute the v4r2 StateInit representation hash. + * StateInit: split_depth(0) + special(0) + code(1,ref) + data(1,ref) + library(0) + * Total: 5 bits, d1=0x02 (2 refs), d2=0x01 + * Augmented: 00110 + 100 = 0x34 + * repr = d1 + d2 + data + depth(code) + depth(data) + hash(code) + hash(data) + */ +static void ton_stateinit_hash(const uint8_t *public_key, uint8_t *out) { + uint8_t data_hash[32]; + ton_data_cell_hash(public_key, data_hash); + + // repr = d1(1) + d2(1) + augmented(1) + code_depth(2) + data_depth(2) + + // code_hash(32) + data_hash(32) = 71 bytes + uint8_t repr[71]; + repr[0] = 0x02; // d1: 2 refs + repr[1] = 0x01; // d2: 1 + repr[2] = 0x34; // augmented: 00110100 + + // code cell depth = 7 (2 bytes big-endian) + repr[3] = 0x00; + repr[4] = V4R2_CODE_DEPTH; + + // data cell depth = 0 (no refs) + repr[5] = 0x00; + repr[6] = 0x00; + + // code cell hash (32 bytes) + memcpy(repr + 7, V4R2_CODE_HASH, 32); + + // data cell hash (32 bytes) + memcpy(repr + 39, data_hash, 32); + + sha256_Raw(repr, 71, out); + memzero(data_hash, sizeof(data_hash)); +} + +/** + * Generate TON v4r2 wallet address from Ed25519 public key. + * Address = base64url(tag || workchain || sha256(StateInit) || crc16) + * where StateInit = code_cell(v4r2) + data_cell(seqno=0, walletId, pubkey) + */ +bool ton_get_address(const ed25519_public_key public_key, bool bounceable, + bool testnet, int32_t workchain, char *address, + size_t address_len, char *raw_address, + size_t raw_address_len) { + if (address_len < TON_ADDRESS_MAX_LEN || + raw_address_len < TON_RAW_ADDRESS_MAX_LEN) { + return false; + } + + // Compute the v4r2 StateInit representation hash — this IS the address hash + uint8_t hash[32]; + ton_stateinit_hash(public_key, hash); + + // Construct address data: [tag][workchain][hash][crc16] + uint8_t addr_data[36]; + uint8_t tag = bounceable ? 0x11 : 0x51; + if (testnet) tag |= 0x80; + + addr_data[0] = tag; + addr_data[1] = (uint8_t)workchain; + memcpy(addr_data + 2, hash, 32); + + // Compute CRC16 checksum + uint16_t crc = ton_crc16(addr_data, 34); + addr_data[34] = (crc >> 8) & 0xFF; + addr_data[35] = crc & 0xFF; + + // Encode to Base64 URL-safe + if (!base64_url_encode(addr_data, 36, address, address_len)) { + memzero(hash, sizeof(hash)); + memzero(addr_data, sizeof(addr_data)); + return false; + } + + // Generate raw address format: workchain:hash_hex + char hash_hex[65]; + for (int i = 0; i < 32; i++) { + snprintf(hash_hex + (i * 2), 3, "%02x", hash[i]); + } + snprintf(raw_address, raw_address_len, "%ld:%s", (long)workchain, hash_hex); + + // Clean up sensitive data + memzero(hash, sizeof(hash)); + memzero(addr_data, sizeof(addr_data)); + + return true; +} + +/** + * Format TON amount (nanoTON) for display + * 1 TON = 1,000,000,000 nanoTON + */ +void ton_formatAmount(char *buf, size_t len, uint64_t amount) { + bignum256 val; + bn_read_uint64(amount, &val); + bn_format(&val, NULL, " TON", TON_DECIMALS, 0, false, buf, len); +} + +/** + * Sign a TON transaction with Ed25519 + */ +bool ton_signTx(const HDNode *node, const TonSignTx *msg, TonSignedTx *resp) { + if (!node || !msg || !resp) { + return false; + } + + // Verify we have raw transaction data + if (!msg->has_raw_tx || msg->raw_tx.size == 0) { + return false; + } + + // Ed25519 sign the transaction + ed25519_signature signature; + ed25519_sign(msg->raw_tx.bytes, msg->raw_tx.size, node->private_key, + &node->public_key[1], signature); + + // Copy signature to response (64 bytes) + resp->has_signature = true; + resp->signature.size = 64; + memcpy(resp->signature.bytes, signature, 64); + + // Zero out the signature buffer for security + memzero(signature, sizeof(signature)); + + return true; +} diff --git a/lib/firmware/transaction.c b/lib/firmware/transaction.c index aa37285e0..b63485ddd 100644 --- a/lib/firmware/transaction.c +++ b/lib/firmware/transaction.c @@ -237,15 +237,20 @@ int compile_output(const CoinType *coin, const HDNode *root, TxOutputType *in, return -1; // user aborted } } else { +#ifndef BITCOIN_ONLY // is this thorchain data? if (!thorchain_parseConfirmMemo((const char *)in->op_return_data.bytes, (size_t)in->op_return_data.size)) { +#endif if (!confirm_data(ButtonRequestType_ButtonRequest_ConfirmOutput, _("Confirm OP_RETURN"), in->op_return_data.bytes, in->op_return_data.size)) { return -1; // user aborted } +#ifndef BITCOIN_ONLY } +#endif } + } uint32_t r = 0; out->script_pubkey.bytes[0] = 0x6A; diff --git a/lib/firmware/tron.c b/lib/firmware/tron.c new file mode 100644 index 000000000..cbd5b0b4b --- /dev/null +++ b/lib/firmware/tron.c @@ -0,0 +1,132 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2024 KeepKey + * + * 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 . + */ + +#include "keepkey/firmware/tron.h" + +#include "keepkey/crypto/curves.h" +#include "trezor/crypto/base58.h" +#include "trezor/crypto/ecdsa.h" +#include "trezor/crypto/memzero.h" +#include "trezor/crypto/secp256k1.h" +#include "trezor/crypto/sha3.h" + +#include + +#define TRON_ADDRESS_PREFIX 0x41 // Mainnet addresses start with 'T' + +/** + * Generate TRON address from secp256k1 public key + * TRON uses Keccak256(uncompressed_pubkey) and takes last 20 bytes, + * then prepends 0x41 and Base58Check encodes it + */ +bool tron_getAddress(const uint8_t public_key[33], char *address, + size_t address_len) { + if (address_len < TRON_ADDRESS_MAX_LEN) { + return false; + } + + uint8_t uncompressed_pubkey[65]; + uint8_t hash[32]; + uint8_t addr_bytes[21]; + + // Uncompress the public key + if (!ecdsa_uncompress_pubkey(&secp256k1, public_key, uncompressed_pubkey)) { + return false; + } + + // Keccak256 hash of uncompressed public key (skip first 0x04 byte) + keccak_256(uncompressed_pubkey + 1, 64, hash); + + // Take last 20 bytes of hash and prepend TRON prefix byte + addr_bytes[0] = TRON_ADDRESS_PREFIX; + memcpy(addr_bytes + 1, hash + 12, 20); + + // Base58Check encode with double SHA256 + if (!base58_encode_check(addr_bytes, 21, HASHER_SHA2D, address, + address_len)) { + return false; + } + + // Clean up sensitive data + memzero(uncompressed_pubkey, sizeof(uncompressed_pubkey)); + memzero(hash, sizeof(hash)); + + return true; +} + +/** + * Format TRON amount (SUN) for display + * 1 TRX = 1,000,000 SUN + */ +void tron_formatAmount(char *buf, size_t len, uint64_t amount) { + bignum256 val; + bn_read_uint64(amount, &val); + bn_format(&val, NULL, " TRX", TRON_DECIMALS, 0, false, buf, len); +} + +/** + * Sign a TRON transaction with secp256k1 + */ +bool tron_signTx(const HDNode *node, const TronSignTx *msg, + TronSignedTx *resp) { + if (!node || !msg || !resp) { + return false; + } + + // Verify we have raw transaction data + if (!msg->has_raw_data || msg->raw_data.size == 0) { + return false; + } + + // Get the curve for secp256k1 + const curve_info *curve = get_curve_by_name(SECP256K1_NAME); + if (!curve) { + return false; + } + + // Hash the transaction with SHA256 + uint8_t hash[32]; + sha256_Raw(msg->raw_data.bytes, msg->raw_data.size, hash); + + // Sign with secp256k1 (recoverable signature: 65 bytes including recovery + // ID) + uint8_t sig[65]; + uint8_t pby; + + if (ecdsa_sign_digest(&secp256k1, node->private_key, hash, sig, &pby, + NULL) != 0) { + memzero(hash, sizeof(hash)); + return false; + } + + // Convert to recoverable signature format (r + s + recovery_id) + // The recovery ID allows recovering the public key from the signature + sig[64] = pby; + + // Copy signature to response (65 bytes) + resp->has_signature = true; + resp->signature.size = 65; + memcpy(resp->signature.bytes, sig, 65); + + // Clean up sensitive data + memzero(hash, sizeof(hash)); + memzero(sig, sizeof(sig)); + + return true; +} diff --git a/lib/transport/CMakeLists.txt b/lib/transport/CMakeLists.txt index 41b8d5864..d42d3e113 100644 --- a/lib/transport/CMakeLists.txt +++ b/lib/transport/CMakeLists.txt @@ -15,6 +15,9 @@ set(protoc_pb_sources ${DEVICE_PROTOCOL}/messages-tendermint.proto ${DEVICE_PROTOCOL}/messages-thorchain.proto ${DEVICE_PROTOCOL}/messages-mayachain.proto + ${DEVICE_PROTOCOL}/messages-solana.proto + ${DEVICE_PROTOCOL}/messages-tron.proto + ${DEVICE_PROTOCOL}/messages-ton.proto ${DEVICE_PROTOCOL}/messages.proto) set(protoc_pb_options @@ -29,6 +32,9 @@ set(protoc_pb_options ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages-tendermint.options ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages-thorchain.options ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages-mayachain.options + ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages-solana.options + ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages-tron.options + ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages-ton.options ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages.options) set(protoc_c_sources @@ -43,6 +49,9 @@ set(protoc_c_sources ${CMAKE_BINARY_DIR}/lib/transport/messages-tendermint.pb.c ${CMAKE_BINARY_DIR}/lib/transport/messages-thorchain.pb.c ${CMAKE_BINARY_DIR}/lib/transport/messages-mayachain.pb.c + ${CMAKE_BINARY_DIR}/lib/transport/messages-solana.pb.c + ${CMAKE_BINARY_DIR}/lib/transport/messages-tron.pb.c + ${CMAKE_BINARY_DIR}/lib/transport/messages-ton.pb.c ${CMAKE_BINARY_DIR}/lib/transport/messages.pb.c) set(protoc_c_headers @@ -57,6 +66,9 @@ set(protoc_c_headers ${CMAKE_BINARY_DIR}/include/messages-tendermint.pb.h ${CMAKE_BINARY_DIR}/include/messages-thorchain.pb.h ${CMAKE_BINARY_DIR}/include/messages-mayachain.pb.h + ${CMAKE_BINARY_DIR}/include/messages-solana.pb.h + ${CMAKE_BINARY_DIR}/include/messages-tron.pb.h + ${CMAKE_BINARY_DIR}/include/messages-ton.pb.h ${CMAKE_BINARY_DIR}/include/messages.pb.h) set(protoc_pb_sources_moved @@ -71,6 +83,9 @@ set(protoc_pb_sources_moved ${CMAKE_BINARY_DIR}/lib/transport/messages-tendermint.proto ${CMAKE_BINARY_DIR}/lib/transport/messages-thorchain.proto ${CMAKE_BINARY_DIR}/lib/transport/messages-mayachain.proto + ${CMAKE_BINARY_DIR}/lib/transport/messages-solana.proto + ${CMAKE_BINARY_DIR}/lib/transport/messages-tron.proto + ${CMAKE_BINARY_DIR}/lib/transport/messages-ton.proto ${CMAKE_BINARY_DIR}/lib/transport/messages.proto) add_custom_command( @@ -136,6 +151,18 @@ add_custom_command( ${PROTOC_BINARY} -I. -I/usr/include --plugin=nanopb=${NANOPB_DIR}/generator/protoc-gen-nanopb "--nanopb_out=-f messages-mayachain.options:." messages-mayachain.proto + COMMAND + ${PROTOC_BINARY} -I. -I/usr/include + --plugin=nanopb=${NANOPB_DIR}/generator/protoc-gen-nanopb + "--nanopb_out=-f messages-solana.options:." messages-solana.proto + COMMAND + ${PROTOC_BINARY} -I. -I/usr/include + --plugin=nanopb=${NANOPB_DIR}/generator/protoc-gen-nanopb + "--nanopb_out=-f messages-tron.options:." messages-tron.proto + COMMAND + ${PROTOC_BINARY} -I. -I/usr/include + --plugin=nanopb=${NANOPB_DIR}/generator/protoc-gen-nanopb + "--nanopb_out=-f messages-ton.options:." messages-ton.proto COMMAND ${PROTOC_BINARY} -I. -I/usr/include --plugin=nanopb=${NANOPB_DIR}/generator/protoc-gen-nanopb diff --git a/lib/transport/pb_decode.c b/lib/transport/pb_decode.c index 5ff4ecd7e..cff906307 100644 --- a/lib/transport/pb_decode.c +++ b/lib/transport/pb_decode.c @@ -452,14 +452,18 @@ static bool checkreturn decode_static_field(pb_istream_t *stream, } case PB_HTYPE_ONEOF: - *(pb_size_t *)iter->pSize = iter->pos->tag; - if (PB_LTYPE(type) == PB_LTYPE_SUBMESSAGE) { + if (PB_LTYPE(type) == PB_LTYPE_SUBMESSAGE && + *(pb_size_t*)iter->pSize != iter->pos->tag) + { /* We memset to zero so that any callbacks are set to NULL. - * Then set any default values. */ + * This is because the callbacks might otherwise have values + * from some other union field. */ memset(iter->pData, 0, iter->pos->data_size); pb_message_set_to_defaults((const pb_field_t *)iter->pos->ptr, iter->pData); } + *(pb_size_t*)iter->pSize = iter->pos->tag; + return func(stream, iter->pos, iter->pData); default: diff --git a/lib/variant/CMakeLists.txt b/lib/variant/CMakeLists.txt index 0728f3f28..99ff2119c 100644 --- a/lib/variant/CMakeLists.txt +++ b/lib/variant/CMakeLists.txt @@ -3,9 +3,15 @@ include_directories( ${CMAKE_BINARY_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}) -add_library(kkvariant.keepkey - ${CMAKE_CURRENT_SOURCE_DIR}/keepkey/keepkey.c - ${CMAKE_CURRENT_SOURCE_DIR}/keepkey/logo.c) +if("${COIN_SUPPORT}" STREQUAL "BTC") + add_library(kkvariant.keepkey + ${CMAKE_CURRENT_SOURCE_DIR}/keepkey/keepkey.c + ${CMAKE_CURRENT_SOURCE_DIR}/keepkey/logobtc.c) +else() + add_library(kkvariant.keepkey + ${CMAKE_CURRENT_SOURCE_DIR}/keepkey/keepkey.c + ${CMAKE_CURRENT_SOURCE_DIR}/keepkey/logo.c) +endif() add_library(kkvariant.salt ${CMAKE_CURRENT_SOURCE_DIR}/salt/salt.c diff --git a/lib/variant/keepkey/logobtc.c b/lib/variant/keepkey/logobtc.c new file mode 100644 index 000000000..478c9c7bb --- /dev/null +++ b/lib/variant/keepkey/logobtc.c @@ -0,0 +1,460 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2025 markrypto + * + * 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 . + */ + +#include "keepkey/board/resources.h" + +const uint8_t kkbtc_logo_data[1798] = +{ + 0x16, 0x0, 0xff, 0x43, 0x2, 0xff, 0x7, 0x0, 0x2, 0xff, 0x3, 0x0, 0xff, 0x89, 0x14, 0x0, 0x2, 0xff, 0x1f, 0x0, 0xff, 0x9d, 0x2, 0xff, 0x7, 0x0, 0xfe, 0xff, 0xf0, 0x2, 0x0, 0x2, 0xff, 0x14, 0x0, 0x2, 0xff, 0x1f, 0x0, 0x2, 0xff, 0xfd, 0xef, 0x4f, 0x8c, 0x8, 0x0, 0xff, 0x78, 0x2, 0xff, 0xff, 0x4f, 0x3, 0x0, 0xfd, 0xb4, 0xe4, 0x9d, 0x5, 0x0, 0xfd, 0xb4, 0xe4, 0xb4, 0xc, 0x0, 0xfe, 0xc4, 0xb8, 0x18, 0x0, 0x6, 0xff, 0xff, 0xb8, 0x2, 0x0, 0x2, 0xff, 0xfe, 0x4f, 0x72, 0x4, 0xff, 0xfe, 0x0, 0x78, 0x5, 0xff, 0x2, 0x0, 0xff, 0x92, 0x5, 0xff, 0xff, 0x57, 0x2, 0x0, 0x2, 0xff, 0xfd, 0xb8, 0x0, 0xe4, 0x6, 0xff, 0xfe, 0x0, 0xcf, 0x3, 0xff, 0xff, 0x19, 0x11, 0x0, 0x2, 0xff, 0xfd, 0xf9, 0x0, 0xcf, 0x2, 0xff, 0x2, 0x0, 0x2, 0xff, 0x2, 0x0, 0x2, 0xff, 0xff, 0xf9, 0x2, 0x0, 0x2, 0xff, 0xfe, 0xbe, 0x4f, 0x2, 0xff, 0xfe, 0x72, 0x0, 0x2, 0xff, 0xfd, 0xdb, 0x0, 0xe4, 0x2, 0xff, 0x2, 0x0, 0x2, 0xff, 0x2, 0x0, 0x3, 0xff, 0xfe, 0x78, 0xb8, 0x2, 0xff, 0xff, 0x0, 0x5, 0xff, 0x10, 0x0, 0xff, 0x3c, 0x2, 0xff, 0x3, 0x0, 0x2, 0xff, 0x2, 0x0, 0x2, 0xff, 0x2, 0x0, 0x2, 0xff, 0x2, 0x0, 0xff, 0xef, 0x2, 0xff, 0x5, 0x0, 0xff, 0xe4, 0x2, 0xff, 0x3, 0x0, 0x2, 0xff, 0x2, 0x0, 0x2, 0xff, 0x2, 0x0, 0x2, 0xff, 0xff, 0x48, 0x2, 0x0, 0x2, 0xff, 0xff, 0x0, 0x5, 0xff, 0xff, 0x43, 0xf, 0x0, 0xff, 0xcf, 0x2, 0xff, 0x3, 0x0, 0x2, 0xff, 0xfe, 0x0, 0x6c, 0x2, 0xff, 0x2, 0x0, 0x2, 0xff, 0x2, 0x0, 0x2, 0xff, 0xff, 0x72, 0x5, 0x0, 0x2, 0xff, 0xff, 0xa9, 0x3, 0x0, 0x2, 0xff, 0x2, 0x0, 0x2, 0xff, 0x2, 0x0, 0x2, 0xff, 0x2, 0x0, 0xff, 0x78, 0x2, 0xff, 0xfe, 0x0, 0xb8, 0x4, 0xff, 0xff, 0x57, 0xf, 0x0, 0x2, 0xff, 0xff, 0xf9, 0x2, 0x0, 0xff, 0x5e, 0x2, 0xff, 0xfc, 0x0, 0xe4, 0xff, 0xef, 0x2, 0x0, 0x2, 0xff, 0x2, 0x0, 0x2, 0xff, 0x6, 0x0, 0x2, 0xff, 0xff, 0xa9, 0x2, 0x0, 0xff, 0xa9, 0x2, 0xff, 0xfa, 0x0, 0xae, 0xff, 0xf9, 0x0, 0x72, 0x2, 0xff, 0x2, 0x0, 0xfd, 0xef, 0xff, 0xef, 0x2, 0x0, 0xff, 0x43, 0x3, 0xff, 0xff, 0x4f, 0xf, 0x0, 0x3, 0xff, 0x2, 0x0, 0x2, 0xff, 0xfe, 0x72, 0x0, 0x2, 0xff, 0xfd, 0x78, 0x0, 0xa9, 0x2, 0xff, 0x2, 0x0, 0x2, 0xff, 0xfe, 0xd8, 0x0, 0x2, 0xff, 0xfd, 0xf0, 0x0, 0xd8, 0x2, 0xff, 0xfe, 0x0, 0x3c, 0x2, 0xff, 0xfe, 0x72, 0x0, 0x2, 0xff, 0xfb, 0xa9, 0x0, 0xef, 0xff, 0xd8, 0x2, 0x0, 0x2, 0xff, 0xff, 0x96, 0x3, 0x0, 0x3, 0xff, 0x10, 0x0, 0x6, 0xff, 0xff, 0xcf, 0x2, 0x0, 0x2, 0xff, 0x2, 0x0, 0xff, 0x9d, 0x3, 0xff, 0x2, 0x0, 0x5, 0xff, 0x3, 0x0, 0x5, 0xff, 0xff, 0xd8, 0x2, 0x0, 0x2, 0xff, 0x2, 0x0, 0x2, 0xff, 0xff, 0x4f, 0x2, 0x0, 0x2, 0xff, 0x3, 0x0, 0xff, 0x19, 0x3, 0xff, 0x10, 0x0, 0xfb, 0xcf, 0x78, 0x0, 0x72, 0xa9, 0x4, 0x0, 0xfe, 0xbe, 0x9d, 0x3, 0x0, 0xfd, 0x96, 0xd4, 0x4f, 0x3, 0x0, 0xfd, 0x78, 0xa9, 0x57, 0x5, 0x0, 0xff, 0x6c, 0x2, 0xbe, 0x4, 0x0, 0xfe, 0x78, 0xa9, 0x2, 0x0, 0xfe, 0x78, 0xa9, 0x3, 0x0, 0xfe, 0xbe, 0x9d, 0x3, 0x0, 0xff, 0x57, 0x3, 0xff, 0x41, 0x0, 0xff, 0x57, 0x3, 0xff, 0x8, 0x0, 0xff, 0x29, 0x2, 0x57, 0x9, 0x0, 0x2, 0xc, 0x10, 0x0, 0xfe, 0x19, 0x36, 0x8, 0x0, 0xfe, 0x57, 0xa9, 0x7, 0x0, 0xfe, 0xa9, 0x5e, 0x6, 0x0, 0xff, 0x5e, 0x3, 0xff, 0x7, 0x0, 0x4, 0xff, 0xff, 0x8c, 0x5, 0x0, 0x8, 0xff, 0xff, 0x4f, 0x8, 0x0, 0xff, 0xa9, 0x7, 0xff, 0xff, 0xdb, 0x4, 0x0, 0xff, 0x57, 0x3, 0xff, 0xff, 0xbe, 0x2, 0x0, 0x7, 0xff, 0xff, 0x5e, 0x3, 0x0, 0xff, 0x5e, 0x3, 0xff, 0x6, 0x0, 0xff, 0xdb, 0x4, 0xff, 0xff, 0x96, 0x3, 0x0, 0xff, 0x72, 0xa, 0xff, 0xff, 0xca, 0x6, 0x0, 0xb, 0xff, 0x4, 0x0, 0x4, 0xff, 0xff, 0xa9, 0x9, 0xff, 0xff, 0xa9, 0x2, 0x0, 0xff, 0x5e, 0x3, 0xff, 0x5, 0x0, 0xff, 0x78, 0x5, 0xff, 0x4, 0x0, 0xc, 0xff, 0x5, 0x0, 0xff, 0xf9, 0xb, 0xff, 0xff, 0xf9, 0x3, 0x0, 0xf, 0xff, 0x2, 0x0, 0xff, 0x57, 0x3, 0xff, 0x4, 0x0, 0xff, 0x5e, 0x4, 0xff, 0x5, 0x0, 0x4, 0xff, 0xff, 0xf0, 0x5, 0x0, 0x4, 0xff, 0x4, 0x0, 0x4, 0xff, 0xff, 0x5e, 0x4, 0x0, 0xff, 0xe4, 0x3, 0xff, 0x4, 0x0, 0x5, 0xff, 0x5, 0x0, 0x4, 0xff, 0xfd, 0x96, 0x0, 0x57, 0x3, 0xff, 0x4, 0x0, 0x4, 0xff, 0xff, 0x43, 0x5, 0x0, 0x3, 0xff, 0xff, 0xf0, 0x7, 0x0, 0x3, 0xff, 0x3, 0x0, 0x4, 0xff, 0x7, 0x0, 0x3, 0xff, 0xff, 0xb4, 0x3, 0x0, 0x4, 0xff, 0x7, 0x0, 0x3, 0xff, 0xfd, 0xef, 0x0, 0x48, 0x3, 0xff, 0xff, 0x81, 0x2, 0x0, 0x4, 0xff, 0xff, 0xa9, 0x5, 0x0, 0xff, 0xa9, 0x3, 0xff, 0x8, 0x0, 0x3, 0xff, 0x3, 0x0, 0x3, 0xff, 0xff, 0x81, 0x7, 0x0, 0xff, 0xa9, 0x3, 0xff, 0x3, 0x0, 0x3, 0xff, 0xff, 0xe4, 0x7, 0x0, 0x3, 0xff, 0xfd, 0xf9, 0x0, 0x1b, 0x9, 0xff, 0xff, 0x8c, 0x6, 0x0, 0xff, 0xef, 0x3, 0xff, 0x8, 0x0, 0x3, 0xff, 0x3, 0x0, 0x3, 0xff, 0xff, 0x81, 0x7, 0x0, 0xff, 0xe4, 0x3, 0xff, 0x3, 0x0, 0x3, 0xff, 0xff, 0xd4, 0x7, 0x0, 0x3, 0xff, 0xfd, 0xf9, 0x0, 0x1b, 0x8, 0xff, 0xff, 0xf0, 0x7, 0x0, 0xf, 0xff, 0xff, 0x72, 0x2, 0x0, 0xf, 0xff, 0x3, 0x0, 0x3, 0xff, 0xff, 0xd4, 0x7, 0x0, 0x3, 0xff, 0xfd, 0xf9, 0x0, 0x1b, 0x9, 0xff, 0x7, 0x0, 0xf, 0xff, 0xff, 0x5e, 0x2, 0x0, 0xf, 0xff, 0x3, 0x0, 0x3, 0xff, 0xff, 0xd4, 0x7, 0x0, 0x3, 0xff, 0xfd, 0xf9, 0x0, 0x1b, 0x4, 0xff, 0xfe, 0xd4, 0xe4, 0x4, 0xff, 0x6, 0x0, 0x4, 0xff, 0x4, 0x0, 0xfe, 0x30, 0x48, 0x4, 0x5e, 0xff, 0x43, 0x3, 0x0, 0x3, 0xff, 0xff, 0x81, 0x3, 0x0, 0xfe, 0x29, 0x43, 0x4, 0x5e, 0xff, 0x72, 0x4, 0x0, 0x3, 0xff, 0xff, 0xd4, 0x7, 0x0, 0x3, 0xff, 0xfd, 0xf9, 0x0, 0x57, 0x3, 0xff, 0x3, 0x0, 0xff, 0x9d, 0x3, 0xff, 0xff, 0xf9, 0x5, 0x0, 0xff, 0xca, 0x3, 0xff, 0xe, 0x0, 0x3, 0xff, 0xff, 0x43, 0xe, 0x0, 0x3, 0xff, 0xff, 0xd8, 0x7, 0x0, 0x4, 0xff, 0xfe, 0x0, 0x57, 0x3, 0xff, 0x4, 0x0, 0x4, 0xff, 0xff, 0xa9, 0x4, 0x0, 0xff, 0x36, 0x3, 0xff, 0xff, 0xa9, 0xd, 0x0, 0x4, 0xff, 0xe, 0x0, 0x4, 0xff, 0x7, 0x0, 0x3, 0xff, 0xfd, 0xef, 0x0, 0x5e, 0x3, 0xff, 0x5, 0x0, 0x4, 0xff, 0x5, 0x0, 0x4, 0xff, 0xff, 0xa9, 0x5, 0x0, 0xff, 0x72, 0x2, 0xff, 0x4, 0x0, 0xff, 0xcf, 0x4, 0xff, 0x6, 0x0, 0x2, 0xff, 0x5, 0x0, 0x4, 0xff, 0xff, 0xf0, 0x5, 0x0, 0xff, 0xf0, 0x3, 0xff, 0xfd, 0x89, 0x0, 0x5e, 0x3, 0xff, 0x6, 0x0, 0x5, 0xff, 0x3, 0x0, 0xff, 0xae, 0xc, 0xff, 0xff, 0x9d, 0x4, 0x0, 0xd, 0xff, 0x4, 0x0, 0xe, 0xff, 0x2, 0x0, 0xff, 0x5e, 0x3, 0xff, 0x6, 0x0, 0xff, 0x9d, 0x4, 0xff, 0xff, 0x96, 0x3, 0x0, 0xc, 0xff, 0xff, 0xc4, 0x4, 0x0, 0xff, 0x78, 0xc, 0xff, 0x4, 0x0, 0xd, 0xff, 0xff, 0xae, 0x2, 0x0, 0xff, 0x5e, 0x3, 0xff, 0x7, 0x0, 0x4, 0xff, 0xff, 0x96, 0x4, 0x0, 0xff, 0xef, 0x8, 0xff, 0xff, 0xd4, 0x7, 0x0, 0xff, 0x57, 0x9, 0xff, 0xff, 0x57, 0x5, 0x0, 0x3, 0xff, 0xfe, 0xc4, 0x0, 0x7, 0xff, 0xff, 0x78, 0x3, 0x0, 0xff, 0x72, 0x3, 0xff, 0x8, 0x0, 0xff, 0x89, 0x2, 0xa9, 0x7, 0x0, 0xfb, 0x8c, 0xf9, 0xff, 0xef, 0x96, 0xc, 0x0, 0xfe, 0x3c, 0xe4, 0x2, 0xff, 0xfe, 0xca, 0x4f, 0x8, 0x0, 0x3, 0xff, 0xff, 0xbe, 0x3, 0x0, 0xfc, 0xa9, 0xff, 0xca, 0x4f, 0x5, 0x0, 0xff, 0x72, 0x3, 0xff, 0x31, 0x0, 0x3, 0xff, 0xff, 0xcf, 0xc, 0x0, 0xff, 0x72, 0x3, 0xff, 0x31, 0x0, 0x3, 0xff, 0xff, 0xd4, 0xc, 0x0, 0xff, 0x72, 0x3, 0xff, 0x31, 0x0, 0x3, 0xff, 0xff, 0xe4, 0xc, 0x0, 0xff, 0x72, 0x3, 0xff, 0x31, 0x0, 0x3, 0xff, 0xff, 0xef, 0xc, 0x0, 0xff, 0x5e, 0x3, 0xff, 0x8, 0x0, 0x2, 0xd8, 0xff, 0xe4, 0x8, 0x0, 0xfb, 0x29, 0xa9, 0xd8, 0xbe, 0x3c, 0x6, 0x0, 0xff, 0x89, 0x3, 0x96, 0xff, 0x8c, 0x6, 0x0, 0xff, 0x89, 0x3, 0x96, 0xff, 0x72, 0x3, 0x0, 0x4, 0xff, 0xc, 0x0, 0xff, 0x5e, 0x3, 0xff, 0x7, 0x0, 0x4, 0xff, 0xff, 0xb4, 0x5, 0x0, 0x8, 0xff, 0xff, 0xd8, 0x4, 0x0, 0x5, 0xff, 0x6, 0x0, 0x5, 0xff, 0x3, 0x0, 0x4, 0xff, 0xc, 0x0, 0xff, 0x5e, 0x3, 0xff, 0x6, 0x0, 0x5, 0xff, 0xff, 0x96, 0x4, 0x0, 0xb, 0xff, 0x3, 0x0, 0x5, 0xff, 0x6, 0x0, 0x5, 0xff, 0x3, 0x0, 0x4, 0xff, 0xc, 0x0, 0xff, 0x5e, 0x3, 0xff, 0x5, 0x0, 0xff, 0xef, 0x4, 0xff, 0xff, 0xd4, 0x4, 0x0, 0xc, 0xff, 0xff, 0xd4, 0x2, 0x0, 0xff, 0xa9, 0x4, 0xff, 0x6, 0x0, 0xff, 0xc4, 0x4, 0xff, 0x3, 0x0, 0xfc, 0x9d, 0xf0, 0xef, 0x81, 0xc, 0x0, 0xff, 0x57, 0x3, 0xff, 0x4, 0x0, 0xff, 0xb8, 0x4, 0xff, 0x5, 0x0, 0xff, 0xc4, 0x4, 0xff, 0x5, 0x0, 0xff, 0xf9, 0x3, 0xff, 0x3, 0x0, 0xff, 0xb8, 0x3, 0xff, 0x7, 0x0, 0xff, 0xca, 0x3, 0xff, 0x13, 0x0, 0xff, 0x57, 0x3, 0xff, 0x4, 0x0, 0x4, 0xff, 0x6, 0x0, 0x4, 0xff, 0x7, 0x0, 0x3, 0xff, 0x3, 0x0, 0xff, 0xb8, 0x3, 0xff, 0x7, 0x0, 0xff, 0xc4, 0x3, 0xff, 0x13, 0x0, 0xff, 0x48, 0x3, 0xff, 0xfd, 0xf0, 0xca, 0xe4, 0x4, 0xff, 0x7, 0x0, 0x3, 0xff, 0x8, 0x0, 0xff, 0xf0, 0x2, 0xff, 0xff, 0xf9, 0x2, 0x0, 0xff, 0xb8, 0x3, 0xff, 0x7, 0x0, 0xff, 0xc4, 0x3, 0xff, 0x13, 0x0, 0xff, 0x1b, 0x9, 0xff, 0x7, 0x0, 0xff, 0x72, 0x3, 0xff, 0xff, 0x43, 0x7, 0x0, 0x4, 0xff, 0x2, 0x0, 0xff, 0xb8, 0x3, 0xff, 0x7, 0x0, 0xff, 0xc4, 0x3, 0xff, 0x13, 0x0, 0xff, 0x1b, 0x8, 0xff, 0xff, 0xf0, 0x7, 0x0, 0xff, 0xae, 0xf, 0xff, 0x2, 0x0, 0xff, 0xb8, 0x3, 0xff, 0x7, 0x0, 0xff, 0xc4, 0x3, 0xff, 0x13, 0x0, 0xff, 0x1b, 0x9, 0xff, 0x7, 0x0, 0xff, 0xae, 0xe, 0xff, 0xff, 0xf9, 0x2, 0x0, 0xff, 0xb8, 0x3, 0xff, 0x7, 0x0, 0xff, 0xc4, 0x3, 0xff, 0x13, 0x0, 0xff, 0x57, 0x3, 0xff, 0xff, 0x9d, 0x2, 0x0, 0x4, 0xff, 0x6, 0x0, 0xff, 0xa9, 0x3, 0xff, 0xe, 0x0, 0xff, 0xb8, 0x3, 0xff, 0x7, 0x0, 0xff, 0xb4, 0x3, 0xff, 0x13, 0x0, 0xff, 0x57, 0x3, 0xff, 0x4, 0x0, 0x4, 0xff, 0x6, 0x0, 0x3, 0xff, 0xe, 0x0, 0xff, 0xc4, 0x3, 0xff, 0x7, 0x0, 0xff, 0xb8, 0x3, 0xff, 0x13, 0x0, 0xff, 0x72, 0x3, 0xff, 0x4, 0x0, 0xff, 0xa9, 0x4, 0xff, 0x5, 0x0, 0x4, 0xff, 0xd, 0x0, 0xff, 0x78, 0x3, 0xff, 0xff, 0xa9, 0x6, 0x0, 0x4, 0xff, 0x13, 0x0, 0xff, 0x72, 0x3, 0xff, 0x5, 0x0, 0x4, 0xff, 0xff, 0xb8, 0x4, 0x0, 0xff, 0xf9, 0x4, 0xff, 0x5, 0x0, 0xff, 0x72, 0x2, 0xff, 0x5, 0x0, 0x4, 0xff, 0xff, 0xb8, 0x3, 0x0, 0xff, 0x6c, 0x5, 0xff, 0x13, 0x0, 0xff, 0x92, 0x3, 0xff, 0x6, 0x0, 0x5, 0xff, 0xff, 0x57, 0x3, 0x0, 0xd, 0xff, 0x4, 0x0, 0xe, 0xff, 0x13, 0x0, 0xff, 0xa9, 0x3, 0xff, 0xff, 0x5e, 0x6, 0x0, 0x4, 0xff, 0xff, 0x96, 0x3, 0x0, 0xff, 0xa9, 0xb, 0xff, 0xff, 0xe4, 0x5, 0x0, 0x9, 0xff, 0xff, 0xe4, 0x3, 0xff, 0x13, 0x0, 0xff, 0xa9, 0x3, 0xff, 0xff, 0x81, 0x6, 0x0, 0xff, 0xc4, 0x3, 0xff, 0xff, 0xa9, 0x4, 0x0, 0xff, 0x81, 0x8, 0xff, 0xff, 0xef, 0x8, 0x0, 0xff, 0xe4, 0x5, 0xff, 0xfd, 0x92, 0x0, 0x9d, 0x3, 0xff, 0x14, 0x0, 0xff, 0x29, 0x14, 0x0, 0xfb, 0x57, 0x9d, 0xd4, 0xc4, 0x96, 0x13, 0x0, 0xff, 0x9d, 0x3, 0xff, 0x41, 0x0, 0xff, 0xb4, 0x3, 0xff, 0x41, 0x0, 0xff, 0xa9, 0x3, 0xff, 0x41, 0x0, 0xff, 0xa9, 0x3, 0xff, 0x41, 0x0, 0xff, 0xca, 0x3, 0xff, 0x40, 0x0, 0xff, 0xef, 0x4, 0xff, 0x40, 0x0, 0x5, 0xff, 0x40, 0x0, 0x5, 0xff, 0x40, 0x0, 0xff, 0xb8, 0x2, 0xff, 0xff, 0xc4, 0x13, 0x0 +}; +static const Image kkbtc_logo_image = {69, 60, 1798, kkbtc_logo_data}; + +const VariantAnimation kkbtc_logo = { + 21, + { + {94, 2, 25, 0, &kkbtc_logo_image}, + {94, 2, 25, 5, &kkbtc_logo_image}, + {94, 2, 25, 10, &kkbtc_logo_image}, + {94, 2, 25, 15, &kkbtc_logo_image}, + {94, 2, 25, 20, &kkbtc_logo_image}, + {94, 2, 25, 25, &kkbtc_logo_image}, + {94, 2, 25, 30, &kkbtc_logo_image}, + {94, 2, 25, 35, &kkbtc_logo_image}, + {94, 2, 25, 40, &kkbtc_logo_image}, + {94, 2, 25, 45, &kkbtc_logo_image}, + {94, 2, 25, 50, &kkbtc_logo_image}, + {94, 2, 25, 55, &kkbtc_logo_image}, + {94, 2, 25, 60, &kkbtc_logo_image}, + {94, 2, 25, 65, &kkbtc_logo_image}, + {94, 2, 25, 70, &kkbtc_logo_image}, + {94, 2, 25, 75, &kkbtc_logo_image}, + {94, 2, 25, 80, &kkbtc_logo_image}, + {94, 2, 25, 85, &kkbtc_logo_image}, + {94, 2, 25, 90, &kkbtc_logo_image}, + {94, 2, 25, 95, &kkbtc_logo_image}, + {94, 2, 25, 100, &kkbtc_logo_image}, + } +}; +const VariantAnimation kkbtc_logo_reversed = { + 21, + { + {94, 2, 25, 100, &kkbtc_logo_image}, + {94, 2, 25, 95, &kkbtc_logo_image}, + {94, 2, 25, 90, &kkbtc_logo_image}, + {94, 2, 25, 85, &kkbtc_logo_image}, + {94, 2, 25, 80, &kkbtc_logo_image}, + {94, 2, 25, 75, &kkbtc_logo_image}, + {94, 2, 25, 70, &kkbtc_logo_image}, + {94, 2, 25, 65, &kkbtc_logo_image}, + {94, 2, 25, 60, &kkbtc_logo_image}, + {94, 2, 25, 55, &kkbtc_logo_image}, + {94, 2, 25, 50, &kkbtc_logo_image}, + {94, 2, 25, 45, &kkbtc_logo_image}, + {94, 2, 25, 40, &kkbtc_logo_image}, + {94, 2, 25, 35, &kkbtc_logo_image}, + {94, 2, 25, 30, &kkbtc_logo_image}, + {94, 2, 25, 25, &kkbtc_logo_image}, + {94, 2, 25, 20, &kkbtc_logo_image}, + {94, 2, 25, 15, &kkbtc_logo_image}, + {94, 2, 25, 10, &kkbtc_logo_image}, + {94, 2, 25, 5, &kkbtc_logo_image}, + {94, 2, 25, 0, &kkbtc_logo_image}, + } +}; + +const VariantAnimation kkbtc_screensaver = { + 374, + { + {94, 2, 75, 60, &kkbtc_logo_image}, + {95, 2, 75, 60, &kkbtc_logo_image}, + {96, 2, 75, 60, &kkbtc_logo_image}, + {97, 2, 75, 60, &kkbtc_logo_image}, + {98, 2, 75, 60, &kkbtc_logo_image}, + {99, 2, 75, 60, &kkbtc_logo_image}, + {100, 2, 75, 60, &kkbtc_logo_image}, + {101, 2, 75, 60, &kkbtc_logo_image}, + {102, 2, 75, 60, &kkbtc_logo_image}, + {103, 2, 75, 60, &kkbtc_logo_image}, + {104, 2, 75, 60, &kkbtc_logo_image}, + {105, 2, 75, 60, &kkbtc_logo_image}, + {106, 2, 75, 60, &kkbtc_logo_image}, + {107, 2, 75, 60, &kkbtc_logo_image}, + {108, 2, 75, 60, &kkbtc_logo_image}, + {109, 2, 75, 60, &kkbtc_logo_image}, + {110, 2, 75, 60, &kkbtc_logo_image}, + {111, 2, 75, 60, &kkbtc_logo_image}, + {112, 2, 75, 60, &kkbtc_logo_image}, + {113, 2, 75, 60, &kkbtc_logo_image}, + {114, 2, 75, 60, &kkbtc_logo_image}, + {115, 2, 75, 60, &kkbtc_logo_image}, + {116, 2, 75, 60, &kkbtc_logo_image}, + {117, 2, 75, 60, &kkbtc_logo_image}, + {118, 2, 75, 60, &kkbtc_logo_image}, + {119, 2, 75, 60, &kkbtc_logo_image}, + {120, 2, 75, 60, &kkbtc_logo_image}, + {121, 2, 75, 60, &kkbtc_logo_image}, + {122, 2, 75, 60, &kkbtc_logo_image}, + {123, 2, 75, 60, &kkbtc_logo_image}, + {124, 2, 75, 60, &kkbtc_logo_image}, + {125, 2, 75, 60, &kkbtc_logo_image}, + {126, 2, 75, 60, &kkbtc_logo_image}, + {127, 2, 75, 60, &kkbtc_logo_image}, + {128, 2, 75, 60, &kkbtc_logo_image}, + {129, 2, 75, 60, &kkbtc_logo_image}, + {130, 2, 75, 60, &kkbtc_logo_image}, + {131, 2, 75, 60, &kkbtc_logo_image}, + {132, 2, 75, 60, &kkbtc_logo_image}, + {133, 2, 75, 60, &kkbtc_logo_image}, + {134, 2, 75, 60, &kkbtc_logo_image}, + {135, 2, 75, 60, &kkbtc_logo_image}, + {136, 2, 75, 60, &kkbtc_logo_image}, + {137, 2, 75, 60, &kkbtc_logo_image}, + {138, 2, 75, 60, &kkbtc_logo_image}, + {139, 2, 75, 60, &kkbtc_logo_image}, + {140, 2, 75, 60, &kkbtc_logo_image}, + {141, 2, 75, 60, &kkbtc_logo_image}, + {142, 2, 75, 60, &kkbtc_logo_image}, + {143, 2, 75, 60, &kkbtc_logo_image}, + {144, 2, 75, 60, &kkbtc_logo_image}, + {145, 2, 75, 60, &kkbtc_logo_image}, + {146, 2, 75, 60, &kkbtc_logo_image}, + {147, 2, 75, 60, &kkbtc_logo_image}, + {148, 2, 75, 60, &kkbtc_logo_image}, + {149, 2, 75, 60, &kkbtc_logo_image}, + {150, 2, 75, 60, &kkbtc_logo_image}, + {151, 2, 75, 60, &kkbtc_logo_image}, + {152, 2, 75, 60, &kkbtc_logo_image}, + {153, 2, 75, 60, &kkbtc_logo_image}, + {154, 2, 75, 60, &kkbtc_logo_image}, + {155, 2, 75, 60, &kkbtc_logo_image}, + {156, 2, 75, 60, &kkbtc_logo_image}, + {157, 2, 75, 60, &kkbtc_logo_image}, + {158, 2, 75, 60, &kkbtc_logo_image}, + {159, 2, 75, 60, &kkbtc_logo_image}, + {160, 2, 75, 60, &kkbtc_logo_image}, + {161, 2, 75, 60, &kkbtc_logo_image}, + {162, 2, 75, 60, &kkbtc_logo_image}, + {163, 2, 75, 60, &kkbtc_logo_image}, + {164, 2, 75, 60, &kkbtc_logo_image}, + {165, 2, 75, 60, &kkbtc_logo_image}, + {166, 2, 75, 60, &kkbtc_logo_image}, + {167, 2, 75, 60, &kkbtc_logo_image}, + {168, 2, 75, 60, &kkbtc_logo_image}, + {169, 2, 75, 60, &kkbtc_logo_image}, + {170, 2, 75, 60, &kkbtc_logo_image}, + {171, 2, 75, 60, &kkbtc_logo_image}, + {172, 2, 75, 60, &kkbtc_logo_image}, + {173, 2, 75, 60, &kkbtc_logo_image}, + {174, 2, 75, 60, &kkbtc_logo_image}, + {175, 2, 75, 60, &kkbtc_logo_image}, + {176, 2, 75, 60, &kkbtc_logo_image}, + {177, 2, 75, 60, &kkbtc_logo_image}, + {178, 2, 75, 60, &kkbtc_logo_image}, + {179, 2, 75, 60, &kkbtc_logo_image}, + {180, 2, 75, 60, &kkbtc_logo_image}, + {181, 2, 75, 60, &kkbtc_logo_image}, + {182, 2, 75, 60, &kkbtc_logo_image}, + {183, 2, 75, 60, &kkbtc_logo_image}, + {184, 2, 75, 60, &kkbtc_logo_image}, + {185, 2, 75, 60, &kkbtc_logo_image}, + {186, 2, 75, 60, &kkbtc_logo_image}, + {185, 2, 75, 60, &kkbtc_logo_image}, + {184, 2, 75, 60, &kkbtc_logo_image}, + {183, 2, 75, 60, &kkbtc_logo_image}, + {182, 2, 75, 60, &kkbtc_logo_image}, + {181, 2, 75, 60, &kkbtc_logo_image}, + {180, 2, 75, 60, &kkbtc_logo_image}, + {179, 2, 75, 60, &kkbtc_logo_image}, + {178, 2, 75, 60, &kkbtc_logo_image}, + {177, 2, 75, 60, &kkbtc_logo_image}, + {176, 2, 75, 60, &kkbtc_logo_image}, + {175, 2, 75, 60, &kkbtc_logo_image}, + {174, 2, 75, 60, &kkbtc_logo_image}, + {173, 2, 75, 60, &kkbtc_logo_image}, + {172, 2, 75, 60, &kkbtc_logo_image}, + {171, 2, 75, 60, &kkbtc_logo_image}, + {170, 2, 75, 60, &kkbtc_logo_image}, + {169, 2, 75, 60, &kkbtc_logo_image}, + {168, 2, 75, 60, &kkbtc_logo_image}, + {167, 2, 75, 60, &kkbtc_logo_image}, + {166, 2, 75, 60, &kkbtc_logo_image}, + {165, 2, 75, 60, &kkbtc_logo_image}, + {164, 2, 75, 60, &kkbtc_logo_image}, + {163, 2, 75, 60, &kkbtc_logo_image}, + {162, 2, 75, 60, &kkbtc_logo_image}, + {161, 2, 75, 60, &kkbtc_logo_image}, + {160, 2, 75, 60, &kkbtc_logo_image}, + {159, 2, 75, 60, &kkbtc_logo_image}, + {158, 2, 75, 60, &kkbtc_logo_image}, + {157, 2, 75, 60, &kkbtc_logo_image}, + {156, 2, 75, 60, &kkbtc_logo_image}, + {155, 2, 75, 60, &kkbtc_logo_image}, + {154, 2, 75, 60, &kkbtc_logo_image}, + {153, 2, 75, 60, &kkbtc_logo_image}, + {152, 2, 75, 60, &kkbtc_logo_image}, + {151, 2, 75, 60, &kkbtc_logo_image}, + {150, 2, 75, 60, &kkbtc_logo_image}, + {149, 2, 75, 60, &kkbtc_logo_image}, + {148, 2, 75, 60, &kkbtc_logo_image}, + {147, 2, 75, 60, &kkbtc_logo_image}, + {146, 2, 75, 60, &kkbtc_logo_image}, + {145, 2, 75, 60, &kkbtc_logo_image}, + {144, 2, 75, 60, &kkbtc_logo_image}, + {143, 2, 75, 60, &kkbtc_logo_image}, + {142, 2, 75, 60, &kkbtc_logo_image}, + {141, 2, 75, 60, &kkbtc_logo_image}, + {140, 2, 75, 60, &kkbtc_logo_image}, + {139, 2, 75, 60, &kkbtc_logo_image}, + {138, 2, 75, 60, &kkbtc_logo_image}, + {137, 2, 75, 60, &kkbtc_logo_image}, + {136, 2, 75, 60, &kkbtc_logo_image}, + {135, 2, 75, 60, &kkbtc_logo_image}, + {134, 2, 75, 60, &kkbtc_logo_image}, + {133, 2, 75, 60, &kkbtc_logo_image}, + {132, 2, 75, 60, &kkbtc_logo_image}, + {131, 2, 75, 60, &kkbtc_logo_image}, + {130, 2, 75, 60, &kkbtc_logo_image}, + {129, 2, 75, 60, &kkbtc_logo_image}, + {128, 2, 75, 60, &kkbtc_logo_image}, + {127, 2, 75, 60, &kkbtc_logo_image}, + {126, 2, 75, 60, &kkbtc_logo_image}, + {125, 2, 75, 60, &kkbtc_logo_image}, + {124, 2, 75, 60, &kkbtc_logo_image}, + {123, 2, 75, 60, &kkbtc_logo_image}, + {122, 2, 75, 60, &kkbtc_logo_image}, + {121, 2, 75, 60, &kkbtc_logo_image}, + {120, 2, 75, 60, &kkbtc_logo_image}, + {119, 2, 75, 60, &kkbtc_logo_image}, + {118, 2, 75, 60, &kkbtc_logo_image}, + {117, 2, 75, 60, &kkbtc_logo_image}, + {116, 2, 75, 60, &kkbtc_logo_image}, + {115, 2, 75, 60, &kkbtc_logo_image}, + {114, 2, 75, 60, &kkbtc_logo_image}, + {113, 2, 75, 60, &kkbtc_logo_image}, + {112, 2, 75, 60, &kkbtc_logo_image}, + {111, 2, 75, 60, &kkbtc_logo_image}, + {110, 2, 75, 60, &kkbtc_logo_image}, + {109, 2, 75, 60, &kkbtc_logo_image}, + {108, 2, 75, 60, &kkbtc_logo_image}, + {107, 2, 75, 60, &kkbtc_logo_image}, + {106, 2, 75, 60, &kkbtc_logo_image}, + {105, 2, 75, 60, &kkbtc_logo_image}, + {104, 2, 75, 60, &kkbtc_logo_image}, + {103, 2, 75, 60, &kkbtc_logo_image}, + {102, 2, 75, 60, &kkbtc_logo_image}, + {101, 2, 75, 60, &kkbtc_logo_image}, + {100, 2, 75, 60, &kkbtc_logo_image}, + {99, 2, 75, 60, &kkbtc_logo_image}, + {98, 2, 75, 60, &kkbtc_logo_image}, + {97, 2, 75, 60, &kkbtc_logo_image}, + {96, 2, 75, 60, &kkbtc_logo_image}, + {95, 2, 75, 60, &kkbtc_logo_image}, + {94, 2, 75, 60, &kkbtc_logo_image}, + {93, 2, 75, 60, &kkbtc_logo_image}, + {92, 2, 75, 60, &kkbtc_logo_image}, + {91, 2, 75, 60, &kkbtc_logo_image}, + {90, 2, 75, 60, &kkbtc_logo_image}, + {89, 2, 75, 60, &kkbtc_logo_image}, + {88, 2, 75, 60, &kkbtc_logo_image}, + {87, 2, 75, 60, &kkbtc_logo_image}, + {86, 2, 75, 60, &kkbtc_logo_image}, + {85, 2, 75, 60, &kkbtc_logo_image}, + {84, 2, 75, 60, &kkbtc_logo_image}, + {83, 2, 75, 60, &kkbtc_logo_image}, + {82, 2, 75, 60, &kkbtc_logo_image}, + {81, 2, 75, 60, &kkbtc_logo_image}, + {80, 2, 75, 60, &kkbtc_logo_image}, + {79, 2, 75, 60, &kkbtc_logo_image}, + {78, 2, 75, 60, &kkbtc_logo_image}, + {77, 2, 75, 60, &kkbtc_logo_image}, + {76, 2, 75, 60, &kkbtc_logo_image}, + {75, 2, 75, 60, &kkbtc_logo_image}, + {74, 2, 75, 60, &kkbtc_logo_image}, + {73, 2, 75, 60, &kkbtc_logo_image}, + {72, 2, 75, 60, &kkbtc_logo_image}, + {71, 2, 75, 60, &kkbtc_logo_image}, + {70, 2, 75, 60, &kkbtc_logo_image}, + {69, 2, 75, 60, &kkbtc_logo_image}, + {68, 2, 75, 60, &kkbtc_logo_image}, + {67, 2, 75, 60, &kkbtc_logo_image}, + {66, 2, 75, 60, &kkbtc_logo_image}, + {65, 2, 75, 60, &kkbtc_logo_image}, + {64, 2, 75, 60, &kkbtc_logo_image}, + {63, 2, 75, 60, &kkbtc_logo_image}, + {62, 2, 75, 60, &kkbtc_logo_image}, + {61, 2, 75, 60, &kkbtc_logo_image}, + {60, 2, 75, 60, &kkbtc_logo_image}, + {59, 2, 75, 60, &kkbtc_logo_image}, + {58, 2, 75, 60, &kkbtc_logo_image}, + {57, 2, 75, 60, &kkbtc_logo_image}, + {56, 2, 75, 60, &kkbtc_logo_image}, + {55, 2, 75, 60, &kkbtc_logo_image}, + {54, 2, 75, 60, &kkbtc_logo_image}, + {53, 2, 75, 60, &kkbtc_logo_image}, + {52, 2, 75, 60, &kkbtc_logo_image}, + {51, 2, 75, 60, &kkbtc_logo_image}, + {50, 2, 75, 60, &kkbtc_logo_image}, + {49, 2, 75, 60, &kkbtc_logo_image}, + {48, 2, 75, 60, &kkbtc_logo_image}, + {47, 2, 75, 60, &kkbtc_logo_image}, + {46, 2, 75, 60, &kkbtc_logo_image}, + {45, 2, 75, 60, &kkbtc_logo_image}, + {44, 2, 75, 60, &kkbtc_logo_image}, + {43, 2, 75, 60, &kkbtc_logo_image}, + {42, 2, 75, 60, &kkbtc_logo_image}, + {41, 2, 75, 60, &kkbtc_logo_image}, + {40, 2, 75, 60, &kkbtc_logo_image}, + {39, 2, 75, 60, &kkbtc_logo_image}, + {38, 2, 75, 60, &kkbtc_logo_image}, + {37, 2, 75, 60, &kkbtc_logo_image}, + {36, 2, 75, 60, &kkbtc_logo_image}, + {35, 2, 75, 60, &kkbtc_logo_image}, + {34, 2, 75, 60, &kkbtc_logo_image}, + {33, 2, 75, 60, &kkbtc_logo_image}, + {32, 2, 75, 60, &kkbtc_logo_image}, + {31, 2, 75, 60, &kkbtc_logo_image}, + {30, 2, 75, 60, &kkbtc_logo_image}, + {29, 2, 75, 60, &kkbtc_logo_image}, + {28, 2, 75, 60, &kkbtc_logo_image}, + {27, 2, 75, 60, &kkbtc_logo_image}, + {26, 2, 75, 60, &kkbtc_logo_image}, + {25, 2, 75, 60, &kkbtc_logo_image}, + {24, 2, 75, 60, &kkbtc_logo_image}, + {23, 2, 75, 60, &kkbtc_logo_image}, + {22, 2, 75, 60, &kkbtc_logo_image}, + {21, 2, 75, 60, &kkbtc_logo_image}, + {20, 2, 75, 60, &kkbtc_logo_image}, + {19, 2, 75, 60, &kkbtc_logo_image}, + {18, 2, 75, 60, &kkbtc_logo_image}, + {17, 2, 75, 60, &kkbtc_logo_image}, + {16, 2, 75, 60, &kkbtc_logo_image}, + {15, 2, 75, 60, &kkbtc_logo_image}, + {14, 2, 75, 60, &kkbtc_logo_image}, + {13, 2, 75, 60, &kkbtc_logo_image}, + {12, 2, 75, 60, &kkbtc_logo_image}, + {11, 2, 75, 60, &kkbtc_logo_image}, + {10, 2, 75, 60, &kkbtc_logo_image}, + {9, 2, 75, 60, &kkbtc_logo_image}, + {8, 2, 75, 60, &kkbtc_logo_image}, + {7, 2, 75, 60, &kkbtc_logo_image}, + {6, 2, 75, 60, &kkbtc_logo_image}, + {5, 2, 75, 60, &kkbtc_logo_image}, + {4, 2, 75, 60, &kkbtc_logo_image}, + {3, 2, 75, 60, &kkbtc_logo_image}, + {2, 2, 75, 60, &kkbtc_logo_image}, + {1, 2, 75, 60, &kkbtc_logo_image}, + {0, 2, 75, 60, &kkbtc_logo_image}, + {0, 2, 75, 60, &kkbtc_logo_image}, + {1, 2, 75, 60, &kkbtc_logo_image}, + {2, 2, 75, 60, &kkbtc_logo_image}, + {3, 2, 75, 60, &kkbtc_logo_image}, + {4, 2, 75, 60, &kkbtc_logo_image}, + {5, 2, 75, 60, &kkbtc_logo_image}, + {6, 2, 75, 60, &kkbtc_logo_image}, + {7, 2, 75, 60, &kkbtc_logo_image}, + {8, 2, 75, 60, &kkbtc_logo_image}, + {9, 2, 75, 60, &kkbtc_logo_image}, + {10, 2, 75, 60, &kkbtc_logo_image}, + {11, 2, 75, 60, &kkbtc_logo_image}, + {12, 2, 75, 60, &kkbtc_logo_image}, + {13, 2, 75, 60, &kkbtc_logo_image}, + {14, 2, 75, 60, &kkbtc_logo_image}, + {15, 2, 75, 60, &kkbtc_logo_image}, + {16, 2, 75, 60, &kkbtc_logo_image}, + {17, 2, 75, 60, &kkbtc_logo_image}, + {18, 2, 75, 60, &kkbtc_logo_image}, + {19, 2, 75, 60, &kkbtc_logo_image}, + {20, 2, 75, 60, &kkbtc_logo_image}, + {21, 2, 75, 60, &kkbtc_logo_image}, + {22, 2, 75, 60, &kkbtc_logo_image}, + {23, 2, 75, 60, &kkbtc_logo_image}, + {24, 2, 75, 60, &kkbtc_logo_image}, + {25, 2, 75, 60, &kkbtc_logo_image}, + {26, 2, 75, 60, &kkbtc_logo_image}, + {27, 2, 75, 60, &kkbtc_logo_image}, + {28, 2, 75, 60, &kkbtc_logo_image}, + {29, 2, 75, 60, &kkbtc_logo_image}, + {30, 2, 75, 60, &kkbtc_logo_image}, + {31, 2, 75, 60, &kkbtc_logo_image}, + {32, 2, 75, 60, &kkbtc_logo_image}, + {33, 2, 75, 60, &kkbtc_logo_image}, + {34, 2, 75, 60, &kkbtc_logo_image}, + {35, 2, 75, 60, &kkbtc_logo_image}, + {36, 2, 75, 60, &kkbtc_logo_image}, + {37, 2, 75, 60, &kkbtc_logo_image}, + {38, 2, 75, 60, &kkbtc_logo_image}, + {39, 2, 75, 60, &kkbtc_logo_image}, + {40, 2, 75, 60, &kkbtc_logo_image}, + {41, 2, 75, 60, &kkbtc_logo_image}, + {42, 2, 75, 60, &kkbtc_logo_image}, + {43, 2, 75, 60, &kkbtc_logo_image}, + {44, 2, 75, 60, &kkbtc_logo_image}, + {45, 2, 75, 60, &kkbtc_logo_image}, + {46, 2, 75, 60, &kkbtc_logo_image}, + {47, 2, 75, 60, &kkbtc_logo_image}, + {48, 2, 75, 60, &kkbtc_logo_image}, + {49, 2, 75, 60, &kkbtc_logo_image}, + {50, 2, 75, 60, &kkbtc_logo_image}, + {51, 2, 75, 60, &kkbtc_logo_image}, + {52, 2, 75, 60, &kkbtc_logo_image}, + {53, 2, 75, 60, &kkbtc_logo_image}, + {54, 2, 75, 60, &kkbtc_logo_image}, + {55, 2, 75, 60, &kkbtc_logo_image}, + {56, 2, 75, 60, &kkbtc_logo_image}, + {57, 2, 75, 60, &kkbtc_logo_image}, + {58, 2, 75, 60, &kkbtc_logo_image}, + {59, 2, 75, 60, &kkbtc_logo_image}, + {60, 2, 75, 60, &kkbtc_logo_image}, + {61, 2, 75, 60, &kkbtc_logo_image}, + {62, 2, 75, 60, &kkbtc_logo_image}, + {63, 2, 75, 60, &kkbtc_logo_image}, + {64, 2, 75, 60, &kkbtc_logo_image}, + {65, 2, 75, 60, &kkbtc_logo_image}, + {66, 2, 75, 60, &kkbtc_logo_image}, + {67, 2, 75, 60, &kkbtc_logo_image}, + {68, 2, 75, 60, &kkbtc_logo_image}, + {69, 2, 75, 60, &kkbtc_logo_image}, + {70, 2, 75, 60, &kkbtc_logo_image}, + {71, 2, 75, 60, &kkbtc_logo_image}, + {72, 2, 75, 60, &kkbtc_logo_image}, + {73, 2, 75, 60, &kkbtc_logo_image}, + {74, 2, 75, 60, &kkbtc_logo_image}, + {75, 2, 75, 60, &kkbtc_logo_image}, + {76, 2, 75, 60, &kkbtc_logo_image}, + {77, 2, 75, 60, &kkbtc_logo_image}, + {78, 2, 75, 60, &kkbtc_logo_image}, + {79, 2, 75, 60, &kkbtc_logo_image}, + {80, 2, 75, 60, &kkbtc_logo_image}, + {81, 2, 75, 60, &kkbtc_logo_image}, + {82, 2, 75, 60, &kkbtc_logo_image}, + {83, 2, 75, 60, &kkbtc_logo_image}, + {84, 2, 75, 60, &kkbtc_logo_image}, + {85, 2, 75, 60, &kkbtc_logo_image}, + {86, 2, 75, 60, &kkbtc_logo_image}, + {87, 2, 75, 60, &kkbtc_logo_image}, + {88, 2, 75, 60, &kkbtc_logo_image}, + {89, 2, 75, 60, &kkbtc_logo_image}, + {90, 2, 75, 60, &kkbtc_logo_image}, + {91, 2, 75, 60, &kkbtc_logo_image}, + {92, 2, 75, 60, &kkbtc_logo_image}, + {93, 2, 75, 60, &kkbtc_logo_image}, + {94, 2, 75, 60, &kkbtc_logo_image}, + } +}; + diff --git a/scripts/build/docker/device/btcdebug.sh b/scripts/build/docker/device/btcdebug.sh new file mode 100755 index 000000000..dc28de2e8 --- /dev/null +++ b/scripts/build/docker/device/btcdebug.sh @@ -0,0 +1,25 @@ +#!/bin/bash -e + +KEEPKEY_FIRMWARE="$(dirname "$(dirname "$(dirname "$(dirname "$( cd "$(dirname "$0")" ; pwd -P )")")")")" +cd $KEEPKEY_FIRMWARE + +IMAGETAG=kktech/firmware:v15 + +docker image inspect $IMAGETAG > /dev/null || docker pull $IMAGETAG + +docker run -t \ + -v $(pwd):/root/keepkey-firmware:z \ + $IMAGETAG /bin/sh -c "\ + mkdir /root/build && cd /root/build && \ + cmake -C /root/keepkey-firmware/cmake/caches/device.cmake /root/keepkey-firmware \ + -DCOIN_SUPPORT=BTC \ + -DCMAKE_BUILD_TYPE=Debug \ + -DKK_DEBUG_LINK=ON \ + -DVARIANTS=NoObsoleteVariants \ + -DCMAKE_COLOR_MAKEFILE=ON &&\ + make && \ + mkdir -p /root/keepkey-firmware/bin && \ + cp -r /root/build /root/keepkey-firmware/bin/ && \ + cp bin/*.bin /root/keepkey-firmware/bin/ && \ + cp bin/*.elf /root/keepkey-firmware/bin/ && \ + chown -R \`stat -c \"%u:%g\" /root/keepkey-firmware\` /root/keepkey-firmware/bin" diff --git a/scripts/build/docker/device/btcrelease.sh b/scripts/build/docker/device/btcrelease.sh new file mode 100755 index 000000000..70869f8ca --- /dev/null +++ b/scripts/build/docker/device/btcrelease.sh @@ -0,0 +1,24 @@ +#!/bin/bash -e + +KEEPKEY_FIRMWARE="$(dirname "$(dirname "$(dirname "$(dirname "$( cd "$(dirname "$0")" ; pwd -P )")")")")" +cd $KEEPKEY_FIRMWARE + +IMAGETAG=kktech/firmware:v15 + +docker image inspect $IMAGETAG > /dev/null || docker pull $IMAGETAG + +docker run -t \ + -v $(pwd):/root/keepkey-firmware:z \ + $IMAGETAG /bin/sh -c "\ + mkdir /root/build && cd /root/build && \ + cmake -C /root/keepkey-firmware/cmake/caches/device.cmake /root/keepkey-firmware \ + -DCOIN_SUPPORT=BTC \ + -DVARIANTS=NoObsoleteVariants \ + -DCMAKE_BUILD_TYPE=MinSizeRel \ + -DCMAKE_COLOR_MAKEFILE=ON &&\ + make && \ + mkdir -p /root/keepkey-firmware/bin && \ + cp -r /root/build /root/keepkey-firmware/bin/ && \ + cp bin/*.bin /root/keepkey-firmware/bin/ && \ + cp bin/*.elf /root/keepkey-firmware/bin/ && \ + chown -R \`stat -c \"%u:%g\" /root/keepkey-firmware\` /root/keepkey-firmware/bin" diff --git a/scripts/build/docker/device/debug.sh b/scripts/build/docker/device/debug.sh index 0150e9138..d0ed9f7d2 100755 --- a/scripts/build/docker/device/debug.sh +++ b/scripts/build/docker/device/debug.sh @@ -14,6 +14,7 @@ docker run -t \ cmake -C /root/keepkey-firmware/cmake/caches/device.cmake /root/keepkey-firmware \ -DCMAKE_BUILD_TYPE=Debug \ -DKK_DEBUG_LINK=ON \ + -DVARIANTS=NoObsoleteVariants \ -DCMAKE_COLOR_MAKEFILE=ON &&\ make && \ mkdir -p /root/keepkey-firmware/bin && \ diff --git a/scripts/build/docker/device/release.sh b/scripts/build/docker/device/release.sh index 18cfe53dc..d29180608 100755 --- a/scripts/build/docker/device/release.sh +++ b/scripts/build/docker/device/release.sh @@ -12,6 +12,7 @@ docker run -t \ $IMAGETAG /bin/sh -c "\ mkdir /root/build && cd /root/build && \ cmake -C /root/keepkey-firmware/cmake/caches/device.cmake /root/keepkey-firmware \ + -DVARIANTS=NoObsoleteVariants \ -DCMAKE_BUILD_TYPE=MinSizeRel \ -DCMAKE_COLOR_MAKEFILE=ON &&\ make && \ diff --git a/scripts/build/docker/emulator/btcdebug.sh b/scripts/build/docker/emulator/btcdebug.sh new file mode 100755 index 000000000..eea64b010 --- /dev/null +++ b/scripts/build/docker/emulator/btcdebug.sh @@ -0,0 +1,24 @@ +#!/bin/bash -e + +KEEPKEY_FIRMWARE="$(dirname "$(dirname "$(dirname "$(dirname "$( cd "$(dirname "$0")" ; pwd -P )")")")")" +cd $KEEPKEY_FIRMWARE + +IMAGETAG=kktech/firmware:v15 + +docker pull $IMAGETAG + +docker run -t \ + -v $(pwd):/root/keepkey-firmware:z \ + $IMAGETAG /bin/sh -c "\ + rm -rf /root/keepkey-firmware/build && \ + mkdir /root/build && cd /root/build && \ + cmake -C /root/keepkey-firmware/cmake/caches/emulator.cmake /root/keepkey-firmware \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DCOIN_SUPPORT=BTC \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_COLOR_MAKEFILE=ON &&\ + make all && \ + (make xunit || true) && \ + cp -r /root/build /root/keepkey-firmware/build && \ + chown -R \`stat -c \"%u:%g\" /root/keepkey-firmware\` /root/keepkey-firmware/build" diff --git a/scripts/emulator/Dockerfile b/scripts/emulator/Dockerfile index e0b2c6d3f..53c6bdd99 100644 --- a/scripts/emulator/Dockerfile +++ b/scripts/emulator/Dockerfile @@ -3,11 +3,13 @@ FROM kktech/firmware:v15 WORKDIR /kkemu COPY ./ /kkemu +ARG coinsupport="" RUN cmake -C ./cmake/caches/emulator.cmake . \ -DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_C_COMPILER=clang \ -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_BUILD_TYPE=Debug \ + ${coinsupport} \ -DCMAKE_COLOR_MAKEFILE=ON RUN make -j diff --git a/scripts/emulator/btcDockerStart.sh b/scripts/emulator/btcDockerStart.sh new file mode 100755 index 000000000..ff07ef79f --- /dev/null +++ b/scripts/emulator/btcDockerStart.sh @@ -0,0 +1,2 @@ +docker-compose -f docker-compose-btc.yml up --build python-keepkey +docker kill emulator_kkemu_1 diff --git a/scripts/emulator/docker-compose-btc.yml b/scripts/emulator/docker-compose-btc.yml new file mode 100644 index 000000000..77bb3fca3 --- /dev/null +++ b/scripts/emulator/docker-compose-btc.yml @@ -0,0 +1,44 @@ +version: '3' +services: + kkemu: + image: kktech/kkemu:latest + build: + context: '../../' + args: + coinsupport: "-DCOIN_SUPPORT=BTC" + dockerfile: 'scripts/emulator/Dockerfile' + networks: + - local-net + ports: + - "127.0.0.1:11044:11044/udp" + - "127.0.0.1:11045:11045/udp" + - "127.0.0.1:5000:5000" + python-keepkey: + build: + context: '../../' + dockerfile: 'scripts/emulator/python-keepkey.Dockerfile' + volumes: + - test-reports:/kkemu/test-reports:rw + networks: + - local-net + depends_on: + - kkemu + firmware-unit: + build: + context: '../../' + args: + coinsupport: "-DCOIN_SUPPORT=BTC" + dockerfile: 'scripts/emulator/Dockerfile' + volumes: + - test-reports:/kkemu/test-reports:rw + entrypoint: ['scripts/emulator/firmware-unit.sh'] + networks: + - local-net + depends_on: + - kkemu +networks: + local-net: + external: false +volumes: + test-reports: + external: false diff --git a/scripts/emulator/dockerStart.sh b/scripts/emulator/dockerStart.sh new file mode 100755 index 000000000..c27aa32ba --- /dev/null +++ b/scripts/emulator/dockerStart.sh @@ -0,0 +1,2 @@ +docker-compose up --build python-keepkey +docker kill emulator_kkemu_1 diff --git a/tools/display_test/CMakeLists.txt b/tools/display_test/CMakeLists.txt index 0d280ab80..549f9cac0 100644 --- a/tools/display_test/CMakeLists.txt +++ b/tools/display_test/CMakeLists.txt @@ -1,3 +1,7 @@ +if("${VARIANTS}" STREQUAL "NoObsoleteVariants") + return() +endif() + if(NOT ${KK_EMULATOR}) set(sources main.c diff --git a/tools/firmware/CMakeLists.txt b/tools/firmware/CMakeLists.txt index 18c50e411..58692eebb 100644 --- a/tools/firmware/CMakeLists.txt +++ b/tools/firmware/CMakeLists.txt @@ -1,4 +1,12 @@ if(NOT ${KK_EMULATOR}) + + set(TARGESTR "firmware.keepkey.elf") + set(TARGBSTR "firmware.keepkey.bin") + if("${COIN_SUPPORT}" STREQUAL "BTC") + set(TARGESTR "firmware.keepkeybtc.elf") + set(TARGBSTR "firmware.keepkeybtc.bin") + endif() + set(sources keepkey_main.c startup.s @@ -25,26 +33,36 @@ if(NOT ${KK_EMULATOR}) -lc -lm) - add_executable(firmware.keepkey.elf ${sources}) - target_link_libraries(firmware.keepkey.elf - kkfirmware - kkfirmware.keepkey - kkboard - kkboard.keepkey - kkvariant.keepkey - kkvariant.salt - kktransport - trezorcrypto - qrcodegenerator - SecAESSTM32 - kkrand + add_executable(${TARGESTR} ${sources}) + + set(libraries + kkfirmware + kkfirmware.keepkey + kkboard + kkboard.keepkey + kkvariant.keepkey + kktransport + trezorcrypto + qrcodegenerator + SecAESSTM32 + kkrand) + +if(NOT "${COIN_SUPPORT}" STREQUAL "BTC") + list(APPEND libraries + kkvariant.salt + ) +endif() + + target_link_libraries(${TARGESTR} + ${libraries} -lopencm3_stm32f2 -lc -lm) - add_custom_command(TARGET firmware.keepkey.elf - POST_BUILD - COMMAND ${CMAKE_OBJCOPY} ARGS -O binary - ${CMAKE_BINARY_DIR}/bin/firmware.keepkey.elf - ${CMAKE_BINARY_DIR}/bin/firmware.keepkey.bin) + + add_custom_command(TARGET ${TARGESTR} + POST_BUILD + COMMAND ${CMAKE_OBJCOPY} ARGS -O binary + ${CMAKE_BINARY_DIR}/bin/${TARGESTR} + ${CMAKE_BINARY_DIR}/bin/${TARGBSTR}) endif() diff --git a/tools/firmware/keepkey_main.c b/tools/firmware/keepkey_main.c index fcf4aa9cb..95fcf94e3 100644 --- a/tools/firmware/keepkey_main.c +++ b/tools/firmware/keepkey_main.c @@ -174,8 +174,24 @@ int main(void) { _mmhusr_isr = (void *)&mmhisr; { // limit sigRet lifetime to this block + /* F5 hardening: replace full signatures_ok() (~1 sec crypto) with fast + * metadata presence check. The bootloader has already performed the + * authoritative signature verification before jumping here. We only + * need to know whether the bootloader considered us signed, which is + * indicated by valid signature indices in flash metadata. */ int sigRet = SIG_FAIL; - sigRet = signatures_ok(); + + volatile uint8_t si1 = *((volatile uint8_t *)FLASH_META_SIGINDEX1); + volatile uint8_t si2 = *((volatile uint8_t *)FLASH_META_SIGINDEX2); + volatile uint8_t si3 = *((volatile uint8_t *)FLASH_META_SIGINDEX3); + + if (si1 >= 1 && si1 <= PUBKEYS && + si2 >= 1 && si2 <= PUBKEYS && + si3 >= 1 && si3 <= PUBKEYS && + si1 != si2 && si1 != si3 && si2 != si3) { + sigRet = SIG_OK; + } + flash_collectHWEntropy(SIG_OK == sigRet); /* Drop privileges */ diff --git a/tools/variant/CMakeLists.txt b/tools/variant/CMakeLists.txt index 0a250e92e..dae9b45a3 100644 --- a/tools/variant/CMakeLists.txt +++ b/tools/variant/CMakeLists.txt @@ -1,3 +1,11 @@ +if("${COIN_SUPPORT}" STREQUAL "BTC") + return() +endif() + +if("${VARIANTS}" STREQUAL "NoObsoleteVariants") + return() +endif() + if(NOT ${KK_EMULATOR}) include_directories( ${CMAKE_SOURCE_DIR}/include diff --git a/unittests/crypto/CMakeLists.txt b/unittests/crypto/CMakeLists.txt index 782549f28..0c925a5c4 100644 --- a/unittests/crypto/CMakeLists.txt +++ b/unittests/crypto/CMakeLists.txt @@ -8,17 +8,27 @@ include_directories( ${CMAKE_SOURCE_DIR}/deps/crypto/trezor-crypto) add_executable(crypto-unit ${sources}) -target_link_libraries(crypto-unit - gtest_main - kkfirmware - kkfirmware.keepkey - kkboard - kkboard.keepkey - kkvariant.keepkey + +set(libraries + gtest_main + kkfirmware + kkfirmware.keepkey + kkboard + kkboard.keepkey + kkvariant.keepkey + kkboard + kkemulator + qrcodegenerator + kkrand + trezorcrypto + kktransport) + +if(NOT "${COIN_SUPPORT}" STREQUAL "BTC") + list(APPEND libraries kkvariant.salt - kkboard - kkemulator - qrcodegenerator - kkrand - trezorcrypto - kktransport) + ) +endif() + +target_link_libraries(crypto-unit + ${libraries} + ) diff --git a/unittests/firmware/CMakeLists.txt b/unittests/firmware/CMakeLists.txt index 647d72571..1f4ccddec 100644 --- a/unittests/firmware/CMakeLists.txt +++ b/unittests/firmware/CMakeLists.txt @@ -1,15 +1,19 @@ set(sources coins.cpp - cosmos.cpp - eos.cpp - ethereum.cpp - nano.cpp recovery.cpp - ripple.cpp storage.cpp usb_rx.cpp u2f.cpp) +if(NOT "${COIN_SUPPORT}" STREQUAL "BTC") + list(APPEND sources + cosmos.cpp + eos.cpp + ethereum.cpp + nano.cpp + ripple.cpp) +endif() + include_directories( ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/lib/firmware @@ -17,14 +21,14 @@ include_directories( ${CMAKE_SOURCE_DIR}/deps/crypto/trezor-crypto) add_executable(firmware-unit ${sources}) -target_link_libraries(firmware-unit + +set(libraries gmock_main kkfirmware kkfirmware.keepkey kkboard kkboard.keepkey kkvariant.keepkey - kkvariant.salt kkboard kkemulator trezorcrypto @@ -32,3 +36,9 @@ target_link_libraries(firmware-unit SecAESSTM32 kkrand kktransport) + +if(NOT "${COIN_SUPPORT}" STREQUAL "BTC") + list(APPEND libraries kkvariant.salt) +endif() + +target_link_libraries(firmware-unit ${libraries}) diff --git a/unittests/firmware/coins.cpp b/unittests/firmware/coins.cpp index c11a59bd6..06062970f 100644 --- a/unittests/firmware/coins.cpp +++ b/unittests/firmware/coins.cpp @@ -1,6 +1,8 @@ extern "C" { #include "keepkey/firmware/coins.h" +#ifndef BITCOIN_ONLY #include "keepkey/firmware/ethereum_tokens.h" +#endif // BITCOIN_ONLY } #include "gtest/gtest.h" @@ -74,7 +76,7 @@ TEST(Coins, TableSanity) { const auto &coin = coins[i]; if (!coin.has_contract_address) continue; - +#ifndef BITCOIN_ONLY const TokenType *token; if (!tokenByTicker(1, coin.coin_shortcut, &token)) { EXPECT_TRUE(false) << "Can't uniquely find " << coin.coin_shortcut; @@ -84,6 +86,7 @@ TEST(Coins, TableSanity) { EXPECT_TRUE(memcmp(coin.contract_address.bytes, token->address, coin.contract_address.size) == 0) << "Contract address mismatch for " << coin.coin_shortcut; +#endif // BITCOIN_ONLY } } @@ -134,6 +137,7 @@ TEST(Coins, BIP32AccountName) { 5, true, "Bitcoin Account #1\nChange Address #1"}, +#ifndef BITCOIN_ONLY {"Ethereum", {0x80000000 | 44, 0x80000000 | 60, 0x80000000 | 1, 0, 0}, 5, @@ -178,7 +182,9 @@ TEST(Coins, BIP32AccountName) { {0x80000000 | 44, 0x80000000 | 931, 0x80000000 | 69, 0, 0}, 5, true, - "MAYAChain Account #69"}}; + "MAYAChain Account #69"} +#endif // BITCOIN_ONLY + }; for (const auto &vec : vector) { char node_str[NODE_STRING_LENGTH]; @@ -200,6 +206,7 @@ TEST(Coins, BIP32AccountName) { } } +#ifndef BITCOIN_ONLY TEST(Coins, CoinByNameOrTicker) { const CoinType *ticker = coinByNameOrTicker("ZRX"); const CoinType *name = coinByNameOrTicker("0x"); @@ -221,3 +228,4 @@ TEST(Coins, TokenByChainAddress) { ASSERT_NE(zrx, nullptr); EXPECT_EQ(zrx->ticker, std::string(" ZRX")); } +#endif // BITCOIN_ONLY