From 35b46debce230b2bd52176a88a9e07ab06836c81 Mon Sep 17 00:00:00 2001 From: Keyur279 Date: Wed, 9 Jul 2025 02:43:25 +0530 Subject: [PATCH 01/23] feat: Add stellar to cmake --- utilities/cmake/firmware/firmware.cmake | 2 ++ utilities/cmake/simulator/simulator.cmake | 2 ++ 2 files changed, 4 insertions(+) diff --git a/utilities/cmake/firmware/firmware.cmake b/utilities/cmake/firmware/firmware.cmake index c9a405509..c505a9902 100644 --- a/utilities/cmake/firmware/firmware.cmake +++ b/utilities/cmake/firmware/firmware.cmake @@ -64,6 +64,7 @@ target_include_directories(${EXECUTABLE} PRIVATE apps/constellation_app apps/icp_app apps/exchange_app + apps/stellar_app src/ src/menu @@ -192,6 +193,7 @@ target_include_directories(${EXECUTABLE} PRIVATE $<$:${PROJECT_SOURCE_DIR}/tests/apps/xrp_app> $<$:${PROJECT_SOURCE_DIR}/tests/apps/constellation_app> $<$:${PROJECT_SOURCE_DIR}/tests/apps/icp_app> + $<$:${PROJECT_SOURCE_DIR}/tests/apps/stellar_app> ) target_compile_options(${EXECUTABLE} PRIVATE diff --git a/utilities/cmake/simulator/simulator.cmake b/utilities/cmake/simulator/simulator.cmake index ece63f670..e54e7f6f5 100644 --- a/utilities/cmake/simulator/simulator.cmake +++ b/utilities/cmake/simulator/simulator.cmake @@ -61,6 +61,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE apps/constellation_app apps/icp_app apps/exchange_app + apps/stellar_app src/ src/menu @@ -168,6 +169,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE $<$:${PROJECT_SOURCE_DIR}/tests/apps/xrp_app> $<$:${PROJECT_SOURCE_DIR}/tests/apps/constellation_app> $<$:${PROJECT_SOURCE_DIR}/tests/apps/icp_app> + $<$:${PROJECT_SOURCE_DIR}/tests/apps/stellar_app> ) IF(UNIT_TESTS_SWITCH) From cd36a59e129d082e6d34fec4cbd50b20f3b3af76 Mon Sep 17 00:00:00 2001 From: Keyur279 Date: Wed, 9 Jul 2025 02:45:14 +0530 Subject: [PATCH 02/23] feat: Add proto-options for stellar --- common/proto-options/stellar/core.options | 1 + common/proto-options/stellar/error.options | 1 + common/proto-options/stellar/get_public_key.options | 5 +++++ common/proto-options/stellar/sign_txn.options | 4 ++++ 4 files changed, 11 insertions(+) create mode 100644 common/proto-options/stellar/core.options create mode 100644 common/proto-options/stellar/error.options create mode 100644 common/proto-options/stellar/get_public_key.options create mode 100644 common/proto-options/stellar/sign_txn.options diff --git a/common/proto-options/stellar/core.options b/common/proto-options/stellar/core.options new file mode 100644 index 000000000..934002ae3 --- /dev/null +++ b/common/proto-options/stellar/core.options @@ -0,0 +1 @@ +# Options for file common/cypherock-common/proto/stellar/core.proto \ No newline at end of file diff --git a/common/proto-options/stellar/error.options b/common/proto-options/stellar/error.options new file mode 100644 index 000000000..30d536752 --- /dev/null +++ b/common/proto-options/stellar/error.options @@ -0,0 +1 @@ +# Options for file common/cypherock-common/proto/stellar/error.proto diff --git a/common/proto-options/stellar/get_public_key.options b/common/proto-options/stellar/get_public_key.options new file mode 100644 index 000000000..5ff250620 --- /dev/null +++ b/common/proto-options/stellar/get_public_key.options @@ -0,0 +1,5 @@ +# Options for file common/cypherock-common/proto/stellar/get_public_key.proto +stellar.GetPublicKeysDerivationPath.path type:FT_STATIC max_count:3 fixed_length:true +stellar.GetPublicKeysIntiateRequest.wallet_id type:FT_STATIC max_size:32 fixed_length:true +stellar.GetPublicKeysIntiateRequest.derivation_paths type:FT_STATIC max_count:100 fixed_length:true +stellar.GetPublicKeysResultResponse.public_keys type:FT_STATIC max_size:32 max_count:10 fixed_length:true diff --git a/common/proto-options/stellar/sign_txn.options b/common/proto-options/stellar/sign_txn.options new file mode 100644 index 000000000..7cfcfad4e --- /dev/null +++ b/common/proto-options/stellar/sign_txn.options @@ -0,0 +1,4 @@ +# Options for file common/cypherock-common/proto/stellar/sign_txn.proto +stellar.SignTxnInitiateRequest.wallet_id type:FT_STATIC max_size:32 fixed_length:true +stellar.SignTxnInitiateRequest.derivation_path type:FT_STATIC max_count:3 fixed_length:true +stellar.SignTxnSignatureResponse.signature type:FT_STATIC max_size:2048 fixed_length:false From 32f9713b5fb78e56dd0f48ca8bf8e581f0724a47 Mon Sep 17 00:00:00 2001 From: Keyur279 Date: Wed, 9 Jul 2025 02:48:33 +0530 Subject: [PATCH 03/23] feat: Registered stellar app --- common/core/app_registry.h | 2 +- common/core/core_flow_init.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/common/core/app_registry.h b/common/core/app_registry.h index f0fa5a938..4a1e048fa 100644 --- a/common/core/app_registry.h +++ b/common/core/app_registry.h @@ -23,7 +23,7 @@ * MACROS AND DEFINES *****************************************************************************/ -#define REGISTRY_MAX_APPS 25 +#define REGISTRY_MAX_APPS 26 /***************************************************************************** * TYPEDEFS diff --git a/common/core/core_flow_init.c b/common/core/core_flow_init.c index ff6466188..9267d48b1 100644 --- a/common/core/core_flow_init.c +++ b/common/core/core_flow_init.c @@ -89,6 +89,7 @@ #include "starknet_main.h" #include "tron_main.h" #include "xrp_main.h" +#include "stellar_main.h" /***************************************************************************** * EXTERN VARIABLES @@ -191,4 +192,5 @@ void core_init_app_registry() { registry_add_app(get_constellation_app_desc()); registry_add_app(get_icp_app_desc()); registry_add_app(get_exchange_app_desc()); + registry_add_app(get_stellar_app_desc()); } From be930b58f329709e4f5f24c5de3d592de4ffb55b Mon Sep 17 00:00:00 2001 From: Keyur279 Date: Wed, 9 Jul 2025 02:53:43 +0530 Subject: [PATCH 04/23] feat: Add main and api files --- apps/stellar_app/stellar_api.c | 196 ++++++++++++++++++++++++++++++++ apps/stellar_app/stellar_api.h | 113 ++++++++++++++++++ apps/stellar_app/stellar_main.c | 151 ++++++++++++++++++++++++ apps/stellar_app/stellar_main.h | 43 +++++++ 4 files changed, 503 insertions(+) create mode 100644 apps/stellar_app/stellar_api.c create mode 100644 apps/stellar_app/stellar_api.h create mode 100644 apps/stellar_app/stellar_main.c create mode 100644 apps/stellar_app/stellar_main.h diff --git a/apps/stellar_app/stellar_api.c b/apps/stellar_app/stellar_api.c new file mode 100644 index 000000000..d167d9283 --- /dev/null +++ b/apps/stellar_app/stellar_api.c @@ -0,0 +1,196 @@ +/** + * @file stellar_api.c + * @author Cypherock X1 Team + * @brief Defines helpers apis for Stellar app. + * @copyright Copyright (c) 2025 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + * + ****************************************************************************** + * @attention + * + * (c) Copyright 2025 by HODL TECH PTE LTD + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject + * to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, + * as defined below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of + * rights under the License will not include, and the License does not + * grant to you, the right to Sell the Software. + * + * For purposes of the foregoing, "Sell" means practicing any or all + * of the rights granted to you under the License to provide to third + * parties, for a fee or other consideration (including without + * limitation fees for hosting or consulting/ support services related + * to the Software), a product or service whose value derives, entirely + * or substantially, from the functionality of the Software. Any license + * notice or attribution required by the License must also include + * this Commons Clause License Condition notice. + * + * Software: All X1Wallet associated files. + * License: MIT + * Licensor: HODL TECH PTE LTD + * + ****************************************************************************** + */ + +/***************************************************************************** + * INCLUDES + *****************************************************************************/ + +#include "stellar_api.h" + +#include +#include + +#include "common_error.h" +#include "core_api.h" +#include "events.h" + +/***************************************************************************** + * EXTERN VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * STATIC VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTION PROTOTYPES + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTIONS + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTIONS + *****************************************************************************/ +bool decode_stellar_query(const uint8_t *data, + uint16_t data_size, + stellar_query_t *query_out) { + if (NULL == data || NULL == query_out || 0 == data_size) { + stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_DECODING_FAILED); + return false; + } + + // zeroise for safety from garbage in the query reference + memzero(query_out, sizeof(stellar_query_t)); + + /* Create a stream that reads from the buffer. */ + pb_istream_t stream = pb_istream_from_buffer(data, data_size); + + /* Now we are ready to decode the message. */ + bool status = pb_decode(&stream, STELLAR_QUERY_FIELDS, query_out); + + /* Send error to host if status is false*/ + if (false == status) { + stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_DECODING_FAILED); + } + + return status; +} + +bool encode_stellar_result(const stellar_result_t *result, + uint8_t *buffer, + uint16_t max_buffer_len, + size_t *bytes_written_out) { + if (NULL == result || NULL == buffer || NULL == bytes_written_out) + return false; + + /* Create a stream that will write to our buffer. */ + pb_ostream_t stream = pb_ostream_from_buffer(buffer, max_buffer_len); + + /* Now we are ready to encode the message! */ + bool status = pb_encode(&stream, STELLAR_RESULT_FIELDS, result); + + if (true == status) { + *bytes_written_out = stream.bytes_written; + } + + return status; +} + +bool check_stellar_query(const stellar_query_t *query, pb_size_t exp_query_tag) { + if ((NULL == query) || (exp_query_tag != query->which_request)) { + stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_QUERY); + return false; + } + return true; +} + +stellar_result_t init_stellar_result(pb_size_t result_tag) { + stellar_result_t result = STELLAR_RESULT_INIT_ZERO; + result.which_response = result_tag; + return result; +} + +void stellar_send_error(pb_size_t which_error, uint32_t error_code) { + stellar_result_t result = init_stellar_result(STELLAR_RESULT_COMMON_ERROR_TAG); + result.common_error = init_common_error(which_error, error_code); + stellar_send_result(&result); +} + +void stellar_send_result(const stellar_result_t *result) { + // TODO: Set all option files + uint8_t buffer[1700] = {0}; + size_t bytes_encoded = 0; + ASSERT(encode_stellar_result(result, buffer, sizeof(buffer), &bytes_encoded)); + send_response_to_host(&buffer[0], bytes_encoded); +} + +bool stellar_get_query(stellar_query_t *query, pb_size_t exp_query_tag) { + evt_status_t event = get_events(EVENT_CONFIG_USB, MAX_INACTIVITY_TIMEOUT); + + if (true == event.p0_event.flag) { + return false; + } + + if (!decode_stellar_query( + event.usb_event.p_msg, event.usb_event.msg_size, query)) { + return false; + } + + if (!check_stellar_query(query, exp_query_tag)) { + return false; + } + + return true; +} diff --git a/apps/stellar_app/stellar_api.h b/apps/stellar_app/stellar_api.h new file mode 100644 index 000000000..6d05e268d --- /dev/null +++ b/apps/stellar_app/stellar_api.h @@ -0,0 +1,113 @@ +/** + * @file stellar_api.h + * @author Cypherock X1 Team + * @brief Header file to export some helper functions for the Stellar app + * @copyright Copyright (c) 2025 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + */ +#ifndef STELLAR_API_H +#define STELLAR_API_H + +/***************************************************************************** + * INCLUDES + *****************************************************************************/ + +#include +#include + +/***************************************************************************** + * MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * EXPORTED VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTION PROTOTYPES + *****************************************************************************/ + +/** + * @brief API to decode query from host with `STELLAR_QUERY_FIELDS` + * + * @param[in] data: PB encoded bytestream received from host + * @param[in] data_size: size of pb encoded bytestream + * @param[out] query_out: @ref stellar_query_t obj to copy the decoded result to + * @return bool True if decoding was successful, else false + */ +bool decode_stellar_query(const uint8_t *data, + uint16_t data_size, + stellar_query_t *query_out); + +/** + * @brief Encodes the STELLAR result with `STELLAR_RESULT_FIELDS` to byte-stream + * + * @param[in] result: object of populated @ref stellar_result_t to be encoded + * @param[out] buffer: buffer to fill byte-stream into + * @param[in] max_buffer_len: Max length allowed for writing bytestream to + * buffer + * @param[out] bytes_written_out: bytes written to bytestream + * @return bool True if decoding was successful, else false + */ +bool encode_stellar_result(const stellar_result_t *result, + uint8_t *buffer, + uint16_t max_buffer_len, + size_t *bytes_written_out); + +/** + * @brief This API checks if the `which_request` field of the query of type + * `stellar_query_t` matches against the expected tag. + * + * @param query The query of type `stellar_query_t` to be checked + * @param exp_query_tag The expected tag of the query + * @return true If the query tag matches the expected tag + * @return false If the query tag does not match the expected tag + */ +bool check_stellar_query(const stellar_query_t *query, pb_size_t exp_query_tag); + +/** + * @brief Returns zero initialized object of type + * stellar_result_t result_tag set in result.which_response field + * + * @param result_tag Result tag to be set in the stellar_result_t result + * @return stellar_result_t Result object of type stellar_result_t + */ +stellar_result_t init_stellar_result(pb_size_t result_tag); + +/** + * @brief Send the error to the host. + * + * @param which_error The error type to be sent + * @param error_code The error code to sent to the host + */ +void stellar_send_error(pb_size_t which_error, uint32_t error_code); + +/** + * @brief This API encodes stellar_result_t in protobuf structure. + * @details If the encoding is successful, then it sends the corresponding + * result to the host. + * + * The function ASSERTs the result of encode_stellar_result internally. + * + * @param result The result which needs to be sent to the host. + */ +void stellar_send_result(const stellar_result_t *result); + +/** + * @brief This API receives request of type stellar_query_t of type + * exp_query_tag from the host. + * + * @param query The reference to which the query needs to be populated + * @param exp_query_tag The expected tag of the query + * @return true If the query was recieved from the host matching the tag + * @return false If the request timed out or the recieved request did not match + * the tag + */ +bool stellar_get_query(stellar_query_t *query, pb_size_t exp_query_tag); + +#endif diff --git a/apps/stellar_app/stellar_main.c b/apps/stellar_app/stellar_main.c new file mode 100644 index 000000000..7d79224ed --- /dev/null +++ b/apps/stellar_app/stellar_main.c @@ -0,0 +1,151 @@ +/** + * @file stellar_main.c + * @author Cypherock X1 Team + * @brief A common entry point to various Stellar coin actions supported. + * @copyright Copyright (c) 2025 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + * + ****************************************************************************** + * @attention + * + * (c) Copyright 2025 by HODL TECH PTE LTD + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject + * to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, + * as defined below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of + * rights under the License will not include, and the License does not + * grant to you, the right to Sell the Software. + * + * For purposes of the foregoing, "Sell" means practicing any or all + * of the rights granted to you under the License to provide to third + * parties, for a fee or other consideration (including without + * limitation fees for hosting or consulting/ support services related + * to the Software), a product or service whose value derives, entirely + * or substantially, from the functionality of the Software. Any license + * notice or attribution required by the License must also include + * this Commons Clause License Condition notice. + * + * Software: All X1Wallet associated files. + * License: MIT + * Licensor: HODL TECH PTE LTD + * + ****************************************************************************** + */ + +/***************************************************************************** + * INCLUDES + *****************************************************************************/ + +#include "stellar_main.h" + +#include "status_api.h" +#include "stellar_api.h" +#include "stellar_priv.h" + +/***************************************************************************** + * EXTERN VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTION PROTOTYPES + *****************************************************************************/ +/** + * @brief Entry point for the Stellar application of the X1 vault. It is invoked + * by the X1 vault firmware, as soon as there is a USB request raised for the + * Stellar app. + * + * @param usb_evt The USB event which triggered invocation of the Stellar app + */ +void stellar_main(usb_event_t usb_evt, const void *stellar_app_config); + +/***************************************************************************** + * STATIC VARIABLES + *****************************************************************************/ + +static const cy_app_desc_t stellar_app_desc = {.id = 25, + .version = + { + .major = 1, + .minor = 0, + .patch = 0, + }, + .app = stellar_main, + .app_config = NULL}; + +/***************************************************************************** + * STATIC FUNCTIONS + *****************************************************************************/ +void stellar_main(usb_event_t usb_evt, const void *stellar_app_config) { + stellar_query_t query = STELLAR_QUERY_INIT_DEFAULT; + + if (false == decode_stellar_query(usb_evt.p_msg, usb_evt.msg_size, &query)) { + return; + } + + /* Set status to CORE_DEVICE_IDLE_STATE_USB to indicate host that we are now + * servicing a USB initiated command */ + core_status_set_idle_state(CORE_DEVICE_IDLE_STATE_USB); + + switch ((uint8_t)query.which_request) { + case STELLAR_QUERY_GET_PUBLIC_KEYS_TAG: + case STELLAR_QUERY_GET_USER_VERIFIED_PUBLIC_KEY_TAG: { + stellar_get_pub_keys(&query); + break; + } + case STELLAR_QUERY_SIGN_TXN_TAG: { + stellar_sign_transaction(&query); + break; + } + default: { + /* In case we ever encounter invalid query, convey to the host app */ + stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_QUERY); + break; + } + } + + return; +} + +/***************************************************************************** + * GLOBAL FUNCTIONS + *****************************************************************************/ +const cy_app_desc_t *get_stellar_app_desc() { + return &stellar_app_desc; +} \ No newline at end of file diff --git a/apps/stellar_app/stellar_main.h b/apps/stellar_app/stellar_main.h new file mode 100644 index 000000000..3a099347f --- /dev/null +++ b/apps/stellar_app/stellar_main.h @@ -0,0 +1,43 @@ +/** + * @file stellar_main.h + * @author Cypherock X1 Team + * @brief + * @details + + * @copyright Copyright (c) 2024 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + * + */ + +#ifndef STELLAR_MAIN_H +#define STELLAR_MAIN_H + +/***************************************************************************** + * INCLUDES + *****************************************************************************/ + +#include "app_registry.h" +#include "events.h" +/***************************************************************************** + * MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * EXPORTED VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTION PROTOTYPES + *****************************************************************************/ +/** + * @brief Returns the config for Stellar chain app descriptor + * + * @return A const reference to cy_app_desc_t + */ +const cy_app_desc_t *get_stellar_app_desc(); +#endif /* STELLAR_MAIN_H */ From 1d6af75d778f48439b60d8a458cc6d6294e340c6 Mon Sep 17 00:00:00 2001 From: Keyur279 Date: Wed, 9 Jul 2025 02:56:11 +0530 Subject: [PATCH 05/23] feat: Add stellar context and helper files --- apps/stellar_app/stellar_context.h | 101 ++++++++++++++++++++++++++ apps/stellar_app/stellar_helpers.c | 110 +++++++++++++++++++++++++++++ apps/stellar_app/stellar_helpers.h | 60 ++++++++++++++++ 3 files changed, 271 insertions(+) create mode 100644 apps/stellar_app/stellar_context.h create mode 100644 apps/stellar_app/stellar_helpers.c create mode 100644 apps/stellar_app/stellar_helpers.h diff --git a/apps/stellar_app/stellar_context.h b/apps/stellar_app/stellar_context.h new file mode 100644 index 000000000..3ce6e74a8 --- /dev/null +++ b/apps/stellar_app/stellar_context.h @@ -0,0 +1,101 @@ +/** + * @file stellar_context.h + * @author Cypherock X1 Team + * @brief Header file defining typedefs and MACROS for the STELLAR app + * @copyright Copyright (c) 2025 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + */ +#ifndef STELLAR_CONTEXT_H +#define STELLAR_CONTEXT_H + +/***************************************************************************** + * INCLUDES + *****************************************************************************/ +#include +#include + +/***************************************************************************** + * MACROS AND DEFINES + *****************************************************************************/ + +// Stellar network constants +#define STELLAR_NAME "Stellar" +#define STELLAR_LUNIT "XLM" + +// Key and address sizes +#define STELLAR_PUB_KEY_SIZE 33 +#define STELLAR_PUBKEY_RAW_SIZE 32 +#define STELLAR_PRIVATE_KEY_SIZE 32 +#define STELLAR_ADDRESS_LENGTH 57 +#define STELLAR_SECRET_KEY_LENGTH 57 +#define STELLAR_SIGNATURE_SIZE 64 + +// Network passphrases +#define TESTNET_PASSPHRASE "Test SDF Network ; September 2015" +#define MAINNET_PASSPHRASE "Public Global Stellar Network ; September 2015" + +/***************************************************************************** + * TYPEDEFS + *****************************************************************************/ + +// TODO: Populate structure for STELLAR +typedef struct { +} stellar_config_t; + +// Stellar Memo types +typedef enum { + MEMO_NONE = 0, + MEMO_TEXT = 1, + MEMO_ID = 2, + MEMO_HASH = 3, + MEMO_RETURN = 4 +} stellar_memo_type_t; + +// Stellar transaction structures +typedef struct { + uint8_t source_account[32]; + uint64_t sequence_number; + uint32_t fee; + uint32_t operation_count; + uint32_t operation_type; // CREATE_ACCOUNT = 0, PAYMENT = 1 + stellar_memo_type_t memo_type; + union { + char text[29]; // MEMO_TEXT (max 28 bytes + 1 byte delimiter) + uint64_t id; // MEMO_ID + uint8_t hash[32]; // MEMO_HASH or MEMO_RETURN(32 bytes) + } memo; +} stellar_transaction_t; + +typedef struct { + uint8_t destination[32]; + uint64_t amount; +} stellar_payment_t; + +typedef enum { + STELLAR_NETWORK_MAINNET = 0, + STELLAR_NETWORK_TESTNET = 1 +} stellar_network_t; + + +/***************************************************************************** + * EXPORTED VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTION PROTOTYPES + *****************************************************************************/ +/** + * @brief Generates a Stellar address from a public key + * @details Follows the Stellar address generation algorithm: + * 1. Creates a payload with account ID type (0x30) and the public key + * 2. Calculates CRC16 checksum + * 3. Encodes the result using base32 + * + * @param public_key The 32-byte ED25519 public key + * @param address Buffer to store the resulting address (must be at least STELLAR_ADDRESS_LENGTH bytes) + * @return true if the address was generated successfully, false otherwise + */ +bool stellar_generate_address(const uint8_t *public_key, char *address); + +#endif /* STELLAR_CONTEXT_H */ \ No newline at end of file diff --git a/apps/stellar_app/stellar_helpers.c b/apps/stellar_app/stellar_helpers.c new file mode 100644 index 000000000..f52e80b67 --- /dev/null +++ b/apps/stellar_app/stellar_helpers.c @@ -0,0 +1,110 @@ +/** + * @file stellar_helpers.c + * @author Cypherock X1 Team + * @brief Utilities specific to Stellar chains + * @copyright Copyright (c) 2024 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + * + ****************************************************************************** + * @attention + * + * (c) Copyright 2024 by HODL TECH PTE LTD + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject + * to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, + * as defined below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of + * rights under the License will not include, and the License does not + * grant to you, the right to Sell the Software. + * + * For purposes of the foregoing, "Sell" means practicing any or all + * of the rights granted to you under the License to provide to third + * parties, for a fee or other consideration (including without + * limitation fees for hosting or consulting/ support services related + * to the Software), a product or service whose value derives, entirely + * or substantially, from the functionality of the Software. Any license + * notice or attribution required by the License must also include + * this Commons Clause License Condition notice. + * + * Software: All X1Wallet associated files. + * License: MIT + * Licensor: HODL TECH PTE LTD + * + ****************************************************************************** + */ + +/***************************************************************************** + * INCLUDES + *****************************************************************************/ + +#include "stellar_helpers.h" + +/***************************************************************************** + * EXTERN VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTION PROTOTYPES + *****************************************************************************/ + +/***************************************************************************** + * STATIC VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTIONS + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTIONS + *****************************************************************************/ + +bool stellar_derivation_path_guard(const uint32_t *path, uint8_t levels) { + bool status = false; + if (levels != STELLAR_IMPLICIT_ACCOUNT_DEPTH) { + return status; + } + + uint32_t purpose = path[0], coin = path[1], account = path[2]; + + // m/44'/148'/n' - support any hardened account index + status = (STELLAR_PURPOSE_INDEX == purpose && STELLAR_COIN_INDEX == coin && + is_hardened(account)); + + return status; +} diff --git a/apps/stellar_app/stellar_helpers.h b/apps/stellar_app/stellar_helpers.h new file mode 100644 index 000000000..2a27811a7 --- /dev/null +++ b/apps/stellar_app/stellar_helpers.h @@ -0,0 +1,60 @@ +/** + * @file stellar_helpers.h + * @author Cypherock X1 Team + * @brief Utilities api definitions for Stellar chains + * @copyright Copyright (c) 2025 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + */ +#ifndef STELLAR_HELPERS_H +#define STELLAR_HELPERS_H + +/***************************************************************************** + * INCLUDES + *****************************************************************************/ + +#include +#include +#include + +#include "coin_utils.h" + +/***************************************************************************** + * MACROS AND DEFINES + *****************************************************************************/ + +#define STELLAR_IMPLICIT_ACCOUNT_DEPTH 3 + +#define STELLAR_PURPOSE_INDEX 0x8000002C // 44' +#define STELLAR_COIN_INDEX 0x80000094 // 148' +#define STELLAR_ACCOUNT_INDEX 0x80000000 // 0' + +/***************************************************************************** + * TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * EXPORTED VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTION PROTOTYPES + *****************************************************************************/ + +/** + * @brief Verifies the derivation path. + * @details The derivation depth is fixed at level 3. So if the depth level != + * 3, then this function return false indicating invalid derivation path. The + * function supports checking derivation paths for HD wallets Types of + * derivations: address: m/44'/148'/0' + * + * @param[in] path The derivation path as an uint32 array + * @param[in] levels The number of levels in the derivation path + * + * @return bool Indicates if the provided derivation path is valid + * @retval true if the derivation path is valid + * @retval false otherwise + */ +bool stellar_derivation_path_guard(const uint32_t *path, uint8_t levels); + +#endif // STELLAR_HELPERS_H \ No newline at end of file From 8aec6df9b8bf314051be3f5d60c17216887ab78b Mon Sep 17 00:00:00 2001 From: Keyur279 Date: Wed, 9 Jul 2025 03:00:11 +0530 Subject: [PATCH 06/23] feat: Add stellar key handling files --- apps/stellar_app/stellar_priv.h | 67 ++++ apps/stellar_app/stellar_pub_key.c | 483 +++++++++++++++++++++++++++++ 2 files changed, 550 insertions(+) create mode 100644 apps/stellar_app/stellar_priv.h create mode 100644 apps/stellar_app/stellar_pub_key.c diff --git a/apps/stellar_app/stellar_priv.h b/apps/stellar_app/stellar_priv.h new file mode 100644 index 000000000..e4cf1cf00 --- /dev/null +++ b/apps/stellar_app/stellar_priv.h @@ -0,0 +1,67 @@ +/** + * @file stellar_priv.h + * @author Cypherock X1 Team + * @brief Support for stellar app internal operations + * This file is defined to separate Stellar's internal use + * functions, flows, common APIs + * @copyright Copyright (c) 2025 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + */ +#ifndef STELLAR_PRIV_H +#define STELLAR_PRIV_H +/***************************************************************************** + * INCLUDES + *****************************************************************************/ +#include +#include + +#include "stellar_context.h" + +/***************************************************************************** + * TYPEDEFS + *****************************************************************************/ +typedef struct { + /** + * The structure holds the wallet information of the transaction. + * @note Populated by stellar_handle_initiate_query() + */ + stellar_sign_txn_initiate_request_t init_info; + + // remembers the allocated buffer for holding complete unsigned transaction + uint8_t *transaction; + + // decoded transaction structures + stellar_transaction_t *txn; + stellar_payment_t *payment; + +} stellar_txn_context_t; + +/***************************************************************************** + * EXPORTED VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTION PROTOTYPES + *****************************************************************************/ + +/** + * @brief Handler for Stellar public key derivation. + * @details This flow expects STELLAR_GET_PUBLIC_KEY_REQUEST_INITIATE_TAG as initial + * query, otherwise the flow is aborted + * + * @param query object for address public key query + */ +void stellar_get_pub_keys(stellar_query_t *query); + +/** + * @brief Handler for signing a transaction on stellar. + * @details The expected request type is STELLAR_SIGN_TXN_REQUEST_INITIATE_TAG. The + * function controls the complete data exchange with host, user prompts and + * confirmations for signing an STELLAR based transaction. + * + * @param query Reference to the decoded query struct from the host app + */ +void stellar_sign_transaction(stellar_query_t *query); + +#endif /* STELLAR_PRIV_H */ \ No newline at end of file diff --git a/apps/stellar_app/stellar_pub_key.c b/apps/stellar_app/stellar_pub_key.c new file mode 100644 index 000000000..5e5c8c26d --- /dev/null +++ b/apps/stellar_app/stellar_pub_key.c @@ -0,0 +1,483 @@ +/** + * @file stellar_pub_key.c + * @author Cypherock X1 Team + * @brief Generates public key for Stellar derivations. + * @copyright Copyright (c) 2025 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + * + ****************************************************************************** + * @attention + * + * (c) Copyright 2025 by HODL TECH PTE LTD + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject + * to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, + * as defined below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of + * rights under the License will not include, and the License does not + * grant to you, the right to Sell the Software. + * + * For purposes of the foregoing, "Sell" means practicing any or all + * of the rights granted to you under the License to provide to third + * parties, for a fee or other consideration (including without + * limitation fees for hosting or consulting/ support services related + * to the Software), a product or service whose value derives, entirely + * or substantially, from the functionality of the Software. Any license + * notice or attribution required by the License must also include + * this Commons Clause License Condition notice. + * + * Software: All X1Wallet associated files. + * License: MIT + * Licensor: HODL TECH PTE LTD + * + ****************************************************************************** + */ + +/***************************************************************************** + * INCLUDES + *****************************************************************************/ + +#include "bip32.h" +#include "base32.h" +#include "composable_app_queue.h" +#include "curves.h" +#include "exchange_main.h" +#include "hasher.h" +#include "reconstruct_wallet_flow.h" +#include "status_api.h" +#include "ui_core_confirm.h" +#include "ui_screens.h" +#include "wallet_list.h" +#include "stellar_api.h" +#include "stellar_context.h" +#include "stellar_helpers.h" +#include "stellar_priv.h" + +/***************************************************************************** + * EXTERN VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTION PROTOTYPES + *****************************************************************************/ + +/** + * @brief Checks if the provided query contains expected request. + * @details The function performs the check on the request type and if the check + * fails, then it will send an error to the host bitcoin app and return false. + * + * @param query Reference to an instance of stellar_query_t containing query + * received from host app + * @param which_request The expected request type enum + * + * @return bool Indicating if the check succeeded or failed + * @retval true If the query contains the expected request + * @retval false If the query does not contain the expected request + */ +static bool check_which_request(const stellar_query_t *query, + pb_size_t which_request); + +/** + * @brief Validates the derivation paths received in the request from host + * @details The function validates each path index in the request. If any + * invalid index is detected, the function will send an error to the host and + * return false. + * + * @param req Reference to an instance of stellar_get_public_keys_intiate_request_t + * @param which_request The type of request received from the host. + * @return bool Indicating if the verification passed or failed + * @retval true If the derivation path entries are valid + * @retval false If any of the derivation path entries are invalid + */ +static bool validate_request(const stellar_get_public_keys_intiate_request_t *req, + const pb_size_t which_request); + +/** + * @brief Fills the list of public keys corresponding to the provided list of + * derivation paths in the buffer + * @details The function expects the size of list for derivation paths and + * location for storing derived public keys to be a match with provided count. + * + * @param path Reference to the list of stellar_get_public_keys_derivation_path_t + * @param seed Reference to a const array containing the seed + * @param public_key Reference to the location to store all the public keys to + * be derived + * @param count Number of derivation paths in the list and consequently, + * sufficient space in memory for storing derived public keys. + * + * @retval true If all the requested public keys were derived successfully + * @retval false If there is any issue occurred during the key derivation + */ +static bool fill_public_keys(const stellar_get_public_keys_derivation_path_t *path, + const uint8_t *seed, + uint8_t public_key_list[][STELLAR_PUBKEY_RAW_SIZE], + pb_size_t count); + +/** + * @brief The function sends public keys for the requested batch + * @details The function determines the batch size from the static struct + * member declaration of nanopb options. The function batches the result based + * on the definition and sends the result. The function expects that the entire + * list of public keys requested is already derived and provided to this + * function as pubkey_list. The function will return false if either the query + * was wrong or a P0 event is occurred. In case of wrong query, the function + * also sends an error to the host app. + * + * @param query Reference to an instance of stellar_query_t + * @param pubkey_list Reference to list of derived public key to be sent to the + * host + * @param count Number of public keys entries in the list of public keys + * @param which_request The type of request to be expected from the host + * @param which_response The type of response to be sent to the host + * + * @return bool Indicating if the public keys was exported completely to the + * host + * @retval true If all the requested public keys were exported to the host app + * @retval false If the export was interrupted by a P0 event or an invalid query + * was received from the host app. + */ +static bool send_public_keys(stellar_query_t *query, + const uint8_t pubkey_list[][STELLAR_PUBKEY_RAW_SIZE], + const pb_size_t count, + const pb_size_t which_request, + const pb_size_t which_response); + +/** + * @details The function provides an ED25519 public key for Stellar. It accepts + * NULL for output parameter and handles accordingly. The function also manages + * all the terminal errors during derivation/encoding, in which case it will + * return false and send a relevant error to the host closing the + * request-response pair All the errors/invalid cases are conveyed to the host + * as unknown_error = 1 because we expect the data validation was success. + * TODO: Make this a common utility function + * + * @param seed Reference to the wallet seed generated + * @param path Derivation path of the node to be derived + * @param path_length Expected length of the provided derivation path + * @param public_key Storage location for raw uncompressed public key + * + * @retval false If derivation failed + */ +static bool get_public_key(const uint8_t *seed, + const uint32_t *path, + uint32_t path_length, + uint8_t *public_key); + +/** + * @brief Helper function to take user consent before exporting public keys to + * the host. Uses an appropriate message template based on the query request + * received from the host. + * + * @param which_request The type of request received from host + * @param wallet_name The name of the wallet on which the request needs to be + * performed + * @return true If the user accepted the request + * @return false If the user rejected or any P0 event occurred during the + * confirmation. + */ +static bool get_user_consent(const pb_size_t which_request, + const char *wallet_name); + +/***************************************************************************** + * STATIC VARIABLES + *****************************************************************************/ +static bool sign_address = false; + + static bool check_which_request(const stellar_query_t *query, + pb_size_t which_request) { + if (which_request != query->get_public_keys.which_request) { + stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_REQUEST); + return false; + } + + return true; +} + +static bool validate_request(const stellar_get_public_keys_intiate_request_t *req, + const pb_size_t which_request) { + bool status = true; + const pb_size_t count = req->derivation_paths_count; + + if (0 == count) { + // request does not have any derivation paths, invalid request + stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_DATA); + status = false; + } + + if (STELLAR_QUERY_GET_USER_VERIFIED_PUBLIC_KEY_TAG == which_request && + 1 < count) { + // `STELLAR_QUERY_GET_USER_VERIFIED_PUBLIC_KEY_TAG` request contains more than + // one derivation path which is not expected + stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_DATA); + status = false; + } + + const stellar_get_public_keys_derivation_path_t *path = NULL; + + for (pb_size_t index = 0; index < count; index++) { + path = &req->derivation_paths[index]; + if (!stellar_derivation_path_guard(path->path, path->path_count)) { + stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_DATA); + status = false; + break; + } + } + + caq_node_data_t data = {.applet_id = get_applet_id()}; + + memzero(data.params, sizeof(data.params)); + memcpy(data.params, req->wallet_id, sizeof(req->wallet_id)); + data.params[32] = EXCHANGE_FLOW_TAG_RECEIVE; + + sign_address = exchange_app_validate_caq(data); + + return status; +} + +static bool get_user_consent(const pb_size_t which_request, + const char *wallet_name) { + char msg[100] = ""; + + if (STELLAR_QUERY_GET_PUBLIC_KEYS_TAG == which_request) { + snprintf( + msg, sizeof(msg), UI_TEXT_ADD_ACCOUNT_PROMPT, STELLAR_NAME, wallet_name); + } else { + snprintf(msg, sizeof(msg), UI_TEXT_RECEIVE_PROMPT, STELLAR_NAME, wallet_name); + } + + return core_scroll_page(NULL, msg, stellar_send_error); +} + +static bool get_public_key(const uint8_t *seed, + const uint32_t *path, + uint32_t path_length, + uint8_t *public_key) { + HDNode node = {0}; + + // Initialize the HDNode with the seed + if (hdnode_from_seed(seed, 64, "ed25519", &node) != 1) { + stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 1); + memzero(&node, sizeof(HDNode)); + return false; + } + + // Derive HDNode from path + for (uint32_t i = 0; i < path_length; i++) { + if (hdnode_private_ckd(&node, path[i]) != 1) { + stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 1); + memzero(&node, sizeof(HDNode)); + return false; + } + } + + hdnode_fill_public_key(&node); + + if (NULL != public_key) { + //skip first byte, use raw 32 bytes + memcpy(public_key, node.public_key + 1, STELLAR_PUBKEY_RAW_SIZE); + } + + memzero(&node, sizeof(HDNode)); + return true; +} + +static bool fill_public_keys(const stellar_get_public_keys_derivation_path_t *path, + const uint8_t *seed, + uint8_t public_key_list[][STELLAR_PUBKEY_RAW_SIZE], + pb_size_t count) { + for (pb_size_t index = 0; index < count; index++) { + const stellar_get_public_keys_derivation_path_t *current = &path[index]; + if (!get_public_key( + seed, current->path, current->path_count, public_key_list[index])) { + return false; + } + } + + return true; +} + +static uint16_t crc16(const uint8_t *data, size_t len) { + uint16_t crc = 0x0000; + for (size_t i = 0; i < len; i++) { + crc ^= data[i] << 8; + for (int j = 0; j < 8; j++) { + if (crc & 0x8000) crc = (crc << 1) ^ 0x1021; + else crc <<= 1; + } + } + return crc; +} + +static bool send_public_keys(stellar_query_t *query, + const uint8_t pubkey_list[][STELLAR_PUBKEY_RAW_SIZE], + const pb_size_t count, + const pb_size_t which_request, + const pb_size_t which_response) { + stellar_result_t response = init_stellar_result(which_response); + stellar_get_public_keys_result_response_t *result = + &response.get_public_keys.result; + size_t batch_limit = + sizeof(response.get_public_keys.result.public_keys) / STELLAR_PUBKEY_RAW_SIZE; + size_t remaining = count; + + response.get_public_keys.which_response = + STELLAR_GET_PUBLIC_KEYS_RESPONSE_RESULT_TAG; + while (true) { + // send response as batched list of public keys + size_t batch_size = CY_MIN(batch_limit, remaining); + result->public_keys_count = batch_size; + + memcpy(result->public_keys, + &pubkey_list[count - remaining], + batch_size * STELLAR_PUBKEY_RAW_SIZE); + + stellar_send_result(&response); + remaining -= batch_size; + if (0 == remaining) { + break; + } + + if (!stellar_get_query(query, which_request) || + !check_which_request(query, + STELLAR_GET_PUBLIC_KEYS_REQUEST_FETCH_NEXT_TAG)) { + return false; + } + } + return true; +} + +/***************************************************************************** + * GLOBAL VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTIONS + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTIONS + *****************************************************************************/ + + bool stellar_generate_address(const uint8_t *public_key, char *address) { + + if(!public_key || !address) { + return false; + } + + uint8_t payload[35]; + payload[0] = 6 << 3; // Account ID type + memcpy(payload + 1, public_key, 32); + + uint16_t checksum = crc16(payload, 33); + payload[33] = checksum & 0xFF; + payload[34] = checksum >> 8; + + base32_encode(payload, 35, address, 57, BASE32_ALPHABET_RFC4648); + return true; +} + + + void stellar_get_pub_keys(stellar_query_t *query) { + char wallet_name[NAME_SIZE] = ""; + uint8_t seed[64] = {0}; + + // Validation & Setup + const pb_size_t which_request = query->which_request; + const stellar_get_public_keys_intiate_request_t *init_req = NULL; + pb_size_t which_response; + + // Determine request type and set response type + if (STELLAR_QUERY_GET_PUBLIC_KEYS_TAG == which_request) { + which_response = STELLAR_RESULT_GET_PUBLIC_KEYS_TAG; + init_req = &query->get_public_keys.initiate; + } else { + which_response = STELLAR_RESULT_GET_USER_VERIFIED_PUBLIC_KEY_TAG; + init_req = &query->get_user_verified_public_key.initiate; + } + + const pb_size_t count = init_req->derivation_paths_count; + uint8_t pubkey_list[sizeof(init_req->derivation_paths) / + sizeof(stellar_get_public_keys_derivation_path_t)] + [STELLAR_PUBKEY_RAW_SIZE] = {0}; + + if (!check_which_request(query, STELLAR_GET_PUBLIC_KEYS_REQUEST_INITIATE_TAG) || + !validate_request(init_req, which_request) || + !get_wallet_name_by_id(init_req->wallet_id, (uint8_t *)wallet_name, stellar_send_error)) { + return; + } + + //User consent + if (!get_user_consent(which_request, wallet_name)) { + return; + } + set_app_flow_status(STELLAR_GET_PUBLIC_KEYS_STATUS_CONFIRM); + + //Key derivation + if (!reconstruct_seed(init_req->wallet_id, &seed[0], stellar_send_error)) { + memzero(seed, sizeof(seed)); + return; + } + set_app_flow_status(STELLAR_GET_PUBLIC_KEYS_STATUS_SEED_GENERATED); + delay_scr_init(ui_text_processing, DELAY_SHORT); + + if (!fill_public_keys(init_req->derivation_paths, seed, pubkey_list, count)) { + memzero(seed, sizeof(seed)); + return; + } + + memzero(seed, sizeof(seed)); + + //Address verification (if single verified key) + if (STELLAR_QUERY_GET_USER_VERIFIED_PUBLIC_KEY_TAG == which_request) { + char address[STELLAR_ADDRESS_LENGTH] = ""; + if (!stellar_generate_address(pubkey_list[0], address)) { + return; + } + if (!core_scroll_page(ui_text_receive_on, address, stellar_send_error)) { + return; + } + set_app_flow_status(STELLAR_GET_PUBLIC_KEYS_STATUS_VERIFY); + } + + //Send results + if (!send_public_keys(query, pubkey_list, count, which_request, which_response)) { + return; + } + + delay_scr_init(ui_text_check_cysync_app, DELAY_TIME); +} From b5e76427fa3366dd830aec1385e56b5845f5c565 Mon Sep 17 00:00:00 2001 From: Keyur279 Date: Wed, 9 Jul 2025 03:01:21 +0530 Subject: [PATCH 07/23] feat: Registered stellar app --- apps/stellar_app/stellar_api.c | 22 +- apps/stellar_app/stellar_api.h | 10 +- apps/stellar_app/stellar_context.h | 46 +- apps/stellar_app/stellar_helpers.c | 2 +- apps/stellar_app/stellar_main.c | 18 +- apps/stellar_app/stellar_priv.h | 9 +- apps/stellar_app/stellar_pub_key.c | 175 ++--- apps/stellar_app/stellar_txn.c | 922 +++++++++++++++++++++++++ apps/stellar_app/stellar_txn_helpers.c | 305 ++++++++ apps/stellar_app/stellar_txn_helpers.h | 58 ++ common/core/core_flow_init.c | 2 +- 11 files changed, 1435 insertions(+), 134 deletions(-) create mode 100644 apps/stellar_app/stellar_txn.c create mode 100644 apps/stellar_app/stellar_txn_helpers.c create mode 100644 apps/stellar_app/stellar_txn_helpers.h diff --git a/apps/stellar_app/stellar_api.c b/apps/stellar_app/stellar_api.c index d167d9283..4931b3f25 100644 --- a/apps/stellar_app/stellar_api.c +++ b/apps/stellar_app/stellar_api.c @@ -101,11 +101,11 @@ * GLOBAL FUNCTIONS *****************************************************************************/ bool decode_stellar_query(const uint8_t *data, - uint16_t data_size, - stellar_query_t *query_out) { + uint16_t data_size, + stellar_query_t *query_out) { if (NULL == data || NULL == query_out || 0 == data_size) { stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, - ERROR_DATA_FLOW_DECODING_FAILED); + ERROR_DATA_FLOW_DECODING_FAILED); return false; } @@ -121,16 +121,16 @@ bool decode_stellar_query(const uint8_t *data, /* Send error to host if status is false*/ if (false == status) { stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, - ERROR_DATA_FLOW_DECODING_FAILED); + ERROR_DATA_FLOW_DECODING_FAILED); } return status; } bool encode_stellar_result(const stellar_result_t *result, - uint8_t *buffer, - uint16_t max_buffer_len, - size_t *bytes_written_out) { + uint8_t *buffer, + uint16_t max_buffer_len, + size_t *bytes_written_out) { if (NULL == result || NULL == buffer || NULL == bytes_written_out) return false; @@ -147,10 +147,11 @@ bool encode_stellar_result(const stellar_result_t *result, return status; } -bool check_stellar_query(const stellar_query_t *query, pb_size_t exp_query_tag) { +bool check_stellar_query(const stellar_query_t *query, + pb_size_t exp_query_tag) { if ((NULL == query) || (exp_query_tag != query->which_request)) { stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, - ERROR_DATA_FLOW_INVALID_QUERY); + ERROR_DATA_FLOW_INVALID_QUERY); return false; } return true; @@ -163,7 +164,8 @@ stellar_result_t init_stellar_result(pb_size_t result_tag) { } void stellar_send_error(pb_size_t which_error, uint32_t error_code) { - stellar_result_t result = init_stellar_result(STELLAR_RESULT_COMMON_ERROR_TAG); + stellar_result_t result = + init_stellar_result(STELLAR_RESULT_COMMON_ERROR_TAG); result.common_error = init_common_error(which_error, error_code); stellar_send_result(&result); } diff --git a/apps/stellar_app/stellar_api.h b/apps/stellar_app/stellar_api.h index 6d05e268d..fd4cde8d7 100644 --- a/apps/stellar_app/stellar_api.h +++ b/apps/stellar_app/stellar_api.h @@ -41,8 +41,8 @@ * @return bool True if decoding was successful, else false */ bool decode_stellar_query(const uint8_t *data, - uint16_t data_size, - stellar_query_t *query_out); + uint16_t data_size, + stellar_query_t *query_out); /** * @brief Encodes the STELLAR result with `STELLAR_RESULT_FIELDS` to byte-stream @@ -55,9 +55,9 @@ bool decode_stellar_query(const uint8_t *data, * @return bool True if decoding was successful, else false */ bool encode_stellar_result(const stellar_result_t *result, - uint8_t *buffer, - uint16_t max_buffer_len, - size_t *bytes_written_out); + uint8_t *buffer, + uint16_t max_buffer_len, + size_t *bytes_written_out); /** * @brief This API checks if the `which_request` field of the query of type diff --git a/apps/stellar_app/stellar_context.h b/apps/stellar_app/stellar_context.h index 3ce6e74a8..88d423268 100644 --- a/apps/stellar_app/stellar_context.h +++ b/apps/stellar_app/stellar_context.h @@ -45,39 +45,38 @@ typedef struct { // Stellar Memo types typedef enum { - MEMO_NONE = 0, - MEMO_TEXT = 1, - MEMO_ID = 2, - MEMO_HASH = 3, - MEMO_RETURN = 4 + MEMO_NONE = 0, + MEMO_TEXT = 1, + MEMO_ID = 2, + MEMO_HASH = 3, + MEMO_RETURN = 4 } stellar_memo_type_t; // Stellar transaction structures typedef struct { - uint8_t source_account[32]; - uint64_t sequence_number; - uint32_t fee; - uint32_t operation_count; - uint32_t operation_type; // CREATE_ACCOUNT = 0, PAYMENT = 1 - stellar_memo_type_t memo_type; - union { - char text[29]; // MEMO_TEXT (max 28 bytes + 1 byte delimiter) - uint64_t id; // MEMO_ID - uint8_t hash[32]; // MEMO_HASH or MEMO_RETURN(32 bytes) - } memo; + uint8_t source_account[32]; + uint64_t sequence_number; + uint32_t fee; + uint32_t operation_count; + uint32_t operation_type; // CREATE_ACCOUNT = 0, PAYMENT = 1 + stellar_memo_type_t memo_type; + union { + char text[29]; // MEMO_TEXT (max 28 bytes + 1 byte delimiter) + uint64_t id; // MEMO_ID + uint8_t hash[32]; // MEMO_HASH or MEMO_RETURN(32 bytes) + } memo; } stellar_transaction_t; typedef struct { - uint8_t destination[32]; - uint64_t amount; + uint8_t destination[32]; + uint64_t amount; } stellar_payment_t; typedef enum { - STELLAR_NETWORK_MAINNET = 0, - STELLAR_NETWORK_TESTNET = 1 + STELLAR_NETWORK_MAINNET = 0, + STELLAR_NETWORK_TESTNET = 1 } stellar_network_t; - /***************************************************************************** * EXPORTED VARIABLES *****************************************************************************/ @@ -91,9 +90,10 @@ typedef enum { * 1. Creates a payload with account ID type (0x30) and the public key * 2. Calculates CRC16 checksum * 3. Encodes the result using base32 - * + * * @param public_key The 32-byte ED25519 public key - * @param address Buffer to store the resulting address (must be at least STELLAR_ADDRESS_LENGTH bytes) + * @param address Buffer to store the resulting address (must be at least + * STELLAR_ADDRESS_LENGTH bytes) * @return true if the address was generated successfully, false otherwise */ bool stellar_generate_address(const uint8_t *public_key, char *address); diff --git a/apps/stellar_app/stellar_helpers.c b/apps/stellar_app/stellar_helpers.c index f52e80b67..369f511a9 100644 --- a/apps/stellar_app/stellar_helpers.c +++ b/apps/stellar_app/stellar_helpers.c @@ -101,7 +101,7 @@ bool stellar_derivation_path_guard(const uint32_t *path, uint8_t levels) { } uint32_t purpose = path[0], coin = path[1], account = path[2]; - + // m/44'/148'/n' - support any hardened account index status = (STELLAR_PURPOSE_INDEX == purpose && STELLAR_COIN_INDEX == coin && is_hardened(account)); diff --git a/apps/stellar_app/stellar_main.c b/apps/stellar_app/stellar_main.c index 7d79224ed..a3f4aee4e 100644 --- a/apps/stellar_app/stellar_main.c +++ b/apps/stellar_app/stellar_main.c @@ -99,14 +99,14 @@ void stellar_main(usb_event_t usb_evt, const void *stellar_app_config); *****************************************************************************/ static const cy_app_desc_t stellar_app_desc = {.id = 25, - .version = - { - .major = 1, - .minor = 0, - .patch = 0, - }, - .app = stellar_main, - .app_config = NULL}; + .version = + { + .major = 1, + .minor = 0, + .patch = 0, + }, + .app = stellar_main, + .app_config = NULL}; /***************************************************************************** * STATIC FUNCTIONS @@ -135,7 +135,7 @@ void stellar_main(usb_event_t usb_evt, const void *stellar_app_config) { default: { /* In case we ever encounter invalid query, convey to the host app */ stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, - ERROR_DATA_FLOW_INVALID_QUERY); + ERROR_DATA_FLOW_INVALID_QUERY); break; } } diff --git a/apps/stellar_app/stellar_priv.h b/apps/stellar_app/stellar_priv.h index e4cf1cf00..889fa8290 100644 --- a/apps/stellar_app/stellar_priv.h +++ b/apps/stellar_app/stellar_priv.h @@ -34,7 +34,6 @@ typedef struct { // decoded transaction structures stellar_transaction_t *txn; stellar_payment_t *payment; - } stellar_txn_context_t; /***************************************************************************** @@ -47,8 +46,8 @@ typedef struct { /** * @brief Handler for Stellar public key derivation. - * @details This flow expects STELLAR_GET_PUBLIC_KEY_REQUEST_INITIATE_TAG as initial - * query, otherwise the flow is aborted + * @details This flow expects STELLAR_GET_PUBLIC_KEY_REQUEST_INITIATE_TAG as + * initial query, otherwise the flow is aborted * * @param query object for address public key query */ @@ -56,8 +55,8 @@ void stellar_get_pub_keys(stellar_query_t *query); /** * @brief Handler for signing a transaction on stellar. - * @details The expected request type is STELLAR_SIGN_TXN_REQUEST_INITIATE_TAG. The - * function controls the complete data exchange with host, user prompts and + * @details The expected request type is STELLAR_SIGN_TXN_REQUEST_INITIATE_TAG. + * The function controls the complete data exchange with host, user prompts and * confirmations for signing an STELLAR based transaction. * * @param query Reference to the decoded query struct from the host app diff --git a/apps/stellar_app/stellar_pub_key.c b/apps/stellar_app/stellar_pub_key.c index 5e5c8c26d..44e529695 100644 --- a/apps/stellar_app/stellar_pub_key.c +++ b/apps/stellar_app/stellar_pub_key.c @@ -60,21 +60,21 @@ * INCLUDES *****************************************************************************/ -#include "bip32.h" #include "base32.h" +#include "bip32.h" #include "composable_app_queue.h" #include "curves.h" #include "exchange_main.h" #include "hasher.h" #include "reconstruct_wallet_flow.h" #include "status_api.h" -#include "ui_core_confirm.h" -#include "ui_screens.h" -#include "wallet_list.h" #include "stellar_api.h" #include "stellar_context.h" #include "stellar_helpers.h" #include "stellar_priv.h" +#include "ui_core_confirm.h" +#include "ui_screens.h" +#include "wallet_list.h" /***************************************************************************** * EXTERN VARIABLES @@ -114,14 +114,16 @@ static bool check_which_request(const stellar_query_t *query, * invalid index is detected, the function will send an error to the host and * return false. * - * @param req Reference to an instance of stellar_get_public_keys_intiate_request_t + * @param req Reference to an instance of + * stellar_get_public_keys_intiate_request_t * @param which_request The type of request received from the host. * @return bool Indicating if the verification passed or failed * @retval true If the derivation path entries are valid * @retval false If any of the derivation path entries are invalid */ -static bool validate_request(const stellar_get_public_keys_intiate_request_t *req, - const pb_size_t which_request); +static bool validate_request( + const stellar_get_public_keys_intiate_request_t *req, + const pb_size_t which_request); /** * @brief Fills the list of public keys corresponding to the provided list of @@ -129,7 +131,8 @@ static bool validate_request(const stellar_get_public_keys_intiate_request_t *re * @details The function expects the size of list for derivation paths and * location for storing derived public keys to be a match with provided count. * - * @param path Reference to the list of stellar_get_public_keys_derivation_path_t + * @param path Reference to the list of + * stellar_get_public_keys_derivation_path_t * @param seed Reference to a const array containing the seed * @param public_key Reference to the location to store all the public keys to * be derived @@ -139,10 +142,11 @@ static bool validate_request(const stellar_get_public_keys_intiate_request_t *re * @retval true If all the requested public keys were derived successfully * @retval false If there is any issue occurred during the key derivation */ -static bool fill_public_keys(const stellar_get_public_keys_derivation_path_t *path, - const uint8_t *seed, - uint8_t public_key_list[][STELLAR_PUBKEY_RAW_SIZE], - pb_size_t count); +static bool fill_public_keys( + const stellar_get_public_keys_derivation_path_t *path, + const uint8_t *seed, + uint8_t public_key_list[][STELLAR_PUBKEY_RAW_SIZE], + pb_size_t count); /** * @brief The function sends public keys for the requested batch @@ -167,11 +171,12 @@ static bool fill_public_keys(const stellar_get_public_keys_derivation_path_t *pa * @retval false If the export was interrupted by a P0 event or an invalid query * was received from the host app. */ -static bool send_public_keys(stellar_query_t *query, - const uint8_t pubkey_list[][STELLAR_PUBKEY_RAW_SIZE], - const pb_size_t count, - const pb_size_t which_request, - const pb_size_t which_response); +static bool send_public_keys( + stellar_query_t *query, + const uint8_t pubkey_list[][STELLAR_PUBKEY_RAW_SIZE], + const pb_size_t count, + const pb_size_t which_request, + const pb_size_t which_response); /** * @details The function provides an ED25519 public key for Stellar. It accepts @@ -214,35 +219,36 @@ static bool get_user_consent(const pb_size_t which_request, *****************************************************************************/ static bool sign_address = false; - static bool check_which_request(const stellar_query_t *query, +static bool check_which_request(const stellar_query_t *query, pb_size_t which_request) { if (which_request != query->get_public_keys.which_request) { stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, - ERROR_DATA_FLOW_INVALID_REQUEST); + ERROR_DATA_FLOW_INVALID_REQUEST); return false; } return true; } -static bool validate_request(const stellar_get_public_keys_intiate_request_t *req, - const pb_size_t which_request) { +static bool validate_request( + const stellar_get_public_keys_intiate_request_t *req, + const pb_size_t which_request) { bool status = true; const pb_size_t count = req->derivation_paths_count; if (0 == count) { // request does not have any derivation paths, invalid request stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, - ERROR_DATA_FLOW_INVALID_DATA); + ERROR_DATA_FLOW_INVALID_DATA); status = false; } if (STELLAR_QUERY_GET_USER_VERIFIED_PUBLIC_KEY_TAG == which_request && 1 < count) { - // `STELLAR_QUERY_GET_USER_VERIFIED_PUBLIC_KEY_TAG` request contains more than - // one derivation path which is not expected + // `STELLAR_QUERY_GET_USER_VERIFIED_PUBLIC_KEY_TAG` request contains more + // than one derivation path which is not expected stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, - ERROR_DATA_FLOW_INVALID_DATA); + ERROR_DATA_FLOW_INVALID_DATA); status = false; } @@ -252,7 +258,7 @@ static bool validate_request(const stellar_get_public_keys_intiate_request_t *re path = &req->derivation_paths[index]; if (!stellar_derivation_path_guard(path->path, path->path_count)) { stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, - ERROR_DATA_FLOW_INVALID_DATA); + ERROR_DATA_FLOW_INVALID_DATA); status = false; break; } @@ -274,10 +280,14 @@ static bool get_user_consent(const pb_size_t which_request, char msg[100] = ""; if (STELLAR_QUERY_GET_PUBLIC_KEYS_TAG == which_request) { - snprintf( - msg, sizeof(msg), UI_TEXT_ADD_ACCOUNT_PROMPT, STELLAR_NAME, wallet_name); + snprintf(msg, + sizeof(msg), + UI_TEXT_ADD_ACCOUNT_PROMPT, + STELLAR_NAME, + wallet_name); } else { - snprintf(msg, sizeof(msg), UI_TEXT_RECEIVE_PROMPT, STELLAR_NAME, wallet_name); + snprintf( + msg, sizeof(msg), UI_TEXT_RECEIVE_PROMPT, STELLAR_NAME, wallet_name); } return core_scroll_page(NULL, msg, stellar_send_error); @@ -288,7 +298,7 @@ static bool get_public_key(const uint8_t *seed, uint32_t path_length, uint8_t *public_key) { HDNode node = {0}; - + // Initialize the HDNode with the seed if (hdnode_from_seed(seed, 64, "ed25519", &node) != 1) { stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 1); @@ -308,7 +318,7 @@ static bool get_public_key(const uint8_t *seed, hdnode_fill_public_key(&node); if (NULL != public_key) { - //skip first byte, use raw 32 bytes + // skip first byte, use raw 32 bytes memcpy(public_key, node.public_key + 1, STELLAR_PUBKEY_RAW_SIZE); } @@ -316,10 +326,11 @@ static bool get_public_key(const uint8_t *seed, return true; } -static bool fill_public_keys(const stellar_get_public_keys_derivation_path_t *path, - const uint8_t *seed, - uint8_t public_key_list[][STELLAR_PUBKEY_RAW_SIZE], - pb_size_t count) { +static bool fill_public_keys( + const stellar_get_public_keys_derivation_path_t *path, + const uint8_t *seed, + uint8_t public_key_list[][STELLAR_PUBKEY_RAW_SIZE], + pb_size_t count) { for (pb_size_t index = 0; index < count; index++) { const stellar_get_public_keys_derivation_path_t *current = &path[index]; if (!get_public_key( @@ -332,27 +343,30 @@ static bool fill_public_keys(const stellar_get_public_keys_derivation_path_t *pa } static uint16_t crc16(const uint8_t *data, size_t len) { - uint16_t crc = 0x0000; - for (size_t i = 0; i < len; i++) { - crc ^= data[i] << 8; - for (int j = 0; j < 8; j++) { - if (crc & 0x8000) crc = (crc << 1) ^ 0x1021; - else crc <<= 1; - } + uint16_t crc = 0x0000; + for (size_t i = 0; i < len; i++) { + crc ^= data[i] << 8; + for (int j = 0; j < 8; j++) { + if (crc & 0x8000) + crc = (crc << 1) ^ 0x1021; + else + crc <<= 1; } - return crc; + } + return crc; } -static bool send_public_keys(stellar_query_t *query, - const uint8_t pubkey_list[][STELLAR_PUBKEY_RAW_SIZE], - const pb_size_t count, - const pb_size_t which_request, - const pb_size_t which_response) { +static bool send_public_keys( + stellar_query_t *query, + const uint8_t pubkey_list[][STELLAR_PUBKEY_RAW_SIZE], + const pb_size_t count, + const pb_size_t which_request, + const pb_size_t which_response) { stellar_result_t response = init_stellar_result(which_response); stellar_get_public_keys_result_response_t *result = &response.get_public_keys.result; - size_t batch_limit = - sizeof(response.get_public_keys.result.public_keys) / STELLAR_PUBKEY_RAW_SIZE; + size_t batch_limit = sizeof(response.get_public_keys.result.public_keys) / + STELLAR_PUBKEY_RAW_SIZE; size_t remaining = count; response.get_public_keys.which_response = @@ -393,29 +407,27 @@ static bool send_public_keys(stellar_query_t *query, * GLOBAL FUNCTIONS *****************************************************************************/ - bool stellar_generate_address(const uint8_t *public_key, char *address) { - - if(!public_key || !address) { - return false; - } - - uint8_t payload[35]; - payload[0] = 6 << 3; // Account ID type - memcpy(payload + 1, public_key, 32); - - uint16_t checksum = crc16(payload, 33); - payload[33] = checksum & 0xFF; - payload[34] = checksum >> 8; - - base32_encode(payload, 35, address, 57, BASE32_ALPHABET_RFC4648); - return true; -} +bool stellar_generate_address(const uint8_t *public_key, char *address) { + if (!public_key || !address) { + return false; + } + uint8_t payload[35]; + payload[0] = 6 << 3; // Account ID type + memcpy(payload + 1, public_key, 32); - void stellar_get_pub_keys(stellar_query_t *query) { + uint16_t checksum = crc16(payload, 33); + payload[33] = checksum & 0xFF; + payload[34] = checksum >> 8; + + base32_encode(payload, 35, address, 57, BASE32_ALPHABET_RFC4648); + return true; +} + +void stellar_get_pub_keys(stellar_query_t *query) { char wallet_name[NAME_SIZE] = ""; uint8_t seed[64] = {0}; - + // Validation & Setup const pb_size_t which_request = query->which_request; const stellar_get_public_keys_intiate_request_t *init_req = NULL; @@ -432,27 +444,29 @@ static bool send_public_keys(stellar_query_t *query, const pb_size_t count = init_req->derivation_paths_count; uint8_t pubkey_list[sizeof(init_req->derivation_paths) / - sizeof(stellar_get_public_keys_derivation_path_t)] - [STELLAR_PUBKEY_RAW_SIZE] = {0}; + sizeof(stellar_get_public_keys_derivation_path_t)] + [STELLAR_PUBKEY_RAW_SIZE] = {0}; - if (!check_which_request(query, STELLAR_GET_PUBLIC_KEYS_REQUEST_INITIATE_TAG) || + if (!check_which_request(query, + STELLAR_GET_PUBLIC_KEYS_REQUEST_INITIATE_TAG) || !validate_request(init_req, which_request) || - !get_wallet_name_by_id(init_req->wallet_id, (uint8_t *)wallet_name, stellar_send_error)) { + !get_wallet_name_by_id( + init_req->wallet_id, (uint8_t *)wallet_name, stellar_send_error)) { return; } - //User consent + // User consent if (!get_user_consent(which_request, wallet_name)) { return; } set_app_flow_status(STELLAR_GET_PUBLIC_KEYS_STATUS_CONFIRM); - //Key derivation + // Key derivation if (!reconstruct_seed(init_req->wallet_id, &seed[0], stellar_send_error)) { memzero(seed, sizeof(seed)); return; } - set_app_flow_status(STELLAR_GET_PUBLIC_KEYS_STATUS_SEED_GENERATED); + set_app_flow_status(STELLAR_GET_PUBLIC_KEYS_STATUS_SEED_GENERATED); delay_scr_init(ui_text_processing, DELAY_SHORT); if (!fill_public_keys(init_req->derivation_paths, seed, pubkey_list, count)) { @@ -460,9 +474,9 @@ static bool send_public_keys(stellar_query_t *query, return; } - memzero(seed, sizeof(seed)); + memzero(seed, sizeof(seed)); - //Address verification (if single verified key) + // Address verification (if single verified key) if (STELLAR_QUERY_GET_USER_VERIFIED_PUBLIC_KEY_TAG == which_request) { char address[STELLAR_ADDRESS_LENGTH] = ""; if (!stellar_generate_address(pubkey_list[0], address)) { @@ -474,8 +488,9 @@ static bool send_public_keys(stellar_query_t *query, set_app_flow_status(STELLAR_GET_PUBLIC_KEYS_STATUS_VERIFY); } - //Send results - if (!send_public_keys(query, pubkey_list, count, which_request, which_response)) { + // Send results + if (!send_public_keys( + query, pubkey_list, count, which_request, which_response)) { return; } diff --git a/apps/stellar_app/stellar_txn.c b/apps/stellar_app/stellar_txn.c new file mode 100644 index 000000000..f866c4a25 --- /dev/null +++ b/apps/stellar_app/stellar_txn.c @@ -0,0 +1,922 @@ +/** + * @file stellar_txn.c + * @author Cypherock X1 Team + * @brief Source file to handle transaction signing logic for Stellar protocol + * + * @copyright Copyright (c) 2025 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + * + ****************************************************************************** + * @attention + * + * (c) Copyright 2025 by HODL TECH PTE LTD + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject + * to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, + * as defined below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of + * rights under the License will not include, and the License does not + * grant to you, the right to Sell the Software. + * + * For purposes of the foregoing, "Sell" means practicing any or all + * of the rights granted to you under the License to provide to third + * parties, for a fee or other consideration (including without + * limitation fees for hosting or consulting/ support services related + * to the Software), a product or service whose value derives, entirely + * or substantially, from the functionality of the Software. Any license + * notice or attribution required by the License must also include + * this Commons Clause License Condition notice. + * + * Software: All X1Wallet associated files. + * License: MIT + * Licensor: HODL TECH PTE LTD + * + ****************************************************************************** + */ + +/***************************************************************************** + * INCLUDES + *****************************************************************************/ + +#include + +#include "base32.h" +#include "bip32.h" +#include "composable_app_queue.h" +#include "curves.h" +#include "ed25519-donna/ed25519-donna.h" +#include "ed25519-donna/ed25519-hash-custom.h" +#include "exchange_main.h" +#include "hasher.h" +#include "reconstruct_wallet_flow.h" +#include "sha2.h" +#include "status_api.h" +#include "stellar_api.h" +#include "stellar_context.h" +#include "stellar_helpers.h" +#include "stellar_priv.h" +#include "stellar_txn_helpers.h" +#include "ui_core_confirm.h" +#include "ui_screens.h" +#include "wallet_list.h" + +/***************************************************************************** + * EXTERN VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE TYPEDEFS + *****************************************************************************/ +typedef stellar_sign_txn_signature_response_signature_t der_sig_t; + +/***************************************************************************** + * STATIC FUNCTION PROTOTYPES + *****************************************************************************/ + +/** + * @brief Checks if the provided query contains expected request. + * @details The function performs the check on the request type and if the check + * fails, then it will send an error to the host stellar app and return false. + * + * @param query Reference to an instance of stellar_query_t containing query + * received from host app + * @param which_request The expected request type enum + * + * @return bool Indicating if the check succeeded or failed + * @retval true If the query contains the expected request + * @retval false If the query does not contain the expected request + */ +static bool check_which_request(const stellar_query_t *query, + pb_size_t which_request); + +/** + * @brief The function prepares and sends empty responses + * + * @param which_response Constant value for the response type to be sent + */ +static void send_response(const pb_size_t which_response); + +/** + * @brief Validates the derivation path received in the request from host + * @details The function validates the provided account derivation path in the + * request. If invalid path is detected, the function will send an error to the + * host and return false. + * + * @param request Reference to an instance of stellar_sign_txn_request_t + * @return bool Indicating if the verification passed or failed + * @retval true If all the derivation path entries are valid + * @retval false If any of the derivation path entries are invalid + */ +static bool validate_request_data(const stellar_sign_txn_request_t *request); + +/** + * @brief Takes already received and decoded query for the user confirmation. + * @details The function will verify if the query contains the + * STELLAR_SIGN_TXN_REQUEST_INITIATE_TAG type of request. Additionally, the + * wallet-id is validated for sanity and the derivation path for the account is + * also validated. After the validations, user is prompted about the action for + * confirmation. The function returns true indicating all the validation and + * user confirmation was a success. The function also duplicates the data from + * query into the stellar_txn_context for further processing. + * + * @param query Constant reference to the decoded query received from the host + * + * @return bool Indicating if the function actions succeeded or failed + * @retval true If all the validation and user confirmation was positive + * @retval false If any of the validation or user confirmation was negative + */ +static bool handle_initiate_query(const stellar_query_t *query); + +/** + * @brief Receives unsigned txn from the host. If reception is successful, it + * also parses the txn to ensure it's validity. + * @note In case of any failure, a corresponding message is conveyed to the host + * + * @param query Reference to buffer of type stellar_query_t + * @return true If the txn is received in the internal buffers and is valid + * @return false If the txn could not be received or it's validation failed + */ +static bool fetch_valid_input(stellar_query_t *query); + +/** + * @brief This function executes user verification flow of the unsigned txn + * received from the host. + * @details The user verification flow is different for different type of action + * types identified from the unsigned txn + * @note This function expected that the unsigned txn is parsed using the helper + * function as only few action types are supported currently. + * + * @return true If the user accepted the transaction display + * @return false If any user rejection occured or P0 event occured + */ +static bool get_user_verification(void); + +/** + * @brief Calculates ED25519 curve based signature over the digest of the user + * verified unsigned txn. + * @details Seed reconstruction takes place within this function + * + * @param signature_buffer Reference to buffer where the signature will be + * populated + * @return true If the signature was computed successfully + * @return false If signature could not be computed - maybe due to some error + * during seed reconstruction phase + */ +static bool sign_txn(der_sig_t *der_signature); + +/** + * @brief Sends signature of the STELLAR unsigned txn to the host + * @details The function waits for the host to send a request of type + * STELLAR_SIGN_TXN_REQUEST_SIGNATURE_TAG and sends the response + * + * @param query Reference to buffer of type stellar_query_t + * @param signature Reference to signature to be sent to the host + * @return true If the signature was sent successfully + * @return false If the signature could not be sent - maybe due to and P0 event + * or invalid request received from the host + */ +static bool send_signature(stellar_query_t *query, + const der_sig_t *der_signature); + +/***************************************************************************** + * STATIC VARIABLES + *****************************************************************************/ +static stellar_txn_context_t *stellar_txn_context = NULL; +static bool use_signature_verification = false; +static const char base64_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static char envelope_b64[2048]; +static uint8_t signature_base[1024]; + +/***************************************************************************** + * GLOBAL VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTIONS + *****************************************************************************/ + +static bool check_which_request(const stellar_query_t *query, + pb_size_t which_request) { + if (which_request != query->sign_txn.which_request) { + stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_REQUEST); + return false; + } + + return true; +} + +static void send_response(const pb_size_t which_response) { + stellar_result_t result = init_stellar_result(STELLAR_RESULT_SIGN_TXN_TAG); + result.sign_txn.which_response = which_response; + stellar_send_result(&result); +} + +static bool validate_request_data(const stellar_sign_txn_request_t *request) { + bool status = true; + + if (!stellar_derivation_path_guard(request->initiate.derivation_path, + request->initiate.derivation_path_count)) { + stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_DATA); + status = false; + } + + caq_node_data_t data = {.applet_id = get_applet_id()}; + + memzero(data.params, sizeof(data.params)); + memcpy(data.params, + request->initiate.wallet_id, + sizeof(request->initiate.wallet_id)); + data.params[32] = EXCHANGE_FLOW_TAG_SEND; + + use_signature_verification = exchange_app_validate_caq(data); + return status; +} + +static bool handle_initiate_query(const stellar_query_t *query) { + char wallet_name[NAME_SIZE] = ""; + char msg[100] = ""; + + // TODO: Handle wallet search failures - eg: Wallet ID not found, Wallet + // ID found but is invalid/locked wallet + if (!check_which_request(query, STELLAR_SIGN_TXN_REQUEST_INITIATE_TAG) || + !validate_request_data(&query->sign_txn) || + !get_wallet_name_by_id(query->sign_txn.initiate.wallet_id, + (uint8_t *)wallet_name, + stellar_send_error)) { + return false; + } + + snprintf( + msg, sizeof(msg), UI_TEXT_SIGN_TXN_PROMPT, STELLAR_NAME, wallet_name); + // Take user consent to sign transaction for the wallet + if (!core_confirmation(msg, stellar_send_error)) { + return false; + } + + set_app_flow_status(STELLAR_SIGN_TXN_STATUS_CONFIRM); + memcpy(&stellar_txn_context->init_info, + &query->sign_txn.initiate, + sizeof(stellar_sign_txn_initiate_request_t)); + + send_response(STELLAR_SIGN_TXN_RESPONSE_CONFIRMATION_TAG); + // show processing screen for a minimum duration (additional time will add due + // to actual processing) + delay_scr_init(ui_text_processing, DELAY_SHORT); + return true; +} + +static bool fetch_valid_input(stellar_query_t *query) { + uint32_t size = 0; + stellar_result_t response = init_stellar_result(STELLAR_RESULT_SIGN_TXN_TAG); + uint32_t total_size = stellar_txn_context->init_info.transaction_size; + const stellar_sign_txn_data_t *txn_data = &query->sign_txn.txn_data; + const common_chunk_payload_t *payload = &txn_data->chunk_payload; + const common_chunk_payload_chunk_t *chunk = &txn_data->chunk_payload.chunk; + + // allocate memory for storing transaction + stellar_txn_context->transaction = (uint8_t *)malloc(total_size); + + while (1) { + if (!stellar_get_query(query, STELLAR_QUERY_SIGN_TXN_TAG) || + !check_which_request(query, STELLAR_SIGN_TXN_REQUEST_TXN_DATA_TAG)) { + return false; + } + + if (!txn_data->has_chunk_payload || + payload->chunk_index >= payload->total_chunks || + size + payload->chunk.size > total_size) { + stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_DATA); + return false; + } + + memcpy(&stellar_txn_context->transaction[size], chunk->bytes, chunk->size); + size += chunk->size; + + // Send chunk ack to host + response.sign_txn.which_response = + STELLAR_SIGN_TXN_RESPONSE_DATA_ACCEPTED_TAG; + response.sign_txn.data_accepted.has_chunk_ack = true; + response.sign_txn.data_accepted.chunk_ack.chunk_index = + payload->chunk_index; + stellar_send_result(&response); + + if (0 == payload->remaining_size || + payload->chunk_index + 1 == payload->total_chunks) { + break; + } + } + + // make sure all chunks were received + if (size != total_size) { + stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_DATA); + return false; + } + + stellar_txn_context->txn = + (stellar_transaction_t *)malloc(sizeof(stellar_transaction_t)); + stellar_txn_context->payment = + (stellar_payment_t *)malloc(sizeof(stellar_payment_t)); + + if (stellar_parse_transaction(stellar_txn_context->transaction, + total_size, + stellar_txn_context->txn, + stellar_txn_context->payment) != 0) { + stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_DATA); + return false; + } + + return true; +} + +// Add this helper function before get_user_verification() +static bool show_memo_details(const stellar_transaction_t *decoded_txn) { + if (decoded_txn->memo_type == MEMO_TEXT) { + char memo_display[100] = {'\0'}; + snprintf(memo_display, + sizeof(memo_display), + "Memo: \"%s\"", + decoded_txn->memo.text); + return core_confirmation(memo_display, stellar_send_error); + } else if (decoded_txn->memo_type == MEMO_ID) { + char memo_display[50] = {'\0'}; + snprintf(memo_display, + sizeof(memo_display), + "Memo ID: %llu", + decoded_txn->memo.id); + return core_confirmation(memo_display, stellar_send_error); + } else if (decoded_txn->memo_type == MEMO_NONE) { + return core_confirmation("Memo: (none)", stellar_send_error); + } else if (decoded_txn->memo_type == MEMO_HASH || + decoded_txn->memo_type == MEMO_RETURN) { + char memo_hash[80] = {'\0'}; + char temp[3]; + strcpy(memo_hash, "Memo Hash: "); + for (int i = 0; i < 32; i++) { + snprintf(temp, sizeof(temp), "%02x", decoded_txn->memo.hash[i]); + strcat(memo_hash, temp); + } + return core_confirmation(memo_hash, stellar_send_error); + } else { + char memo_display[50] = {'\0'}; + snprintf(memo_display, + sizeof(memo_display), + "Memo: (unknown type %u)", + decoded_txn->memo_type); + return core_confirmation(memo_display, stellar_send_error); + } +} + +// Updated main function +static bool get_user_verification(void) { + const stellar_transaction_t *decoded_txn = stellar_txn_context->txn; + const stellar_payment_t *payment = stellar_txn_context->payment; + + char to_address[STELLAR_ADDRESS_LENGTH] = ""; + char from_address[STELLAR_ADDRESS_LENGTH] = ""; + + // Generate addresses for display + if (!stellar_generate_address(payment->destination, to_address)) { + stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 2); + return false; + } + + if (!stellar_generate_address(decoded_txn->source_account, from_address)) { + stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 2); + return false; + } + + // Exchange validation + if (use_signature_verification) { + if (!exchange_validate_stored_signature(to_address, sizeof(to_address))) { + return false; + } + } + + // Show operation type + char operation_display[50] = {'\0'}; + snprintf(operation_display, + sizeof(operation_display), + "Operation: %s", + decoded_txn->operation_type == 0 ? "CREATE_ACCOUNT" : "PAYMENT"); + if (!core_confirmation(operation_display, stellar_send_error)) { + return false; + } + + // Show from address + if (!core_scroll_page("From:", from_address, stellar_send_error)) { + return false; + } + + // Show destination address + if (!core_scroll_page( + ui_text_verify_address, to_address, stellar_send_error)) { + return false; + } + + // Show amount + char amount_string[30] = {'\0'}; + double decimal_amount = (double)payment->amount; + decimal_amount *= 1e-7; // Convert stroops to XLM + snprintf(amount_string, sizeof(amount_string), "%.7f", decimal_amount); + + char display[100] = {'\0'}; + snprintf(display, + sizeof(display), + UI_TEXT_VERIFY_AMOUNT, + amount_string, + STELLAR_LUNIT); + + if (!core_confirmation(display, stellar_send_error)) { + return false; + } + + // Show fee + char fee_display[50] = {'\0'}; + snprintf( + fee_display, sizeof(fee_display), "Fee: %lu stroops", decoded_txn->fee); + if (!core_confirmation(fee_display, stellar_send_error)) { + return false; + } + + // Show sequence number + char seq_display[50] = {'\0'}; + snprintf(seq_display, + sizeof(seq_display), + "Sequence: %llu", + decoded_txn->sequence_number); + if (!core_confirmation(seq_display, stellar_send_error)) { + return false; + } + + // Handle memo display + if (!show_memo_details(decoded_txn)) { + return false; + } + + set_app_flow_status(STELLAR_SIGN_TXN_STATUS_VERIFY); + return true; +} + +static void write_uint32_be(uint8_t *buffer, uint32_t value) { + buffer[0] = (value >> 24) & 0xFF; + buffer[1] = (value >> 16) & 0xFF; + buffer[2] = (value >> 8) & 0xFF; + buffer[3] = value & 0xFF; +} + +static void write_uint64_be(uint8_t *buffer, uint64_t value) { + write_uint32_be(buffer, (uint32_t)(value >> 32)); + write_uint32_be(buffer + 4, (uint32_t)(value & 0xFFFFFFFF)); +} + +static void base64_encode(const uint8_t *data, int len, char *output) { + int i = 0, j = 0; + uint8_t char_array_3[3]; + uint8_t char_array_4[4]; + + while (len--) { + char_array_3[i++] = *(data++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = + ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = + ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (i = 0; i < 4; i++) { + output[j++] = base64_chars[char_array_4[i]]; + } + i = 0; + } + } + + if (i) { + for (int k = i; k < 3; k++) { + char_array_3[k] = '\0'; + } + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = + ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = + ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (int k = 0; k < i + 1; k++) { + output[j++] = base64_chars[char_array_4[k]]; + } + + while (i++ < 3) { + output[j++] = '='; + } + } + + output[j] = '\0'; +} + +static int create_signature_base(const char *network_passphrase, + stellar_transaction_t *tx, + stellar_payment_t *payment, + uint8_t *signature_base, + int *base_len) { + int pos = 0; + + // 1. Network passphrase hash (32 bytes) + uint8_t network_hash[32]; + sha256_Raw((const uint8_t *)network_passphrase, + strlen(network_passphrase), + network_hash); + memcpy(signature_base + pos, network_hash, 32); + pos += 32; + + // 2. TX_TYPE (4 bytes) - ENVELOPE_TYPE_TX = 2 + write_uint32_be(signature_base + pos, 2); + pos += 4; + + // 3. Source account type (4 bytes) + public key (32 bytes) + write_uint32_be(signature_base + pos, 0); // KEY_TYPE_ED25519 = 0 + pos += 4; + memcpy(signature_base + pos, tx->source_account, 32); + pos += 32; + + // 4. Fee (4 bytes, big-endian) + write_uint32_be(signature_base + pos, tx->fee); + pos += 4; + + // 5. Sequence number (8 bytes, big-endian) + write_uint64_be(signature_base + pos, tx->sequence_number); + pos += 8; + + // 6. Time bounds (1 + 8 + 8 = 17 bytes) + write_uint32_be(signature_base + pos, 1); // has time bounds = true + pos += 4; + write_uint64_be(signature_base + pos, 0); // min_time = 0 + pos += 8; + write_uint64_be(signature_base + pos, 0); // max_time = 0 + pos += 8; + + // 7. Memo + write_uint32_be(signature_base + pos, tx->memo_type); + pos += 4; + + if (tx->memo_type == MEMO_TEXT) { + int memo_len = strlen(tx->memo.text); + write_uint32_be(signature_base + pos, memo_len); + pos += 4; + memcpy(signature_base + pos, tx->memo.text, memo_len); + pos += memo_len; + // Add padding to 4-byte boundary + int padding = (4 - (memo_len % 4)) % 4; + for (int i = 0; i < padding; i++) { + signature_base[pos++] = 0; + } + } else if (tx->memo_type == MEMO_ID) { + write_uint64_be(signature_base + pos, tx->memo.id); + pos += 8; + } else if (tx->memo_type == MEMO_HASH || tx->memo_type == MEMO_RETURN) { + memcpy(signature_base + pos, tx->memo.hash, 32); + pos += 32; + } + + // 8. Operations count (4 bytes) + write_uint32_be(signature_base + pos, 1); + pos += 4; + + // 9. Operation: has_source_account = false + write_uint32_be(signature_base + pos, 0); + pos += 4; + + // 10. Operation type = PAYMENT OR CREATE_ACCOUNT + write_uint32_be(signature_base + pos, tx->operation_type); + pos += 4; + + // 11. Payment operation + // Destination (type + pubkey) + write_uint32_be(signature_base + pos, 0); // KEY_TYPE_ED25519 + pos += 4; + memcpy(signature_base + pos, payment->destination, 32); + pos += 32; + // Asset (native) + if (tx->operation_type) { + write_uint32_be(signature_base + pos, 0); // ASSET_TYPE_NATIVE + pos += 4; + } + // Amount + write_uint64_be(signature_base + pos, payment->amount); + pos += 8; + + // 12. Extension + write_uint32_be(signature_base + pos, 0); + pos += 4; + + *base_len = pos; + + return 0; +} + +static int stellar_create_signature(stellar_transaction_t *tx, + stellar_payment_t *payment, + const uint8_t *private_key, + const uint8_t *public_key, + stellar_network_t network, + uint8_t *signature) { + // uint8_t signature_base[1024]; + int base_len; + + // 1. Create signature base + const char *passphrase = (network == STELLAR_NETWORK_TESTNET) + ? TESTNET_PASSPHRASE + : MAINNET_PASSPHRASE; + + int result = + create_signature_base(passphrase, tx, payment, signature_base, &base_len); + if (result != 0) { + return result; + } + + // 2. Hash the signature base (this is what we actually sign) + uint8_t transaction_hash[32]; + sha256_Raw(signature_base, base_len, transaction_hash); + + // 3. Sign the hash (32 bytes), not the raw signature base + ed25519_signature sig; + ed25519_sign(transaction_hash, 32, private_key, public_key, sig); + memcpy(signature, sig, 64); + + return 0; +} + +static int create_signed_transaction_envelope(stellar_transaction_t *tx, + stellar_payment_t *payment, + const uint8_t *private_key, + const uint8_t *public_key, + stellar_network_t network, + char *signed_envelope_b64) { + uint8_t signature[64]; + uint8_t signed_envelope[1024]; + int pos = 0; + + // 1. Sign the transaction first + int result = stellar_create_signature( + tx, payment, private_key, public_key, network, signature); + if (result != 0) { + return result; + } + + // 2. Create TransactionEnvelope XDR structure + + // Envelope type (4 bytes) - ENVELOPE_TYPE_TX = 2 + write_uint32_be(signed_envelope + pos, 2); + pos += 4; + + // Transaction structure + // Source account + write_uint32_be(signed_envelope + pos, 0); // KEY_TYPE_ED25519 + pos += 4; + memcpy(signed_envelope + pos, tx->source_account, 32); + pos += 32; + + // Fee + write_uint32_be(signed_envelope + pos, tx->fee); + pos += 4; + + // Sequence number + write_uint64_be(signed_envelope + pos, tx->sequence_number); + pos += 8; + + // Preconditions (time bounds) + write_uint32_be(signed_envelope + pos, 1); // PRECOND_TIME + pos += 4; + write_uint64_be(signed_envelope + pos, 0); // min_time + pos += 8; + write_uint64_be(signed_envelope + pos, 0); // max_time + pos += 8; + + // Memo + write_uint32_be(signed_envelope + pos, tx->memo_type); + pos += 4; + + if (tx->memo_type == MEMO_TEXT) { + int memo_len = strlen(tx->memo.text); + write_uint32_be(signed_envelope + pos, memo_len); + pos += 4; + memcpy(signed_envelope + pos, tx->memo.text, memo_len); + pos += memo_len; + // Add padding + int padding = (4 - (memo_len % 4)) % 4; + for (int i = 0; i < padding; i++) { + signed_envelope[pos++] = 0; + } + } else if (tx->memo_type == MEMO_ID) { + write_uint64_be(signed_envelope + pos, tx->memo.id); + pos += 8; + } else if (tx->memo_type == MEMO_HASH || tx->memo_type == MEMO_RETURN) { + memcpy(signed_envelope + pos, tx->memo.hash, 32); + pos += 32; + } + + // Operations + write_uint32_be(signed_envelope + pos, 1); // operation count + pos += 4; + + // Operation: source account (optional) + write_uint32_be(signed_envelope + pos, 0); // no source account + pos += 4; + + // Operation type + write_uint32_be(signed_envelope + pos, tx->operation_type); // PAYMENT + pos += 4; + + // Payment operation + write_uint32_be(signed_envelope + pos, 0); // KEY_TYPE_ED25519 + pos += 4; + memcpy(signed_envelope + pos, payment->destination, 32); + pos += 32; + if (tx->operation_type) { + write_uint32_be(signed_envelope + pos, 0); // ASSET_TYPE_NATIVE + pos += 4; + } + write_uint64_be(signed_envelope + pos, payment->amount); + pos += 8; + + // Transaction extension + write_uint32_be(signed_envelope + pos, 0); + pos += 4; + + // 3. Add signatures array + write_uint32_be(signed_envelope + pos, 1); // signature count + pos += 4; + + // Decorated signature + // Signature hint (last 4 bytes of public key) + memcpy(signed_envelope + pos, public_key + 28, 4); + pos += 4; + + // Signature data + write_uint32_be(signed_envelope + pos, 64); // signature length + pos += 4; + memcpy(signed_envelope + pos, signature, 64); + pos += 64; + + // 4. Convert to base64 + base64_encode(signed_envelope, pos, signed_envelope_b64); + + return 0; +} + +static bool sign_txn(der_sig_t *der_signature) { + uint8_t seed[64] = {0}; + if (!reconstruct_seed( + stellar_txn_context->init_info.wallet_id, seed, stellar_send_error)) { + memzero(seed, sizeof(seed)); + return false; + } + + set_app_flow_status(STELLAR_SIGN_TXN_STATUS_SEED_GENERATED); + + // Create HDNode from seed and derive private key + HDNode node = {0}; + if (hdnode_from_seed(seed, 64, "ed25519", &node) != 1) { + stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 1); + memzero(seed, sizeof(seed)); + memzero(&node, sizeof(HDNode)); + return false; + } + + // Derive to correct path + for (uint32_t i = 0; i < stellar_txn_context->init_info.derivation_path_count; + i++) { + if (hdnode_private_ckd( + &node, stellar_txn_context->init_info.derivation_path[i]) != 1) { + stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 1); + memzero(seed, sizeof(seed)); + memzero(&node, sizeof(HDNode)); + return false; + } + } + + // Fill public key + hdnode_fill_public_key(&node); + + // Create the full transaction envelope + // char envelope_b64[2048] = {0}; + int result = create_signed_transaction_envelope( + stellar_txn_context->txn, + stellar_txn_context->payment, + node.private_key, + node.public_key + 1, // Skip first byte for Stellar raw public key + STELLAR_NETWORK_MAINNET, + envelope_b64); + + if (result != 0) { + stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 2); + memzero(seed, sizeof(seed)); + memzero(&node, sizeof(HDNode)); + return false; + } + + // Copy the base64 encoded envelope to the output buffer + // Check if the envelope fits in the signature buffer + size_t envelope_len = strlen(envelope_b64); + if (envelope_len > sizeof(der_signature->bytes)) { + stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 3); + memzero(seed, sizeof(seed)); + memzero(&node, sizeof(HDNode)); + return false; + } + + memcpy(der_signature->bytes, envelope_b64, envelope_len); + der_signature->size = envelope_len; + + // Clean up sensitive data + memzero(seed, sizeof(seed)); + memzero(&node, sizeof(HDNode)); + memzero(envelope_b64, sizeof(envelope_b64)); + + return true; +} + +static bool send_signature(stellar_query_t *query, + const der_sig_t *der_signature) { + stellar_result_t result = init_stellar_result(STELLAR_RESULT_SIGN_TXN_TAG); + result.sign_txn.which_response = STELLAR_SIGN_TXN_RESPONSE_SIGNATURE_TAG; + + if (!stellar_get_query(query, STELLAR_QUERY_SIGN_TXN_TAG) || + !check_which_request(query, STELLAR_SIGN_TXN_REQUEST_SIGNATURE_TAG)) { + return false; + } + + memcpy( + &result.sign_txn.signature.signature, der_signature, sizeof(der_sig_t)); + + stellar_send_result(&result); + return true; +} + +/***************************************************************************** + * GLOBAL FUNCTIONS + *****************************************************************************/ +void stellar_sign_transaction(stellar_query_t *query) { + stellar_txn_context = + (stellar_txn_context_t *)malloc(sizeof(stellar_txn_context_t)); + memzero(stellar_txn_context, sizeof(stellar_txn_context_t)); + + der_sig_t der_signature = {0}; + + if (handle_initiate_query(query) && fetch_valid_input(query) && + get_user_verification() && sign_txn(&der_signature) && + send_signature(query, &der_signature)) { + delay_scr_init(ui_text_check_cysync, DELAY_TIME); + } + + // Clean up + if (stellar_txn_context) { + if (stellar_txn_context->transaction) { + free(stellar_txn_context->transaction); + } + if (stellar_txn_context->txn) { + free(stellar_txn_context->txn); + } + if (stellar_txn_context->payment) { + free(stellar_txn_context->payment); + } + free(stellar_txn_context); + stellar_txn_context = NULL; + } + + return; +} \ No newline at end of file diff --git a/apps/stellar_app/stellar_txn_helpers.c b/apps/stellar_app/stellar_txn_helpers.c new file mode 100644 index 000000000..80031cc8f --- /dev/null +++ b/apps/stellar_app/stellar_txn_helpers.c @@ -0,0 +1,305 @@ +/** + * @file stellar_txn_helpers.c + * @author Cypherock X1 Team + * @brief Helper functions for the STELLAR app for txn signing flow + * @copyright Copyright (c) 2025 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + * + ****************************************************************************** + * @attention + * + * (c) Copyright 2025 by HODL TECH PTE LTD + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject + * to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, + * as defined below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of + * rights under the License will not include, and the License does not + * grant to you, the right to Sell the Software. + * + * For purposes of the foregoing, "Sell" means practicing any or all + * of the rights granted to you under the License to provide to third + * parties, for a fee or other consideration (including without + * limitation fees for hosting or consulting/ support services related + * to the Software), a product or service whose value derives, entirely + * or substantially, from the functionality of the Software. Any license + * notice or attribution required by the License must also include + * this Commons Clause License Condition notice. + * + * Software: All X1Wallet associated files. + * License: MIT + * Licensor: HODL TECH PTE LTD + * + ****************************************************************************** + */ + +/***************************************************************************** + * INCLUDES + *****************************************************************************/ + +#include "stellar_txn_helpers.h" + +#include +#include +#include + +#include "stellar_context.h" + +/***************************************************************************** + * EXTERN VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * STATIC VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTION PROTOTYPES + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTIONS + *****************************************************************************/ + +static uint32_t read_uint32_be_pos(const uint8_t *data, int *pos) { + uint32_t value = (data[*pos] << 24) | (data[*pos + 1] << 16) | + (data[*pos + 2] << 8) | data[*pos + 3]; + *pos += 4; + return value; +} + +static uint64_t read_uint64_be_pos(const uint8_t *data, int *pos) { + uint64_t high = read_uint32_be_pos(data, pos); + uint64_t low = read_uint32_be_pos(data, pos); + return (high << 32) | low; +} + +static void read_account_pos(const uint8_t *data, int *pos, uint8_t *account) { + memcpy(account, data + *pos, 32); + *pos += 32; +} + +static int read_string_pos(const uint8_t *data, + int *pos, + char *str, + int max_len, + int data_len) { + if (*pos + 4 > data_len) + return -1; + + uint32_t len = read_uint32_be_pos(data, pos); + if (len >= max_len || *pos + len > data_len) + return -1; + + // Calculate padded length (round up to 4-byte boundary) + int padded_len = ((len + 3) / 4) * 4; + if (*pos + padded_len > data_len) + return -1; + + memcpy(str, data + *pos, len); + str[len] = '\0'; + *pos += padded_len; // Skip data + padding in one go + + return len; +} + +/***************************************************************************** + * GLOBAL FUNCTIONS + *****************************************************************************/ + +// Helper function 1: Parse memo data +static int parse_memo_data(const uint8_t *xdr, + int *pos, + int xdr_len, + stellar_transaction_t *tx) { + tx->memo_type = (stellar_memo_type_t)read_uint32_be_pos(xdr, pos); + + switch (tx->memo_type) { + case MEMO_NONE: + break; + + case MEMO_TEXT: { + char temp_memo[64]; + int memo_len = + read_string_pos(xdr, pos, temp_memo, sizeof(temp_memo), xdr_len); + if (memo_len < 0) { + return -1; + } + strncpy(tx->memo.text, temp_memo, sizeof(tx->memo.text) - 1); + tx->memo.text[sizeof(tx->memo.text) - 1] = '\0'; + } break; + + case MEMO_ID: + tx->memo.id = read_uint64_be_pos(xdr, pos); + break; + + case MEMO_HASH: + case MEMO_RETURN: + if (*pos + 32 > xdr_len) { + return -1; + } + memcpy(tx->memo.hash, xdr + *pos, 32); + *pos += 32; + break; + + default: + return -1; + } + + return 0; +} + +// Helper function 2: Parse operation data +static int parse_operation_data(const uint8_t *xdr, + int *pos, + int xdr_len, + stellar_transaction_t *tx, + stellar_payment_t *payment) { + // Parse Operations count + tx->operation_count = read_uint32_be_pos(xdr, pos); + + if (tx->operation_count == 0) { + return -1; + } + + // Parse First Operation + uint32_t has_source_account = read_uint32_be_pos(xdr, pos); + + if (has_source_account == 1) { + *pos += 36; + } else if (has_source_account != 0) { + return -1; + } + + // Parse operation type + uint32_t operation_type = read_uint32_be_pos(xdr, pos); + + if (operation_type != 1 && operation_type != 0) { + return -1; + } + tx->operation_type = operation_type; + + if (operation_type == 0) { // CREATE_ACCOUNT + uint32_t dest_account_type = read_uint32_be_pos(xdr, pos); + if (dest_account_type != 0) { + return -1; + } + read_account_pos(xdr, pos, payment->destination); + payment->amount = read_uint64_be_pos(xdr, pos); + return 0; // Early return for CREATE_ACCOUNT + } + + // PAYMENT Operation + uint32_t dest_account_type = read_uint32_be_pos(xdr, pos); + if (dest_account_type != 0) { + return -1; + } + + read_account_pos(xdr, pos, payment->destination); + + uint32_t asset_type = read_uint32_be_pos(xdr, pos); + if (asset_type != 0) { + return -1; + } + + payment->amount = read_uint64_be_pos(xdr, pos); + return 0; +} + +// Main function - much simpler now +int stellar_parse_transaction(const uint8_t *xdr, + int xdr_len, + stellar_transaction_t *tx, + stellar_payment_t *payment) { + if (!xdr || !tx || !payment || xdr_len < 60) { + return -1; + } + + int pos = 0; + + // Clear structures + memset(tx, 0, sizeof(stellar_transaction_t)); + memset(payment, 0, sizeof(stellar_payment_t)); + + // 1. Parse Envelope Type (4 bytes) + uint32_t envelope_type = read_uint32_be_pos(xdr, &pos); + if (envelope_type != 2) { + return -1; + } + + // 2. Parse Source Account + uint32_t source_account_type = read_uint32_be_pos(xdr, &pos); + if (source_account_type != 0) { + return -1; + } + + read_account_pos(xdr, &pos, tx->source_account); + + // 3. Parse Fee (4 bytes) + tx->fee = read_uint32_be_pos(xdr, &pos); + + // 4. Parse Sequence Number (8 bytes) + tx->sequence_number = read_uint64_be_pos(xdr, &pos); + + // 5. Parse Preconditions + uint32_t preconditions_type = read_uint32_be_pos(xdr, &pos); + + if (preconditions_type == 1) { + pos += 16; + } else if (preconditions_type != 0) { + return -1; + } + + // 6. Parse Memo + if (parse_memo_data(xdr, &pos, xdr_len, tx) != 0) { + return -1; + } + + // 7. Parse Operations + int result = parse_operation_data(xdr, &pos, xdr_len, tx, payment); + if (result != 0) { + return result; + } + + // 8. Skip transaction extension + if (pos + 4 <= xdr_len) { + read_uint32_be_pos(xdr, &pos); + } + + return 0; +} \ No newline at end of file diff --git a/apps/stellar_app/stellar_txn_helpers.h b/apps/stellar_app/stellar_txn_helpers.h new file mode 100644 index 000000000..51e1302aa --- /dev/null +++ b/apps/stellar_app/stellar_txn_helpers.h @@ -0,0 +1,58 @@ +/** + * @file stellar_txn_helpers.h + * @author Cypherock X1 Team + * @brief Helper functions for the Stellar app for txn signing flow + * @copyright Copyright (c) 2025 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + */ +#ifndef STELLAR_TXN_HELPERS_H +#define STELLAR_TXN_HELPERS_H + +/***************************************************************************** + * INCLUDES + *****************************************************************************/ +#include +#include + +#include "stellar_context.h" + +/***************************************************************************** + * MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * EXPORTED VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTION PROTOTYPES + *****************************************************************************/ + +/** + * @brief Parse XDR byte array of unsigned transaction and store decoded + * information to be used for user confirmation. + * @details Supports Stellar payment and create account transactions. + * + * @param xdr Constant reference to buffer containing the raw unsigned XDR + * transaction + * @param xdr_len Size in bytes of the XDR transaction + * @param tx Reference to buffer where decoded transaction information will be + * populated. It can be used at a later stage for user verification. + * @param payment Reference to buffer where decoded payment/create account + * operation will be populated. + * @return int 0 if the parsing was successful, negative value if parsing failed + * @retval 0 If the parsing was successful + * @retval -1 If the parsing failed - could be due to unsupported transaction, + * data type, or missing information + */ +int stellar_parse_transaction(const uint8_t *xdr, + int xdr_len, + stellar_transaction_t *tx, + stellar_payment_t *payment); + +#endif /* STELLAR_TXN_HELPERS_H */ diff --git a/common/core/core_flow_init.c b/common/core/core_flow_init.c index 9267d48b1..e0af70e63 100644 --- a/common/core/core_flow_init.c +++ b/common/core/core_flow_init.c @@ -87,9 +87,9 @@ #include "restricted_app.h" #include "solana_main.h" #include "starknet_main.h" +#include "stellar_main.h" #include "tron_main.h" #include "xrp_main.h" -#include "stellar_main.h" /***************************************************************************** * EXTERN VARIABLES From bdff72a60796def6c77eb745c19b0db33a97ab88 Mon Sep 17 00:00:00 2001 From: Keyur279 Date: Tue, 22 Jul 2025 01:38:10 +0530 Subject: [PATCH 08/23] chore: Update cypherock-common for stellar --- common/cypherock-common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/cypherock-common b/common/cypherock-common index 67a0d07b7..dea8607fe 160000 --- a/common/cypherock-common +++ b/common/cypherock-common @@ -1 +1 @@ -Subproject commit 67a0d07b71c8041558ca7c0d19e7dff9d4153677 +Subproject commit dea8607feb6c406e9336d5f63163bf1d8249ac10 From c99340f54c5ae26a72d5ca8a0f6c5b93a9619eb6 Mon Sep 17 00:00:00 2001 From: Keyur279 Date: Fri, 25 Jul 2025 18:13:30 +0530 Subject: [PATCH 09/23] feat: Add reference links to stellar files --- apps/stellar_app/stellar_context.h | 3 +++ apps/stellar_app/stellar_helpers.h | 3 +++ apps/stellar_app/stellar_pub_key.c | 7 ++++++- apps/stellar_app/stellar_txn.c | 3 +++ apps/stellar_app/stellar_txn_helpers.c | 4 ++++ 5 files changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/stellar_app/stellar_context.h b/apps/stellar_app/stellar_context.h index 88d423268..bc1662a1e 100644 --- a/apps/stellar_app/stellar_context.h +++ b/apps/stellar_app/stellar_context.h @@ -32,6 +32,7 @@ #define STELLAR_SIGNATURE_SIZE 64 // Network passphrases +// See https://developers.stellar.org/docs/learn/fundamentals/networks #define TESTNET_PASSPHRASE "Test SDF Network ; September 2015" #define MAINNET_PASSPHRASE "Public Global Stellar Network ; September 2015" @@ -44,6 +45,7 @@ typedef struct { } stellar_config_t; // Stellar Memo types +// See https://developers.stellar.org/docs/learn/encyclopedia/transactions-specialized/memos typedef enum { MEMO_NONE = 0, MEMO_TEXT = 1, @@ -90,6 +92,7 @@ typedef enum { * 1. Creates a payload with account ID type (0x30) and the public key * 2. Calculates CRC16 checksum * 3. Encodes the result using base32 + * See https://developers.stellar.org/docs/fundamentals-and-concepts/stellar-data-structures/accounts * * @param public_key The 32-byte ED25519 public key * @param address Buffer to store the resulting address (must be at least diff --git a/apps/stellar_app/stellar_helpers.h b/apps/stellar_app/stellar_helpers.h index 2a27811a7..bc35c9e1f 100644 --- a/apps/stellar_app/stellar_helpers.h +++ b/apps/stellar_app/stellar_helpers.h @@ -25,6 +25,8 @@ #define STELLAR_IMPLICIT_ACCOUNT_DEPTH 3 +// BIP44 derivation path: m/44'/148'/n' +// See https://github.com/satoshilabs/slips/blob/master/slip-0044.md #define STELLAR_PURPOSE_INDEX 0x8000002C // 44' #define STELLAR_COIN_INDEX 0x80000094 // 148' #define STELLAR_ACCOUNT_INDEX 0x80000000 // 0' @@ -47,6 +49,7 @@ * 3, then this function return false indicating invalid derivation path. The * function supports checking derivation paths for HD wallets Types of * derivations: address: m/44'/148'/0' + * See https://developers.stellar.org/docs/fundamentals-and-concepts/accounts * * @param[in] path The derivation path as an uint32 array * @param[in] levels The number of levels in the derivation path diff --git a/apps/stellar_app/stellar_pub_key.c b/apps/stellar_app/stellar_pub_key.c index 44e529695..6e074da53 100644 --- a/apps/stellar_app/stellar_pub_key.c +++ b/apps/stellar_app/stellar_pub_key.c @@ -412,14 +412,19 @@ bool stellar_generate_address(const uint8_t *public_key, char *address) { return false; } + // Stellar address encoding (StrKey format) + // See https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0023.md uint8_t payload[35]; - payload[0] = 6 << 3; // Account ID type + payload[0] = 6 << 3; // Account ID version byte (0x30) memcpy(payload + 1, public_key, 32); + // CRC16-XModem checksum calculation + // See https://stellar.stackexchange.com/questions/255/which-cryptographic-algorithm-is-used-to-generate-the-secret-and-public-keys uint16_t checksum = crc16(payload, 33); payload[33] = checksum & 0xFF; payload[34] = checksum >> 8; + // RFC4648 base32 encoding without padding base32_encode(payload, 35, address, 57, BASE32_ALPHABET_RFC4648); return true; } diff --git a/apps/stellar_app/stellar_txn.c b/apps/stellar_app/stellar_txn.c index f866c4a25..0933e69a7 100644 --- a/apps/stellar_app/stellar_txn.c +++ b/apps/stellar_app/stellar_txn.c @@ -490,6 +490,9 @@ static bool get_user_verification(void) { return true; } +// See https://developers.stellar.org/docs/learn/encyclopedia/data-format/xdr +// Envelope type (4 bytes) - ENVELOPE_TYPE_TX = 2 +// See https://github.com/stellar/stellar-protocol/blob/master/core/cap-0015.md static void write_uint32_be(uint8_t *buffer, uint32_t value) { buffer[0] = (value >> 24) & 0xFF; buffer[1] = (value >> 16) & 0xFF; diff --git a/apps/stellar_app/stellar_txn_helpers.c b/apps/stellar_app/stellar_txn_helpers.c index 80031cc8f..fe5747188 100644 --- a/apps/stellar_app/stellar_txn_helpers.c +++ b/apps/stellar_app/stellar_txn_helpers.c @@ -185,6 +185,8 @@ static int parse_memo_data(const uint8_t *xdr, } // Helper function 2: Parse operation data +// Parse XDR transaction envelope format +// See https://developers.stellar.org/docs/learn/encyclopedia/data-format/xdr static int parse_operation_data(const uint8_t *xdr, int *pos, int xdr_len, @@ -257,6 +259,8 @@ int stellar_parse_transaction(const uint8_t *xdr, memset(payment, 0, sizeof(stellar_payment_t)); // 1. Parse Envelope Type (4 bytes) + // ENVELOPE_TYPE_TX = 2 + // See https://github.com/stellar/stellar-protocol/blob/master/core/cap-0015.md uint32_t envelope_type = read_uint32_be_pos(xdr, &pos); if (envelope_type != 2) { return -1; From f35100de4852e83e47cdcfb0a0a8cb6ca9638007 Mon Sep 17 00:00:00 2001 From: Keyur279 Date: Fri, 25 Jul 2025 18:30:12 +0530 Subject: [PATCH 10/23] feat: Add reference links to stellar files --- apps/stellar_app/stellar_context.h | 9 ++++++--- apps/stellar_app/stellar_pub_key.c | 6 ++++-- apps/stellar_app/stellar_txn_helpers.c | 8 +++++--- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/stellar_app/stellar_context.h b/apps/stellar_app/stellar_context.h index bc1662a1e..e7a307bf7 100644 --- a/apps/stellar_app/stellar_context.h +++ b/apps/stellar_app/stellar_context.h @@ -32,7 +32,8 @@ #define STELLAR_SIGNATURE_SIZE 64 // Network passphrases -// See https://developers.stellar.org/docs/learn/fundamentals/networks +// See +// https://developers.stellar.org/docs/learn/fundamentals/networks #define TESTNET_PASSPHRASE "Test SDF Network ; September 2015" #define MAINNET_PASSPHRASE "Public Global Stellar Network ; September 2015" @@ -45,7 +46,8 @@ typedef struct { } stellar_config_t; // Stellar Memo types -// See https://developers.stellar.org/docs/learn/encyclopedia/transactions-specialized/memos +// See +// https://developers.stellar.org/docs/learn/encyclopedia/transactions-specialized/memos typedef enum { MEMO_NONE = 0, MEMO_TEXT = 1, @@ -92,7 +94,8 @@ typedef enum { * 1. Creates a payload with account ID type (0x30) and the public key * 2. Calculates CRC16 checksum * 3. Encodes the result using base32 - * See https://developers.stellar.org/docs/fundamentals-and-concepts/stellar-data-structures/accounts + * See + * https://developers.stellar.org/docs/fundamentals-and-concepts/stellar-data-structures/accounts * * @param public_key The 32-byte ED25519 public key * @param address Buffer to store the resulting address (must be at least diff --git a/apps/stellar_app/stellar_pub_key.c b/apps/stellar_app/stellar_pub_key.c index 6e074da53..6a7a45410 100644 --- a/apps/stellar_app/stellar_pub_key.c +++ b/apps/stellar_app/stellar_pub_key.c @@ -413,13 +413,15 @@ bool stellar_generate_address(const uint8_t *public_key, char *address) { } // Stellar address encoding (StrKey format) - // See https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0023.md + // See + // https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0023.md uint8_t payload[35]; payload[0] = 6 << 3; // Account ID version byte (0x30) memcpy(payload + 1, public_key, 32); // CRC16-XModem checksum calculation - // See https://stellar.stackexchange.com/questions/255/which-cryptographic-algorithm-is-used-to-generate-the-secret-and-public-keys + // See + // https://stellar.stackexchange.com/questions/255/which-cryptographic-algorithm-is-used-to-generate-the-secret-and-public-keys uint16_t checksum = crc16(payload, 33); payload[33] = checksum & 0xFF; payload[34] = checksum >> 8; diff --git a/apps/stellar_app/stellar_txn_helpers.c b/apps/stellar_app/stellar_txn_helpers.c index fe5747188..753db0e1a 100644 --- a/apps/stellar_app/stellar_txn_helpers.c +++ b/apps/stellar_app/stellar_txn_helpers.c @@ -186,7 +186,8 @@ static int parse_memo_data(const uint8_t *xdr, // Helper function 2: Parse operation data // Parse XDR transaction envelope format -// See https://developers.stellar.org/docs/learn/encyclopedia/data-format/xdr +// See +// https://developers.stellar.org/docs/learn/encyclopedia/data-format/xdr static int parse_operation_data(const uint8_t *xdr, int *pos, int xdr_len, @@ -259,8 +260,9 @@ int stellar_parse_transaction(const uint8_t *xdr, memset(payment, 0, sizeof(stellar_payment_t)); // 1. Parse Envelope Type (4 bytes) - // ENVELOPE_TYPE_TX = 2 - // See https://github.com/stellar/stellar-protocol/blob/master/core/cap-0015.md + // ENVELOPE_TYPE_TX = 2 + // See + // https://github.com/stellar/stellar-protocol/blob/master/core/cap-0015.md uint32_t envelope_type = read_uint32_be_pos(xdr, &pos); if (envelope_type != 2) { return -1; From 2af50618d2af5e6b9739d52279b68d649e452dbf Mon Sep 17 00:00:00 2001 From: Keyur279 Date: Wed, 30 Jul 2025 05:15:38 +0530 Subject: [PATCH 11/23] feat: Address stellar review feedback --- apps/stellar_app/stellar_context.h | 52 ++- apps/stellar_app/stellar_helpers.c | 47 +++ apps/stellar_app/stellar_helpers.h | 15 + apps/stellar_app/stellar_pub_key.c | 66 +--- apps/stellar_app/stellar_txn.c | 429 ++++++++----------------- apps/stellar_app/stellar_txn_helpers.c | 161 +++++----- common/libraries/util/utils.c | 12 + common/libraries/util/utils.h | 15 + src/constant_texts.c | 7 + src/constant_texts.h | 16 +- 10 files changed, 349 insertions(+), 471 deletions(-) diff --git a/apps/stellar_app/stellar_context.h b/apps/stellar_app/stellar_context.h index e7a307bf7..ddb3ae5ff 100644 --- a/apps/stellar_app/stellar_context.h +++ b/apps/stellar_app/stellar_context.h @@ -31,9 +31,13 @@ #define STELLAR_SECRET_KEY_LENGTH 57 #define STELLAR_SIGNATURE_SIZE 64 +// Stellar XDR constants +#define STELLAR_ENVELOPE_TYPE_TX 2 +#define STELLAR_KEY_TYPE_ED25519 0 +#define STELLAR_ASSET_TYPE_NATIVE 0 + // Network passphrases -// See -// https://developers.stellar.org/docs/learn/fundamentals/networks +// See https://developers.stellar.org/docs/learn/fundamentals/networks #define TESTNET_PASSPHRASE "Test SDF Network ; September 2015" #define MAINNET_PASSPHRASE "Public Global Stellar Network ; September 2015" @@ -46,28 +50,35 @@ typedef struct { } stellar_config_t; // Stellar Memo types -// See -// https://developers.stellar.org/docs/learn/encyclopedia/transactions-specialized/memos +// See https://developers.stellar.org/docs/learn/encyclopedia/transactions-specialized/memos typedef enum { - MEMO_NONE = 0, - MEMO_TEXT = 1, - MEMO_ID = 2, - MEMO_HASH = 3, - MEMO_RETURN = 4 + STELLAR_MEMO_NONE = 0, + STELLAR_MEMO_TEXT = 1, + STELLAR_MEMO_ID = 2, + STELLAR_MEMO_HASH = 3, + STELLAR_MEMO_RETURN = 4 } stellar_memo_type_t; +// Stellar operation types +// See https://developers.stellar.org/docs/learn/fundamentals/transactions/list-of-operations +typedef enum { + STELLAR_OPERATION_CREATE_ACCOUNT = 0, + STELLAR_OPERATION_PAYMENT = 1 +} stellar_operation_type_t; + // Stellar transaction structures +// See https://developers.stellar.org/docs/learn/encyclopedia/data-format/xdr typedef struct { uint8_t source_account[32]; uint64_t sequence_number; uint32_t fee; uint32_t operation_count; - uint32_t operation_type; // CREATE_ACCOUNT = 0, PAYMENT = 1 + stellar_operation_type_t operation_type; stellar_memo_type_t memo_type; union { - char text[29]; // MEMO_TEXT (max 28 bytes + 1 byte delimiter) - uint64_t id; // MEMO_ID - uint8_t hash[32]; // MEMO_HASH or MEMO_RETURN(32 bytes) + char text[29]; // STELLAR_MEMO_TEXT (max 28 bytes + 1 byte delimiter) + uint64_t id; // STELLAR_MEMO_ID + uint8_t hash[32]; // STELLAR_MEMO_HASH or STELLAR_MEMO_RETURN(32 bytes) } memo; } stellar_transaction_t; @@ -88,20 +99,5 @@ typedef enum { /***************************************************************************** * GLOBAL FUNCTION PROTOTYPES *****************************************************************************/ -/** - * @brief Generates a Stellar address from a public key - * @details Follows the Stellar address generation algorithm: - * 1. Creates a payload with account ID type (0x30) and the public key - * 2. Calculates CRC16 checksum - * 3. Encodes the result using base32 - * See - * https://developers.stellar.org/docs/fundamentals-and-concepts/stellar-data-structures/accounts - * - * @param public_key The 32-byte ED25519 public key - * @param address Buffer to store the resulting address (must be at least - * STELLAR_ADDRESS_LENGTH bytes) - * @return true if the address was generated successfully, false otherwise - */ -bool stellar_generate_address(const uint8_t *public_key, char *address); #endif /* STELLAR_CONTEXT_H */ \ No newline at end of file diff --git a/apps/stellar_app/stellar_helpers.c b/apps/stellar_app/stellar_helpers.c index 369f511a9..0be58a9d2 100644 --- a/apps/stellar_app/stellar_helpers.c +++ b/apps/stellar_app/stellar_helpers.c @@ -62,6 +62,9 @@ #include "stellar_helpers.h" +#include "base32.h" +#include "stellar_context.h" + /***************************************************************************** * EXTERN VARIABLES *****************************************************************************/ @@ -78,6 +81,14 @@ * STATIC FUNCTION PROTOTYPES *****************************************************************************/ +/** + * @brief Calculate CRC16 checksum for Stellar address encoding + * @param data Input data buffer + * @param len Length of input data + * @return CRC16 checksum + */ +static uint16_t crc16(const uint8_t *data, size_t len); + /***************************************************************************** * STATIC VARIABLES *****************************************************************************/ @@ -90,6 +101,20 @@ * STATIC FUNCTIONS *****************************************************************************/ +static uint16_t crc16(const uint8_t *data, size_t len) { + uint16_t crc = 0x0000; + for (size_t i = 0; i < len; i++) { + crc ^= data[i] << 8; + for (int j = 0; j < 8; j++) { + if (crc & 0x8000) + crc = (crc << 1) ^ 0x1021; + else + crc <<= 1; + } + } + return crc; +} + /***************************************************************************** * GLOBAL FUNCTIONS *****************************************************************************/ @@ -108,3 +133,25 @@ bool stellar_derivation_path_guard(const uint32_t *path, uint8_t levels) { return status; } + +bool stellar_generate_address(const uint8_t *public_key, char *address) { + if (!public_key || !address) { + return false; + } + + // Stellar address encoding (StrKey format) + // See https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0023.md + uint8_t payload[35]; + payload[0] = 0x30; // Account ID version byte (6 << 3 | 0 = STRKEY_PUBKEY OR STRKEY_ALG_ED25519) + memcpy(payload + 1, public_key, STELLAR_PUBKEY_RAW_SIZE); + + // CRC16-XModem checksum calculation + // See https://stellar.stackexchange.com/questions/255/which-cryptographic-algorithm-is-used-to-generate-the-secret-and-public-keys + uint16_t checksum = crc16(payload, 33); + payload[33] = checksum & 0xFF; + payload[34] = checksum >> 8; + + // RFC4648 base32 encoding without padding + base32_encode(payload, 35, address, STELLAR_ADDRESS_LENGTH, BASE32_ALPHABET_RFC4648); + return true; +} \ No newline at end of file diff --git a/apps/stellar_app/stellar_helpers.h b/apps/stellar_app/stellar_helpers.h index bc35c9e1f..ec422daf5 100644 --- a/apps/stellar_app/stellar_helpers.h +++ b/apps/stellar_app/stellar_helpers.h @@ -60,4 +60,19 @@ */ bool stellar_derivation_path_guard(const uint32_t *path, uint8_t levels); +/** + * @brief Generates a Stellar address from a public key + * @details Follows the Stellar address generation algorithm: + * 1. Creates a payload with account ID type (0x30) and the public key + * 2. Calculates CRC16 checksum + * 3. Encodes the result using base32 + * See https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0023.md + * + * @param public_key The 32-byte ED25519 public key + * @param address Buffer to store the resulting address (must be at least + * STELLAR_ADDRESS_LENGTH bytes) + * @return true if the address was generated successfully, false otherwise + */ +bool stellar_generate_address(const uint8_t *public_key, char *address); + #endif // STELLAR_HELPERS_H \ No newline at end of file diff --git a/apps/stellar_app/stellar_pub_key.c b/apps/stellar_app/stellar_pub_key.c index 6a7a45410..7b9851ef4 100644 --- a/apps/stellar_app/stellar_pub_key.c +++ b/apps/stellar_app/stellar_pub_key.c @@ -219,6 +219,10 @@ static bool get_user_consent(const pb_size_t which_request, *****************************************************************************/ static bool sign_address = false; +/***************************************************************************** + * STATIC FUNCTIONS + *****************************************************************************/ + static bool check_which_request(const stellar_query_t *query, pb_size_t which_request) { if (which_request != query->get_public_keys.which_request) { @@ -299,24 +303,13 @@ static bool get_public_key(const uint8_t *seed, uint8_t *public_key) { HDNode node = {0}; - // Initialize the HDNode with the seed - if (hdnode_from_seed(seed, 64, "ed25519", &node) != 1) { + // Use derive_hdnode_from_path instead of manual derivation + if (!derive_hdnode_from_path(path, path_length, ED25519_NAME, seed, &node)) { stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 1); memzero(&node, sizeof(HDNode)); return false; } - // Derive HDNode from path - for (uint32_t i = 0; i < path_length; i++) { - if (hdnode_private_ckd(&node, path[i]) != 1) { - stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 1); - memzero(&node, sizeof(HDNode)); - return false; - } - } - - hdnode_fill_public_key(&node); - if (NULL != public_key) { // skip first byte, use raw 32 bytes memcpy(public_key, node.public_key + 1, STELLAR_PUBKEY_RAW_SIZE); @@ -342,20 +335,6 @@ static bool fill_public_keys( return true; } -static uint16_t crc16(const uint8_t *data, size_t len) { - uint16_t crc = 0x0000; - for (size_t i = 0; i < len; i++) { - crc ^= data[i] << 8; - for (int j = 0; j < 8; j++) { - if (crc & 0x8000) - crc = (crc << 1) ^ 0x1021; - else - crc <<= 1; - } - } - return crc; -} - static bool send_public_keys( stellar_query_t *query, const uint8_t pubkey_list[][STELLAR_PUBKEY_RAW_SIZE], @@ -399,38 +378,10 @@ static bool send_public_keys( * GLOBAL VARIABLES *****************************************************************************/ -/***************************************************************************** - * STATIC FUNCTIONS - *****************************************************************************/ - /***************************************************************************** * GLOBAL FUNCTIONS *****************************************************************************/ -bool stellar_generate_address(const uint8_t *public_key, char *address) { - if (!public_key || !address) { - return false; - } - - // Stellar address encoding (StrKey format) - // See - // https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0023.md - uint8_t payload[35]; - payload[0] = 6 << 3; // Account ID version byte (0x30) - memcpy(payload + 1, public_key, 32); - - // CRC16-XModem checksum calculation - // See - // https://stellar.stackexchange.com/questions/255/which-cryptographic-algorithm-is-used-to-generate-the-secret-and-public-keys - uint16_t checksum = crc16(payload, 33); - payload[33] = checksum & 0xFF; - payload[34] = checksum >> 8; - - // RFC4648 base32 encoding without padding - base32_encode(payload, 35, address, 57, BASE32_ALPHABET_RFC4648); - return true; -} - void stellar_get_pub_keys(stellar_query_t *query) { char wallet_name[NAME_SIZE] = ""; uint8_t seed[64] = {0}; @@ -489,6 +440,9 @@ void stellar_get_pub_keys(stellar_query_t *query) { if (!stellar_generate_address(pubkey_list[0], address)) { return; } + if (sign_address) { + exchange_sign_address(address, sizeof(address)); + } if (!core_scroll_page(ui_text_receive_on, address, stellar_send_error)) { return; } @@ -502,4 +456,4 @@ void stellar_get_pub_keys(stellar_query_t *query) { } delay_scr_init(ui_text_check_cysync_app, DELAY_TIME); -} +} \ No newline at end of file diff --git a/apps/stellar_app/stellar_txn.c b/apps/stellar_app/stellar_txn.c index 0933e69a7..f4db89cab 100644 --- a/apps/stellar_app/stellar_txn.c +++ b/apps/stellar_app/stellar_txn.c @@ -63,9 +63,9 @@ #include -#include "base32.h" #include "bip32.h" #include "composable_app_queue.h" +#include "constant_texts.h" #include "curves.h" #include "ed25519-donna/ed25519-donna.h" #include "ed25519-donna/ed25519-hash-custom.h" @@ -81,6 +81,7 @@ #include "stellar_txn_helpers.h" #include "ui_core_confirm.h" #include "ui_screens.h" +#include "utils.h" #include "wallet_list.h" /***************************************************************************** @@ -181,7 +182,8 @@ static bool get_user_verification(void); /** * @brief Calculates ED25519 curve based signature over the digest of the user * verified unsigned txn. - * @details Seed reconstruction takes place within this function + * @details Seed reconstruction takes place within this function. Returns only + * the 64-byte signature, not the full transaction envelope. * * @param signature_buffer Reference to buffer where the signature will be * populated @@ -191,6 +193,43 @@ static bool get_user_verification(void); */ static bool sign_txn(der_sig_t *der_signature); +/** + * @brief Creates signature base data for Stellar transaction signing + * @details Builds the standardized data structure that must be signed according + * to Stellar protocol specifications + * + * @param network_passphrase Network passphrase string (mainnet/testnet) + * @param tx Parsed transaction structure + * @param payment Parsed payment operation structure + * @param signature_base Output buffer for signature base data + * @param base_len Output length of signature base data + * @return int 0 on success, non-zero on error + */ +static int create_signature_base(const char *network_passphrase, + stellar_transaction_t *tx, + stellar_payment_t *payment, + uint8_t *signature_base, + int *base_len); + +/** + * @brief Creates ED25519 signature for Stellar transaction + * @details Signs the transaction hash using ED25519 algorithm + * + * @param tx Parsed transaction structure + * @param payment Parsed payment operation structure + * @param private_key ED25519 private key for signing + * @param public_key ED25519 public key for verification + * @param network Target network (mainnet/testnet) + * @param signature Output buffer for 64-byte signature + * @return int 0 on success, non-zero on error + */ +static int stellar_create_signature(stellar_transaction_t *tx, + stellar_payment_t *payment, + const uint8_t *private_key, + const uint8_t *public_key, + stellar_network_t network, + uint8_t *signature); + /** * @brief Sends signature of the STELLAR unsigned txn to the host * @details The function waits for the host to send a request of type @@ -210,10 +249,6 @@ static bool send_signature(stellar_query_t *query, *****************************************************************************/ static stellar_txn_context_t *stellar_txn_context = NULL; static bool use_signature_verification = false; -static const char base64_chars[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -static char envelope_b64[2048]; -static uint8_t signature_base[1024]; /***************************************************************************** * GLOBAL VARIABLES @@ -266,8 +301,6 @@ static bool handle_initiate_query(const stellar_query_t *query) { char wallet_name[NAME_SIZE] = ""; char msg[100] = ""; - // TODO: Handle wallet search failures - eg: Wallet ID not found, Wallet - // ID found but is invalid/locked wallet if (!check_which_request(query, STELLAR_SIGN_TXN_REQUEST_INITIATE_TAG) || !validate_request_data(&query->sign_txn) || !get_wallet_name_by_id(query->sign_txn.initiate.wallet_id, @@ -278,7 +311,6 @@ static bool handle_initiate_query(const stellar_query_t *query) { snprintf( msg, sizeof(msg), UI_TEXT_SIGN_TXN_PROMPT, STELLAR_NAME, wallet_name); - // Take user consent to sign transaction for the wallet if (!core_confirmation(msg, stellar_send_error)) { return false; } @@ -289,8 +321,6 @@ static bool handle_initiate_query(const stellar_query_t *query) { sizeof(stellar_sign_txn_initiate_request_t)); send_response(STELLAR_SIGN_TXN_RESPONSE_CONFIRMATION_TAG); - // show processing screen for a minimum duration (additional time will add due - // to actual processing) delay_scr_init(ui_text_processing, DELAY_SHORT); return true; } @@ -361,26 +391,26 @@ static bool fetch_valid_input(stellar_query_t *query) { return true; } -// Add this helper function before get_user_verification() static bool show_memo_details(const stellar_transaction_t *decoded_txn) { - if (decoded_txn->memo_type == MEMO_TEXT) { + if (decoded_txn->memo_type == STELLAR_MEMO_TEXT) { char memo_display[100] = {'\0'}; snprintf(memo_display, sizeof(memo_display), - "Memo: \"%s\"", + ui_text_stellar_memo_text, decoded_txn->memo.text); return core_confirmation(memo_display, stellar_send_error); - } else if (decoded_txn->memo_type == MEMO_ID) { + } else if (decoded_txn->memo_type == STELLAR_MEMO_ID) { char memo_display[50] = {'\0'}; snprintf(memo_display, sizeof(memo_display), - "Memo ID: %llu", + ui_text_stellar_memo_id, decoded_txn->memo.id); return core_confirmation(memo_display, stellar_send_error); - } else if (decoded_txn->memo_type == MEMO_NONE) { - return core_confirmation("Memo: (none)", stellar_send_error); - } else if (decoded_txn->memo_type == MEMO_HASH || - decoded_txn->memo_type == MEMO_RETURN) { + } else if (decoded_txn->memo_type == STELLAR_MEMO_NONE) { + // Don't show anything for MEMO_NONE + return true; + } else if (decoded_txn->memo_type == STELLAR_MEMO_HASH || + decoded_txn->memo_type == STELLAR_MEMO_RETURN) { char memo_hash[80] = {'\0'}; char temp[3]; strcpy(memo_hash, "Memo Hash: "); @@ -393,13 +423,12 @@ static bool show_memo_details(const stellar_transaction_t *decoded_txn) { char memo_display[50] = {'\0'}; snprintf(memo_display, sizeof(memo_display), - "Memo: (unknown type %u)", + ui_text_stellar_memo_unknown, decoded_txn->memo_type); return core_confirmation(memo_display, stellar_send_error); } } -// Updated main function static bool get_user_verification(void) { const stellar_transaction_t *decoded_txn = stellar_txn_context->txn; const stellar_payment_t *payment = stellar_txn_context->payment; @@ -429,8 +458,8 @@ static bool get_user_verification(void) { char operation_display[50] = {'\0'}; snprintf(operation_display, sizeof(operation_display), - "Operation: %s", - decoded_txn->operation_type == 0 ? "CREATE_ACCOUNT" : "PAYMENT"); + ui_text_stellar_operation, + decoded_txn->operation_type == STELLAR_OPERATION_CREATE_ACCOUNT ? "CREATE_ACCOUNT" : "PAYMENT"); if (!core_confirmation(operation_display, stellar_send_error)) { return false; } @@ -490,164 +519,104 @@ static bool get_user_verification(void) { return true; } -// See https://developers.stellar.org/docs/learn/encyclopedia/data-format/xdr -// Envelope type (4 bytes) - ENVELOPE_TYPE_TX = 2 -// See https://github.com/stellar/stellar-protocol/blob/master/core/cap-0015.md -static void write_uint32_be(uint8_t *buffer, uint32_t value) { - buffer[0] = (value >> 24) & 0xFF; - buffer[1] = (value >> 16) & 0xFF; - buffer[2] = (value >> 8) & 0xFF; - buffer[3] = value & 0xFF; -} - -static void write_uint64_be(uint8_t *buffer, uint64_t value) { - write_uint32_be(buffer, (uint32_t)(value >> 32)); - write_uint32_be(buffer + 4, (uint32_t)(value & 0xFFFFFFFF)); -} - -static void base64_encode(const uint8_t *data, int len, char *output) { - int i = 0, j = 0; - uint8_t char_array_3[3]; - uint8_t char_array_4[4]; - - while (len--) { - char_array_3[i++] = *(data++); - if (i == 3) { - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = - ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = - ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - - for (i = 0; i < 4; i++) { - output[j++] = base64_chars[char_array_4[i]]; - } - i = 0; - } - } - - if (i) { - for (int k = i; k < 3; k++) { - char_array_3[k] = '\0'; - } - - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = - ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = - ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - - for (int k = 0; k < i + 1; k++) { - output[j++] = base64_chars[char_array_4[k]]; - } - - while (i++ < 3) { - output[j++] = '='; - } - } - - output[j] = '\0'; -} - static int create_signature_base(const char *network_passphrase, stellar_transaction_t *tx, stellar_payment_t *payment, uint8_t *signature_base, int *base_len) { - int pos = 0; + int offset = 0; // 1. Network passphrase hash (32 bytes) uint8_t network_hash[32]; sha256_Raw((const uint8_t *)network_passphrase, strlen(network_passphrase), network_hash); - memcpy(signature_base + pos, network_hash, 32); - pos += 32; + memcpy(signature_base + offset, network_hash, 32); + offset += 32; - // 2. TX_TYPE (4 bytes) - ENVELOPE_TYPE_TX = 2 - write_uint32_be(signature_base + pos, 2); - pos += 4; + // 2. ENVELOPE_TYPE_TX (4 bytes) + write_uint32_be(signature_base + offset, STELLAR_ENVELOPE_TYPE_TX); + offset += 4; // 3. Source account type (4 bytes) + public key (32 bytes) - write_uint32_be(signature_base + pos, 0); // KEY_TYPE_ED25519 = 0 - pos += 4; - memcpy(signature_base + pos, tx->source_account, 32); - pos += 32; + write_uint32_be(signature_base + offset, STELLAR_KEY_TYPE_ED25519); + offset += 4; + memcpy(signature_base + offset, tx->source_account, STELLAR_PUBKEY_RAW_SIZE); + offset += STELLAR_PUBKEY_RAW_SIZE; // 4. Fee (4 bytes, big-endian) - write_uint32_be(signature_base + pos, tx->fee); - pos += 4; + write_uint32_be(signature_base + offset, tx->fee); + offset += 4; // 5. Sequence number (8 bytes, big-endian) - write_uint64_be(signature_base + pos, tx->sequence_number); - pos += 8; + write_uint64_be(signature_base + offset, tx->sequence_number); + offset += 8; - // 6. Time bounds (1 + 8 + 8 = 17 bytes) - write_uint32_be(signature_base + pos, 1); // has time bounds = true - pos += 4; - write_uint64_be(signature_base + pos, 0); // min_time = 0 - pos += 8; - write_uint64_be(signature_base + pos, 0); // max_time = 0 - pos += 8; + // 6. Time bounds (17 bytes total) + write_uint32_be(signature_base + offset, 1); // has time bounds = true + offset += 4; + write_uint64_be(signature_base + offset, 0); // min_time = 0 + offset += 8; + write_uint64_be(signature_base + offset, 0); // max_time = 0 + offset += 8; // 7. Memo - write_uint32_be(signature_base + pos, tx->memo_type); - pos += 4; + write_uint32_be(signature_base + offset, tx->memo_type); + offset += 4; - if (tx->memo_type == MEMO_TEXT) { + if (tx->memo_type == STELLAR_MEMO_TEXT) { int memo_len = strlen(tx->memo.text); - write_uint32_be(signature_base + pos, memo_len); - pos += 4; - memcpy(signature_base + pos, tx->memo.text, memo_len); - pos += memo_len; + write_uint32_be(signature_base + offset, memo_len); + offset += 4; + memcpy(signature_base + offset, tx->memo.text, memo_len); + offset += memo_len; // Add padding to 4-byte boundary int padding = (4 - (memo_len % 4)) % 4; for (int i = 0; i < padding; i++) { - signature_base[pos++] = 0; + signature_base[offset++] = 0; } - } else if (tx->memo_type == MEMO_ID) { - write_uint64_be(signature_base + pos, tx->memo.id); - pos += 8; - } else if (tx->memo_type == MEMO_HASH || tx->memo_type == MEMO_RETURN) { - memcpy(signature_base + pos, tx->memo.hash, 32); - pos += 32; + } else if (tx->memo_type == STELLAR_MEMO_ID) { + write_uint64_be(signature_base + offset, tx->memo.id); + offset += 8; + } else if (tx->memo_type == STELLAR_MEMO_HASH || tx->memo_type == STELLAR_MEMO_RETURN) { + memcpy(signature_base + offset, tx->memo.hash, 32); + offset += 32; } // 8. Operations count (4 bytes) - write_uint32_be(signature_base + pos, 1); - pos += 4; + write_uint32_be(signature_base + offset, 1); + offset += 4; // 9. Operation: has_source_account = false - write_uint32_be(signature_base + pos, 0); - pos += 4; + write_uint32_be(signature_base + offset, 0); + offset += 4; // 10. Operation type = PAYMENT OR CREATE_ACCOUNT - write_uint32_be(signature_base + pos, tx->operation_type); - pos += 4; + write_uint32_be(signature_base + offset, tx->operation_type); + offset += 4; - // 11. Payment operation + // 11. Payment/CreateAccount operation // Destination (type + pubkey) - write_uint32_be(signature_base + pos, 0); // KEY_TYPE_ED25519 - pos += 4; - memcpy(signature_base + pos, payment->destination, 32); - pos += 32; - // Asset (native) - if (tx->operation_type) { - write_uint32_be(signature_base + pos, 0); // ASSET_TYPE_NATIVE - pos += 4; - } + write_uint32_be(signature_base + offset, STELLAR_KEY_TYPE_ED25519); + offset += 4; + memcpy(signature_base + offset, payment->destination, STELLAR_PUBKEY_RAW_SIZE); + offset += STELLAR_PUBKEY_RAW_SIZE; + + // Asset (native) - only for PAYMENT operations + if (tx->operation_type == STELLAR_OPERATION_PAYMENT) { + write_uint32_be(signature_base + offset, STELLAR_ASSET_TYPE_NATIVE); + offset += 4; + } + // Amount - write_uint64_be(signature_base + pos, payment->amount); - pos += 8; + write_uint64_be(signature_base + offset, payment->amount); + offset += 8; // 12. Extension - write_uint32_be(signature_base + pos, 0); - pos += 4; - - *base_len = pos; + write_uint32_be(signature_base + offset, 0); + offset += 4; + *base_len = offset; return 0; } @@ -657,8 +626,8 @@ static int stellar_create_signature(stellar_transaction_t *tx, const uint8_t *public_key, stellar_network_t network, uint8_t *signature) { - // uint8_t signature_base[1024]; - int base_len; + uint8_t signature_base[1024] = {0}; + int base_len = 0; // 1. Create signature base const char *passphrase = (network == STELLAR_NETWORK_TESTNET) @@ -676,133 +645,18 @@ static int stellar_create_signature(stellar_transaction_t *tx, sha256_Raw(signature_base, base_len, transaction_hash); // 3. Sign the hash (32 bytes), not the raw signature base - ed25519_signature sig; + ed25519_signature sig = {0}; ed25519_sign(transaction_hash, 32, private_key, public_key, sig); - memcpy(signature, sig, 64); - - return 0; -} - -static int create_signed_transaction_envelope(stellar_transaction_t *tx, - stellar_payment_t *payment, - const uint8_t *private_key, - const uint8_t *public_key, - stellar_network_t network, - char *signed_envelope_b64) { - uint8_t signature[64]; - uint8_t signed_envelope[1024]; - int pos = 0; - - // 1. Sign the transaction first - int result = stellar_create_signature( - tx, payment, private_key, public_key, network, signature); - if (result != 0) { - return result; - } - - // 2. Create TransactionEnvelope XDR structure - - // Envelope type (4 bytes) - ENVELOPE_TYPE_TX = 2 - write_uint32_be(signed_envelope + pos, 2); - pos += 4; - - // Transaction structure - // Source account - write_uint32_be(signed_envelope + pos, 0); // KEY_TYPE_ED25519 - pos += 4; - memcpy(signed_envelope + pos, tx->source_account, 32); - pos += 32; - - // Fee - write_uint32_be(signed_envelope + pos, tx->fee); - pos += 4; - - // Sequence number - write_uint64_be(signed_envelope + pos, tx->sequence_number); - pos += 8; - - // Preconditions (time bounds) - write_uint32_be(signed_envelope + pos, 1); // PRECOND_TIME - pos += 4; - write_uint64_be(signed_envelope + pos, 0); // min_time - pos += 8; - write_uint64_be(signed_envelope + pos, 0); // max_time - pos += 8; - - // Memo - write_uint32_be(signed_envelope + pos, tx->memo_type); - pos += 4; - - if (tx->memo_type == MEMO_TEXT) { - int memo_len = strlen(tx->memo.text); - write_uint32_be(signed_envelope + pos, memo_len); - pos += 4; - memcpy(signed_envelope + pos, tx->memo.text, memo_len); - pos += memo_len; - // Add padding - int padding = (4 - (memo_len % 4)) % 4; - for (int i = 0; i < padding; i++) { - signed_envelope[pos++] = 0; - } - } else if (tx->memo_type == MEMO_ID) { - write_uint64_be(signed_envelope + pos, tx->memo.id); - pos += 8; - } else if (tx->memo_type == MEMO_HASH || tx->memo_type == MEMO_RETURN) { - memcpy(signed_envelope + pos, tx->memo.hash, 32); - pos += 32; - } - - // Operations - write_uint32_be(signed_envelope + pos, 1); // operation count - pos += 4; - - // Operation: source account (optional) - write_uint32_be(signed_envelope + pos, 0); // no source account - pos += 4; - - // Operation type - write_uint32_be(signed_envelope + pos, tx->operation_type); // PAYMENT - pos += 4; - - // Payment operation - write_uint32_be(signed_envelope + pos, 0); // KEY_TYPE_ED25519 - pos += 4; - memcpy(signed_envelope + pos, payment->destination, 32); - pos += 32; - if (tx->operation_type) { - write_uint32_be(signed_envelope + pos, 0); // ASSET_TYPE_NATIVE - pos += 4; - } - write_uint64_be(signed_envelope + pos, payment->amount); - pos += 8; - - // Transaction extension - write_uint32_be(signed_envelope + pos, 0); - pos += 4; - - // 3. Add signatures array - write_uint32_be(signed_envelope + pos, 1); // signature count - pos += 4; - - // Decorated signature - // Signature hint (last 4 bytes of public key) - memcpy(signed_envelope + pos, public_key + 28, 4); - pos += 4; - - // Signature data - write_uint32_be(signed_envelope + pos, 64); // signature length - pos += 4; - memcpy(signed_envelope + pos, signature, 64); - pos += 64; - - // 4. Convert to base64 - base64_encode(signed_envelope, pos, signed_envelope_b64); + memcpy(signature, sig, STELLAR_SIGNATURE_SIZE); return 0; } static bool sign_txn(der_sig_t *der_signature) { uint8_t seed[64] = {0}; + uint8_t signature[STELLAR_SIGNATURE_SIZE] = {0}; + + // Reconstruct seed if (!reconstruct_seed( stellar_txn_context->init_info.wallet_id, seed, stellar_send_error)) { memzero(seed, sizeof(seed)); @@ -811,39 +665,27 @@ static bool sign_txn(der_sig_t *der_signature) { set_app_flow_status(STELLAR_SIGN_TXN_STATUS_SEED_GENERATED); - // Create HDNode from seed and derive private key + // Create HDNode from seed and derive private key using derive_hdnode_from_path HDNode node = {0}; - if (hdnode_from_seed(seed, 64, "ed25519", &node) != 1) { + if (!derive_hdnode_from_path(stellar_txn_context->init_info.derivation_path, + stellar_txn_context->init_info.derivation_path_count, + ED25519_NAME, + seed, + &node)) { stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 1); memzero(seed, sizeof(seed)); memzero(&node, sizeof(HDNode)); return false; } - // Derive to correct path - for (uint32_t i = 0; i < stellar_txn_context->init_info.derivation_path_count; - i++) { - if (hdnode_private_ckd( - &node, stellar_txn_context->init_info.derivation_path[i]) != 1) { - stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 1); - memzero(seed, sizeof(seed)); - memzero(&node, sizeof(HDNode)); - return false; - } - } - - // Fill public key - hdnode_fill_public_key(&node); - - // Create the full transaction envelope - // char envelope_b64[2048] = {0}; - int result = create_signed_transaction_envelope( + // Create signature only (not full envelope) + int result = stellar_create_signature( stellar_txn_context->txn, stellar_txn_context->payment, node.private_key, node.public_key + 1, // Skip first byte for Stellar raw public key STELLAR_NETWORK_MAINNET, - envelope_b64); + signature); if (result != 0) { stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 2); @@ -852,23 +694,14 @@ static bool sign_txn(der_sig_t *der_signature) { return false; } - // Copy the base64 encoded envelope to the output buffer - // Check if the envelope fits in the signature buffer - size_t envelope_len = strlen(envelope_b64); - if (envelope_len > sizeof(der_signature->bytes)) { - stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 3); - memzero(seed, sizeof(seed)); - memzero(&node, sizeof(HDNode)); - return false; - } - - memcpy(der_signature->bytes, envelope_b64, envelope_len); - der_signature->size = envelope_len; + // Copy the 64-byte signature to output buffer + memcpy(der_signature->bytes, signature, STELLAR_SIGNATURE_SIZE); + der_signature->size = STELLAR_SIGNATURE_SIZE; // Clean up sensitive data memzero(seed, sizeof(seed)); memzero(&node, sizeof(HDNode)); - memzero(envelope_b64, sizeof(envelope_b64)); + memzero(signature, sizeof(signature)); return true; } diff --git a/apps/stellar_app/stellar_txn_helpers.c b/apps/stellar_app/stellar_txn_helpers.c index 753db0e1a..a15184a9f 100644 --- a/apps/stellar_app/stellar_txn_helpers.c +++ b/apps/stellar_app/stellar_txn_helpers.c @@ -67,6 +67,7 @@ #include #include "stellar_context.h" +#include "utils.h" /***************************************************************************** * EXTERN VARIABLES @@ -96,44 +97,43 @@ * STATIC FUNCTIONS *****************************************************************************/ -static uint32_t read_uint32_be_pos(const uint8_t *data, int *pos) { - uint32_t value = (data[*pos] << 24) | (data[*pos + 1] << 16) | - (data[*pos + 2] << 8) | data[*pos + 3]; - *pos += 4; +static uint32_t read_uint32_be_offset(const uint8_t *data, int *offset) { + uint32_t value = U32_READ_BE_ARRAY(data + *offset); + *offset += 4; return value; } -static uint64_t read_uint64_be_pos(const uint8_t *data, int *pos) { - uint64_t high = read_uint32_be_pos(data, pos); - uint64_t low = read_uint32_be_pos(data, pos); +static uint64_t read_uint64_be_offset(const uint8_t *data, int *offset) { + uint64_t high = read_uint32_be_offset(data, offset); + uint64_t low = read_uint32_be_offset(data, offset); return (high << 32) | low; } -static void read_account_pos(const uint8_t *data, int *pos, uint8_t *account) { - memcpy(account, data + *pos, 32); - *pos += 32; +static void read_account_offset(const uint8_t *data, int *offset, uint8_t *account) { + memcpy(account, data + *offset, STELLAR_PUBKEY_RAW_SIZE); + *offset += STELLAR_PUBKEY_RAW_SIZE; } -static int read_string_pos(const uint8_t *data, - int *pos, +static int read_string_offset(const uint8_t *data, + int *offset, char *str, int max_len, int data_len) { - if (*pos + 4 > data_len) + if (*offset + 4 > data_len) return -1; - uint32_t len = read_uint32_be_pos(data, pos); - if (len >= max_len || *pos + len > data_len) + uint32_t len = read_uint32_be_offset(data, offset); + if (len >= max_len || *offset + len > data_len) return -1; // Calculate padded length (round up to 4-byte boundary) int padded_len = ((len + 3) / 4) * 4; - if (*pos + padded_len > data_len) + if (*offset + padded_len > data_len) return -1; - memcpy(str, data + *pos, len); + memcpy(str, data + *offset, len); str[len] = '\0'; - *pos += padded_len; // Skip data + padding in one go + *offset += padded_len; // Skip data + padding in one go return len; } @@ -142,21 +142,20 @@ static int read_string_pos(const uint8_t *data, * GLOBAL FUNCTIONS *****************************************************************************/ -// Helper function 1: Parse memo data static int parse_memo_data(const uint8_t *xdr, - int *pos, + int *offset, int xdr_len, stellar_transaction_t *tx) { - tx->memo_type = (stellar_memo_type_t)read_uint32_be_pos(xdr, pos); + tx->memo_type = (stellar_memo_type_t)read_uint32_be_offset(xdr, offset); switch (tx->memo_type) { - case MEMO_NONE: + case STELLAR_MEMO_NONE: break; - case MEMO_TEXT: { + case STELLAR_MEMO_TEXT: { char temp_memo[64]; int memo_len = - read_string_pos(xdr, pos, temp_memo, sizeof(temp_memo), xdr_len); + read_string_offset(xdr, offset, temp_memo, sizeof(temp_memo), xdr_len); if (memo_len < 0) { return -1; } @@ -164,17 +163,17 @@ static int parse_memo_data(const uint8_t *xdr, tx->memo.text[sizeof(tx->memo.text) - 1] = '\0'; } break; - case MEMO_ID: - tx->memo.id = read_uint64_be_pos(xdr, pos); + case STELLAR_MEMO_ID: + tx->memo.id = read_uint64_be_offset(xdr, offset); break; - case MEMO_HASH: - case MEMO_RETURN: - if (*pos + 32 > xdr_len) { + case STELLAR_MEMO_HASH: + case STELLAR_MEMO_RETURN: + if (*offset + 32 > xdr_len) { return -1; } - memcpy(tx->memo.hash, xdr + *pos, 32); - *pos += 32; + memcpy(tx->memo.hash, xdr + *offset, 32); + *offset += 32; break; default: @@ -184,67 +183,57 @@ static int parse_memo_data(const uint8_t *xdr, return 0; } -// Helper function 2: Parse operation data -// Parse XDR transaction envelope format -// See -// https://developers.stellar.org/docs/learn/encyclopedia/data-format/xdr static int parse_operation_data(const uint8_t *xdr, - int *pos, + int *offset, int xdr_len, stellar_transaction_t *tx, stellar_payment_t *payment) { // Parse Operations count - tx->operation_count = read_uint32_be_pos(xdr, pos); + tx->operation_count = read_uint32_be_offset(xdr, offset); if (tx->operation_count == 0) { return -1; } // Parse First Operation - uint32_t has_source_account = read_uint32_be_pos(xdr, pos); + uint32_t has_source_account = read_uint32_be_offset(xdr, offset); if (has_source_account == 1) { - *pos += 36; + *offset += 36; // Skip source account (4 bytes type + 32 bytes key) } else if (has_source_account != 0) { return -1; } // Parse operation type - uint32_t operation_type = read_uint32_be_pos(xdr, pos); + uint32_t operation_type = read_uint32_be_offset(xdr, offset); - if (operation_type != 1 && operation_type != 0) { + if (operation_type != STELLAR_OPERATION_PAYMENT && + operation_type != STELLAR_OPERATION_CREATE_ACCOUNT) { return -1; } - tx->operation_type = operation_type; + tx->operation_type = (stellar_operation_type_t)operation_type; - if (operation_type == 0) { // CREATE_ACCOUNT - uint32_t dest_account_type = read_uint32_be_pos(xdr, pos); - if (dest_account_type != 0) { - return -1; - } - read_account_pos(xdr, pos, payment->destination); - payment->amount = read_uint64_be_pos(xdr, pos); - return 0; // Early return for CREATE_ACCOUNT - } - - // PAYMENT Operation - uint32_t dest_account_type = read_uint32_be_pos(xdr, pos); - if (dest_account_type != 0) { + // Parse destination account (common for both operations) + uint32_t dest_account_type = read_uint32_be_offset(xdr, offset); + if (dest_account_type != STELLAR_KEY_TYPE_ED25519) { return -1; } + read_account_offset(xdr, offset, payment->destination); - read_account_pos(xdr, pos, payment->destination); - - uint32_t asset_type = read_uint32_be_pos(xdr, pos); - if (asset_type != 0) { - return -1; + // For PAYMENT operations, we need to parse the asset type + if (operation_type == STELLAR_OPERATION_PAYMENT) { + uint32_t asset_type = read_uint32_be_offset(xdr, offset); + if (asset_type != STELLAR_ASSET_TYPE_NATIVE) { + return -1; + } } - payment->amount = read_uint64_be_pos(xdr, pos); + // Parse amount (common for both operations) + payment->amount = read_uint64_be_offset(xdr, offset); + return 0; } -// Main function - much simpler now int stellar_parse_transaction(const uint8_t *xdr, int xdr_len, stellar_transaction_t *tx, @@ -253,58 +242,54 @@ int stellar_parse_transaction(const uint8_t *xdr, return -1; } - int pos = 0; + int offset = 0; - // Clear structures memset(tx, 0, sizeof(stellar_transaction_t)); memset(payment, 0, sizeof(stellar_payment_t)); - // 1. Parse Envelope Type (4 bytes) - // ENVELOPE_TYPE_TX = 2 - // See - // https://github.com/stellar/stellar-protocol/blob/master/core/cap-0015.md - uint32_t envelope_type = read_uint32_be_pos(xdr, &pos); - if (envelope_type != 2) { + // Parse Envelope Type (4 bytes) + uint32_t envelope_type = read_uint32_be_offset(xdr, &offset); + if (envelope_type != STELLAR_ENVELOPE_TYPE_TX) { return -1; } - // 2. Parse Source Account - uint32_t source_account_type = read_uint32_be_pos(xdr, &pos); - if (source_account_type != 0) { + // Parse Source Account + uint32_t source_account_type = read_uint32_be_offset(xdr, &offset); + if (source_account_type != STELLAR_KEY_TYPE_ED25519) { return -1; } - read_account_pos(xdr, &pos, tx->source_account); + read_account_offset(xdr, &offset, tx->source_account); - // 3. Parse Fee (4 bytes) - tx->fee = read_uint32_be_pos(xdr, &pos); + // Parse Fee (4 bytes) + tx->fee = read_uint32_be_offset(xdr, &offset); - // 4. Parse Sequence Number (8 bytes) - tx->sequence_number = read_uint64_be_pos(xdr, &pos); + // Parse Sequence Number (8 bytes) + tx->sequence_number = read_uint64_be_offset(xdr, &offset); - // 5. Parse Preconditions - uint32_t preconditions_type = read_uint32_be_pos(xdr, &pos); + // Parse Preconditions + uint32_t preconditions_type = read_uint32_be_offset(xdr, &offset); if (preconditions_type == 1) { - pos += 16; + offset += 16; // Skip time bounds (8 + 8 bytes) } else if (preconditions_type != 0) { return -1; } - // 6. Parse Memo - if (parse_memo_data(xdr, &pos, xdr_len, tx) != 0) { + // Parse Memo + if (parse_memo_data(xdr, &offset, xdr_len, tx) != 0) { return -1; } - // 7. Parse Operations - int result = parse_operation_data(xdr, &pos, xdr_len, tx, payment); + // Parse Operations + int result = parse_operation_data(xdr, &offset, xdr_len, tx, payment); if (result != 0) { return result; } - // 8. Skip transaction extension - if (pos + 4 <= xdr_len) { - read_uint32_be_pos(xdr, &pos); + // Skip transaction extension + if (offset + 4 <= xdr_len) { + read_uint32_be_offset(xdr, &offset); } return 0; diff --git a/common/libraries/util/utils.c b/common/libraries/util/utils.c index 5f96328d9..3441faf76 100644 --- a/common/libraries/util/utils.c +++ b/common/libraries/util/utils.c @@ -668,3 +668,15 @@ uint8_t string_to_escaped_string(const char *input, return 0; } } + +void write_uint32_be(uint8_t *buffer, uint32_t value) { + buffer[0] = (value >> 24) & 0xFF; + buffer[1] = (value >> 16) & 0xFF; + buffer[2] = (value >> 8) & 0xFF; + buffer[3] = value & 0xFF; +} + +void write_uint64_be(uint8_t *buffer, uint64_t value) { + write_uint32_be(buffer, (uint32_t)(value >> 32)); + write_uint32_be(buffer + 4, (uint32_t)(value & 0xFFFFFFFF)); +} \ No newline at end of file diff --git a/common/libraries/util/utils.h b/common/libraries/util/utils.h index 36b93ad5f..a5703c2f6 100644 --- a/common/libraries/util/utils.h +++ b/common/libraries/util/utils.h @@ -480,4 +480,19 @@ uint8_t UTIL_CheckBound(const uint8_t *pBaseAddr, uint8_t string_to_escaped_string(const char *input, char *escaped_string, size_t out_len); + +/** + * @brief Write 32-bit value in big-endian format + * @param buffer Output buffer + * @param value 32-bit value to write + */ +void write_uint32_be(uint8_t *buffer, uint32_t value); + +/** + * @brief Write 64-bit value in big-endian format + * @param buffer Output buffer + * @param value 64-bit value to write + */ +void write_uint64_be(uint8_t *buffer, uint64_t value); + #endif diff --git a/src/constant_texts.c b/src/constant_texts.c index 7c7399fff..1fbd5508a 100644 --- a/src/constant_texts.c +++ b/src/constant_texts.c @@ -584,3 +584,10 @@ const char *ui_text_verify_account_id = "Verify account id"; #ifdef ALLOW_LOG_EXPORT const char *ui_text_send_logs_prompt = "Send logs to the cySync app?"; #endif + +// Stellar memo display texts +const char *ui_text_stellar_memo_text = "Memo: \"%s\""; +const char *ui_text_stellar_memo_id = "Memo ID: %llu"; +const char *ui_text_stellar_memo_hash = "Memo Hash: %s"; +const char *ui_text_stellar_operation = "Operation: %s"; +const char *ui_text_stellar_memo_unknown = "Memo: (unknown type %u)"; \ No newline at end of file diff --git a/src/constant_texts.h b/src/constant_texts.h index b4af96512..5fb89f6b6 100644 --- a/src/constant_texts.h +++ b/src/constant_texts.h @@ -418,4 +418,18 @@ extern const char *ui_text_verify_account_id; #ifdef ALLOW_LOG_EXPORT extern const char *ui_text_send_logs_prompt; #endif -#endif // CONSTANT_TEXTS_H + +// Stellar memo display texts +#define UI_TEXT_STELLAR_MEMO_TEXT "Memo: \"%s\"" +#define UI_TEXT_STELLAR_MEMO_ID "Memo ID: %llu" +#define UI_TEXT_STELLAR_MEMO_HASH "Memo Hash: %s" +#define UI_TEXT_STELLAR_OPERATION "Operation: %s" +#define UI_TEXT_STELLAR_MEMO_UNKNOWN "Memo: (unknown type %u)" + +extern const char *ui_text_stellar_memo_text; +extern const char *ui_text_stellar_memo_id; +extern const char *ui_text_stellar_memo_hash; +extern const char *ui_text_stellar_operation; +extern const char *ui_text_stellar_memo_unknown; + +#endif // CONSTANT_TEXTS_H \ No newline at end of file From 5c155b42b6a0036072b25c1f8a0148af2f5d0cfa Mon Sep 17 00:00:00 2001 From: Keyur279 Date: Wed, 30 Jul 2025 05:26:46 +0530 Subject: [PATCH 12/23] feat: Address stellar review feedback --- apps/stellar_app/stellar_context.h | 8 +++++--- apps/stellar_app/stellar_helpers.c | 12 +++++++---- apps/stellar_app/stellar_helpers.h | 5 +++-- apps/stellar_app/stellar_txn.c | 28 ++++++++++++++++---------- apps/stellar_app/stellar_txn_helpers.c | 24 ++++++++++++---------- common/libraries/util/utils.h | 2 +- src/constant_texts.h | 2 +- 7 files changed, 48 insertions(+), 33 deletions(-) diff --git a/apps/stellar_app/stellar_context.h b/apps/stellar_app/stellar_context.h index ddb3ae5ff..d25169d8d 100644 --- a/apps/stellar_app/stellar_context.h +++ b/apps/stellar_app/stellar_context.h @@ -50,7 +50,8 @@ typedef struct { } stellar_config_t; // Stellar Memo types -// See https://developers.stellar.org/docs/learn/encyclopedia/transactions-specialized/memos +// See +// https://developers.stellar.org/docs/learn/encyclopedia/transactions-specialized/memos typedef enum { STELLAR_MEMO_NONE = 0, STELLAR_MEMO_TEXT = 1, @@ -59,8 +60,9 @@ typedef enum { STELLAR_MEMO_RETURN = 4 } stellar_memo_type_t; -// Stellar operation types -// See https://developers.stellar.org/docs/learn/fundamentals/transactions/list-of-operations +// Stellar operation types +// See +// https://developers.stellar.org/docs/learn/fundamentals/transactions/list-of-operations typedef enum { STELLAR_OPERATION_CREATE_ACCOUNT = 0, STELLAR_OPERATION_PAYMENT = 1 diff --git a/apps/stellar_app/stellar_helpers.c b/apps/stellar_app/stellar_helpers.c index 0be58a9d2..9daff601b 100644 --- a/apps/stellar_app/stellar_helpers.c +++ b/apps/stellar_app/stellar_helpers.c @@ -140,18 +140,22 @@ bool stellar_generate_address(const uint8_t *public_key, char *address) { } // Stellar address encoding (StrKey format) - // See https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0023.md + // See + // https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0023.md uint8_t payload[35]; - payload[0] = 0x30; // Account ID version byte (6 << 3 | 0 = STRKEY_PUBKEY OR STRKEY_ALG_ED25519) + payload[0] = 0x30; // Account ID version byte (6 << 3 | 0 = STRKEY_PUBKEY + // OR STRKEY_ALG_ED25519) memcpy(payload + 1, public_key, STELLAR_PUBKEY_RAW_SIZE); // CRC16-XModem checksum calculation - // See https://stellar.stackexchange.com/questions/255/which-cryptographic-algorithm-is-used-to-generate-the-secret-and-public-keys + // See + // https://stellar.stackexchange.com/questions/255/which-cryptographic-algorithm-is-used-to-generate-the-secret-and-public-keys uint16_t checksum = crc16(payload, 33); payload[33] = checksum & 0xFF; payload[34] = checksum >> 8; // RFC4648 base32 encoding without padding - base32_encode(payload, 35, address, STELLAR_ADDRESS_LENGTH, BASE32_ALPHABET_RFC4648); + base32_encode( + payload, 35, address, STELLAR_ADDRESS_LENGTH, BASE32_ALPHABET_RFC4648); return true; } \ No newline at end of file diff --git a/apps/stellar_app/stellar_helpers.h b/apps/stellar_app/stellar_helpers.h index ec422daf5..5ab0c5991 100644 --- a/apps/stellar_app/stellar_helpers.h +++ b/apps/stellar_app/stellar_helpers.h @@ -64,9 +64,10 @@ bool stellar_derivation_path_guard(const uint32_t *path, uint8_t levels); * @brief Generates a Stellar address from a public key * @details Follows the Stellar address generation algorithm: * 1. Creates a payload with account ID type (0x30) and the public key - * 2. Calculates CRC16 checksum + * 2. Calculates CRC16 checksum * 3. Encodes the result using base32 - * See https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0023.md + * See + * https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0023.md * * @param public_key The 32-byte ED25519 public key * @param address Buffer to store the resulting address (must be at least diff --git a/apps/stellar_app/stellar_txn.c b/apps/stellar_app/stellar_txn.c index f4db89cab..79cd502ea 100644 --- a/apps/stellar_app/stellar_txn.c +++ b/apps/stellar_app/stellar_txn.c @@ -459,7 +459,9 @@ static bool get_user_verification(void) { snprintf(operation_display, sizeof(operation_display), ui_text_stellar_operation, - decoded_txn->operation_type == STELLAR_OPERATION_CREATE_ACCOUNT ? "CREATE_ACCOUNT" : "PAYMENT"); + decoded_txn->operation_type == STELLAR_OPERATION_CREATE_ACCOUNT + ? "CREATE_ACCOUNT" + : "PAYMENT"); if (!core_confirmation(operation_display, stellar_send_error)) { return false; } @@ -578,7 +580,8 @@ static int create_signature_base(const char *network_passphrase, } else if (tx->memo_type == STELLAR_MEMO_ID) { write_uint64_be(signature_base + offset, tx->memo.id); offset += 8; - } else if (tx->memo_type == STELLAR_MEMO_HASH || tx->memo_type == STELLAR_MEMO_RETURN) { + } else if (tx->memo_type == STELLAR_MEMO_HASH || + tx->memo_type == STELLAR_MEMO_RETURN) { memcpy(signature_base + offset, tx->memo.hash, 32); offset += 32; } @@ -599,15 +602,16 @@ static int create_signature_base(const char *network_passphrase, // Destination (type + pubkey) write_uint32_be(signature_base + offset, STELLAR_KEY_TYPE_ED25519); offset += 4; - memcpy(signature_base + offset, payment->destination, STELLAR_PUBKEY_RAW_SIZE); + memcpy( + signature_base + offset, payment->destination, STELLAR_PUBKEY_RAW_SIZE); offset += STELLAR_PUBKEY_RAW_SIZE; - + // Asset (native) - only for PAYMENT operations if (tx->operation_type == STELLAR_OPERATION_PAYMENT) { write_uint32_be(signature_base + offset, STELLAR_ASSET_TYPE_NATIVE); offset += 4; } - + // Amount write_uint64_be(signature_base + offset, payment->amount); offset += 8; @@ -665,13 +669,15 @@ static bool sign_txn(der_sig_t *der_signature) { set_app_flow_status(STELLAR_SIGN_TXN_STATUS_SEED_GENERATED); - // Create HDNode from seed and derive private key using derive_hdnode_from_path + // Create HDNode from seed and derive private key using + // derive_hdnode_from_path HDNode node = {0}; - if (!derive_hdnode_from_path(stellar_txn_context->init_info.derivation_path, - stellar_txn_context->init_info.derivation_path_count, - ED25519_NAME, - seed, - &node)) { + if (!derive_hdnode_from_path( + stellar_txn_context->init_info.derivation_path, + stellar_txn_context->init_info.derivation_path_count, + ED25519_NAME, + seed, + &node)) { stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 1); memzero(seed, sizeof(seed)); memzero(&node, sizeof(HDNode)); diff --git a/apps/stellar_app/stellar_txn_helpers.c b/apps/stellar_app/stellar_txn_helpers.c index a15184a9f..29812e324 100644 --- a/apps/stellar_app/stellar_txn_helpers.c +++ b/apps/stellar_app/stellar_txn_helpers.c @@ -109,16 +109,18 @@ static uint64_t read_uint64_be_offset(const uint8_t *data, int *offset) { return (high << 32) | low; } -static void read_account_offset(const uint8_t *data, int *offset, uint8_t *account) { +static void read_account_offset(const uint8_t *data, + int *offset, + uint8_t *account) { memcpy(account, data + *offset, STELLAR_PUBKEY_RAW_SIZE); *offset += STELLAR_PUBKEY_RAW_SIZE; } static int read_string_offset(const uint8_t *data, - int *offset, - char *str, - int max_len, - int data_len) { + int *offset, + char *str, + int max_len, + int data_len) { if (*offset + 4 > data_len) return -1; @@ -154,8 +156,8 @@ static int parse_memo_data(const uint8_t *xdr, case STELLAR_MEMO_TEXT: { char temp_memo[64]; - int memo_len = - read_string_offset(xdr, offset, temp_memo, sizeof(temp_memo), xdr_len); + int memo_len = read_string_offset( + xdr, offset, temp_memo, sizeof(temp_memo), xdr_len); if (memo_len < 0) { return -1; } @@ -199,7 +201,7 @@ static int parse_operation_data(const uint8_t *xdr, uint32_t has_source_account = read_uint32_be_offset(xdr, offset); if (has_source_account == 1) { - *offset += 36; // Skip source account (4 bytes type + 32 bytes key) + *offset += 36; // Skip source account (4 bytes type + 32 bytes key) } else if (has_source_account != 0) { return -1; } @@ -207,7 +209,7 @@ static int parse_operation_data(const uint8_t *xdr, // Parse operation type uint32_t operation_type = read_uint32_be_offset(xdr, offset); - if (operation_type != STELLAR_OPERATION_PAYMENT && + if (operation_type != STELLAR_OPERATION_PAYMENT && operation_type != STELLAR_OPERATION_CREATE_ACCOUNT) { return -1; } @@ -230,7 +232,7 @@ static int parse_operation_data(const uint8_t *xdr, // Parse amount (common for both operations) payment->amount = read_uint64_be_offset(xdr, offset); - + return 0; } @@ -271,7 +273,7 @@ int stellar_parse_transaction(const uint8_t *xdr, uint32_t preconditions_type = read_uint32_be_offset(xdr, &offset); if (preconditions_type == 1) { - offset += 16; // Skip time bounds (8 + 8 bytes) + offset += 16; // Skip time bounds (8 + 8 bytes) } else if (preconditions_type != 0) { return -1; } diff --git a/common/libraries/util/utils.h b/common/libraries/util/utils.h index a5703c2f6..8e25a2451 100644 --- a/common/libraries/util/utils.h +++ b/common/libraries/util/utils.h @@ -489,7 +489,7 @@ uint8_t string_to_escaped_string(const char *input, void write_uint32_be(uint8_t *buffer, uint32_t value); /** - * @brief Write 64-bit value in big-endian format + * @brief Write 64-bit value in big-endian format * @param buffer Output buffer * @param value 64-bit value to write */ diff --git a/src/constant_texts.h b/src/constant_texts.h index 5fb89f6b6..e64ecf4a3 100644 --- a/src/constant_texts.h +++ b/src/constant_texts.h @@ -421,7 +421,7 @@ extern const char *ui_text_send_logs_prompt; // Stellar memo display texts #define UI_TEXT_STELLAR_MEMO_TEXT "Memo: \"%s\"" -#define UI_TEXT_STELLAR_MEMO_ID "Memo ID: %llu" +#define UI_TEXT_STELLAR_MEMO_ID "Memo ID: %llu" #define UI_TEXT_STELLAR_MEMO_HASH "Memo Hash: %s" #define UI_TEXT_STELLAR_OPERATION "Operation: %s" #define UI_TEXT_STELLAR_MEMO_UNKNOWN "Memo: (unknown type %u)" From 6207e648393e4cc83a695017208dd06362e010f0 Mon Sep 17 00:00:00 2001 From: Keyur279 Date: Wed, 30 Jul 2025 08:46:25 +0530 Subject: [PATCH 13/23] feat: Add reference for signature creation --- apps/stellar_app/stellar_txn.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/stellar_app/stellar_txn.c b/apps/stellar_app/stellar_txn.c index 79cd502ea..e5e68d87d 100644 --- a/apps/stellar_app/stellar_txn.c +++ b/apps/stellar_app/stellar_txn.c @@ -214,6 +214,8 @@ static int create_signature_base(const char *network_passphrase, /** * @brief Creates ED25519 signature for Stellar transaction * @details Signs the transaction hash using ED25519 algorithm + * See + * https://github.com/trezor/trezor-firmware/blob/main/core/src/apps/stellar/sign_tx.py * * @param tx Parsed transaction structure * @param payment Parsed payment operation structure From fefd57026bca8be8a05fa3c1eff43d03858cc085 Mon Sep 17 00:00:00 2001 From: Keyur279 Date: Mon, 4 Aug 2025 11:55:28 +0530 Subject: [PATCH 14/23] chore: Address feedbacks remove re-parsing of xdr --- apps/stellar_app/stellar_priv.h | 1 + apps/stellar_app/stellar_txn.c | 182 ++++-------------- apps/stellar_app/stellar_txn_helpers.c | 101 ++++++---- apps/stellar_app/stellar_txn_helpers.h | 3 +- common/libraries/util/utils.h | 16 +- common/proto-options/stellar/sign_txn.options | 2 +- src/constant_texts.c | 11 +- src/constant_texts.h | 18 +- 8 files changed, 112 insertions(+), 222 deletions(-) diff --git a/apps/stellar_app/stellar_priv.h b/apps/stellar_app/stellar_priv.h index 889fa8290..c08157598 100644 --- a/apps/stellar_app/stellar_priv.h +++ b/apps/stellar_app/stellar_priv.h @@ -34,6 +34,7 @@ typedef struct { // decoded transaction structures stellar_transaction_t *txn; stellar_payment_t *payment; + size_t signature_data_len; } stellar_txn_context_t; /***************************************************************************** diff --git a/apps/stellar_app/stellar_txn.c b/apps/stellar_app/stellar_txn.c index e5e68d87d..ffda03c42 100644 --- a/apps/stellar_app/stellar_txn.c +++ b/apps/stellar_app/stellar_txn.c @@ -95,7 +95,7 @@ /***************************************************************************** * PRIVATE TYPEDEFS *****************************************************************************/ -typedef stellar_sign_txn_signature_response_signature_t der_sig_t; +typedef stellar_sign_txn_signature_response_signature_t stellar_sig_t; /***************************************************************************** * STATIC FUNCTION PROTOTYPES @@ -191,26 +191,25 @@ static bool get_user_verification(void); * @return false If signature could not be computed - maybe due to some error * during seed reconstruction phase */ -static bool sign_txn(der_sig_t *der_signature); +static bool sign_txn(stellar_sig_t *der_signature); /** * @brief Creates signature base data for Stellar transaction signing - * @details Builds the standardized data structure that must be signed according - * to Stellar protocol specifications + * @details Combines network passphrase hash with truncated XDR transaction data * * @param network_passphrase Network passphrase string (mainnet/testnet) - * @param tx Parsed transaction structure - * @param payment Parsed payment operation structure + * @param transaction_xdr Raw XDR transaction data + * @param signature_data_len Length of signature-relevant XDR data * @param signature_base Output buffer for signature base data * @param base_len Output length of signature base data * @return int 0 on success, non-zero on error */ + static int create_signature_base(const char *network_passphrase, - stellar_transaction_t *tx, - stellar_payment_t *payment, + uint8_t *transaction_xdr, + size_t signature_data_len, uint8_t *signature_base, int *base_len); - /** * @brief Creates ED25519 signature for Stellar transaction * @details Signs the transaction hash using ED25519 algorithm @@ -244,7 +243,7 @@ static int stellar_create_signature(stellar_transaction_t *tx, * or invalid request received from the host */ static bool send_signature(stellar_query_t *query, - const der_sig_t *der_signature); + const stellar_sig_t *der_signature); /***************************************************************************** * STATIC VARIABLES @@ -381,14 +380,17 @@ static bool fetch_valid_input(stellar_query_t *query) { stellar_txn_context->payment = (stellar_payment_t *)malloc(sizeof(stellar_payment_t)); + int signature_data_len = 0; if (stellar_parse_transaction(stellar_txn_context->transaction, total_size, stellar_txn_context->txn, - stellar_txn_context->payment) != 0) { + stellar_txn_context->payment, + &signature_data_len) != 0) { stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, ERROR_DATA_FLOW_INVALID_DATA); return false; } + stellar_txn_context->signature_data_len = signature_data_len; return true; } @@ -398,14 +400,14 @@ static bool show_memo_details(const stellar_transaction_t *decoded_txn) { char memo_display[100] = {'\0'}; snprintf(memo_display, sizeof(memo_display), - ui_text_stellar_memo_text, + ui_text_memo_text, decoded_txn->memo.text); return core_confirmation(memo_display, stellar_send_error); } else if (decoded_txn->memo_type == STELLAR_MEMO_ID) { char memo_display[50] = {'\0'}; snprintf(memo_display, sizeof(memo_display), - ui_text_stellar_memo_id, + ui_text_memo_id, decoded_txn->memo.id); return core_confirmation(memo_display, stellar_send_error); } else if (decoded_txn->memo_type == STELLAR_MEMO_NONE) { @@ -415,7 +417,7 @@ static bool show_memo_details(const stellar_transaction_t *decoded_txn) { decoded_txn->memo_type == STELLAR_MEMO_RETURN) { char memo_hash[80] = {'\0'}; char temp[3]; - strcpy(memo_hash, "Memo Hash: "); + strcpy(memo_hash, ui_text_memo_hash_prefix); for (int i = 0; i < 32; i++) { snprintf(temp, sizeof(temp), "%02x", decoded_txn->memo.hash[i]); strcat(memo_hash, temp); @@ -425,7 +427,7 @@ static bool show_memo_details(const stellar_transaction_t *decoded_txn) { char memo_display[50] = {'\0'}; snprintf(memo_display, sizeof(memo_display), - ui_text_stellar_memo_unknown, + ui_text_memo_unknown, decoded_txn->memo_type); return core_confirmation(memo_display, stellar_send_error); } @@ -436,7 +438,6 @@ static bool get_user_verification(void) { const stellar_payment_t *payment = stellar_txn_context->payment; char to_address[STELLAR_ADDRESS_LENGTH] = ""; - char from_address[STELLAR_ADDRESS_LENGTH] = ""; // Generate addresses for display if (!stellar_generate_address(payment->destination, to_address)) { @@ -444,11 +445,6 @@ static bool get_user_verification(void) { return false; } - if (!stellar_generate_address(decoded_txn->source_account, from_address)) { - stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 2); - return false; - } - // Exchange validation if (use_signature_verification) { if (!exchange_validate_stored_signature(to_address, sizeof(to_address))) { @@ -456,23 +452,6 @@ static bool get_user_verification(void) { } } - // Show operation type - char operation_display[50] = {'\0'}; - snprintf(operation_display, - sizeof(operation_display), - ui_text_stellar_operation, - decoded_txn->operation_type == STELLAR_OPERATION_CREATE_ACCOUNT - ? "CREATE_ACCOUNT" - : "PAYMENT"); - if (!core_confirmation(operation_display, stellar_send_error)) { - return false; - } - - // Show from address - if (!core_scroll_page("From:", from_address, stellar_send_error)) { - return false; - } - // Show destination address if (!core_scroll_page( ui_text_verify_address, to_address, stellar_send_error)) { @@ -504,16 +483,6 @@ static bool get_user_verification(void) { return false; } - // Show sequence number - char seq_display[50] = {'\0'}; - snprintf(seq_display, - sizeof(seq_display), - "Sequence: %llu", - decoded_txn->sequence_number); - if (!core_confirmation(seq_display, stellar_send_error)) { - return false; - } - // Handle memo display if (!show_memo_details(decoded_txn)) { return false; @@ -524,105 +493,21 @@ static bool get_user_verification(void) { } static int create_signature_base(const char *network_passphrase, - stellar_transaction_t *tx, - stellar_payment_t *payment, + uint8_t *transaction_xdr, + size_t signature_data_len, uint8_t *signature_base, int *base_len) { - int offset = 0; - - // 1. Network passphrase hash (32 bytes) + // 1. Hash network passphrase uint8_t network_hash[32]; sha256_Raw((const uint8_t *)network_passphrase, strlen(network_passphrase), network_hash); - memcpy(signature_base + offset, network_hash, 32); - offset += 32; - - // 2. ENVELOPE_TYPE_TX (4 bytes) - write_uint32_be(signature_base + offset, STELLAR_ENVELOPE_TYPE_TX); - offset += 4; - - // 3. Source account type (4 bytes) + public key (32 bytes) - write_uint32_be(signature_base + offset, STELLAR_KEY_TYPE_ED25519); - offset += 4; - memcpy(signature_base + offset, tx->source_account, STELLAR_PUBKEY_RAW_SIZE); - offset += STELLAR_PUBKEY_RAW_SIZE; - - // 4. Fee (4 bytes, big-endian) - write_uint32_be(signature_base + offset, tx->fee); - offset += 4; - - // 5. Sequence number (8 bytes, big-endian) - write_uint64_be(signature_base + offset, tx->sequence_number); - offset += 8; - - // 6. Time bounds (17 bytes total) - write_uint32_be(signature_base + offset, 1); // has time bounds = true - offset += 4; - write_uint64_be(signature_base + offset, 0); // min_time = 0 - offset += 8; - write_uint64_be(signature_base + offset, 0); // max_time = 0 - offset += 8; - - // 7. Memo - write_uint32_be(signature_base + offset, tx->memo_type); - offset += 4; - - if (tx->memo_type == STELLAR_MEMO_TEXT) { - int memo_len = strlen(tx->memo.text); - write_uint32_be(signature_base + offset, memo_len); - offset += 4; - memcpy(signature_base + offset, tx->memo.text, memo_len); - offset += memo_len; - // Add padding to 4-byte boundary - int padding = (4 - (memo_len % 4)) % 4; - for (int i = 0; i < padding; i++) { - signature_base[offset++] = 0; - } - } else if (tx->memo_type == STELLAR_MEMO_ID) { - write_uint64_be(signature_base + offset, tx->memo.id); - offset += 8; - } else if (tx->memo_type == STELLAR_MEMO_HASH || - tx->memo_type == STELLAR_MEMO_RETURN) { - memcpy(signature_base + offset, tx->memo.hash, 32); - offset += 32; - } - - // 8. Operations count (4 bytes) - write_uint32_be(signature_base + offset, 1); - offset += 4; - - // 9. Operation: has_source_account = false - write_uint32_be(signature_base + offset, 0); - offset += 4; - - // 10. Operation type = PAYMENT OR CREATE_ACCOUNT - write_uint32_be(signature_base + offset, tx->operation_type); - offset += 4; - - // 11. Payment/CreateAccount operation - // Destination (type + pubkey) - write_uint32_be(signature_base + offset, STELLAR_KEY_TYPE_ED25519); - offset += 4; - memcpy( - signature_base + offset, payment->destination, STELLAR_PUBKEY_RAW_SIZE); - offset += STELLAR_PUBKEY_RAW_SIZE; - - // Asset (native) - only for PAYMENT operations - if (tx->operation_type == STELLAR_OPERATION_PAYMENT) { - write_uint32_be(signature_base + offset, STELLAR_ASSET_TYPE_NATIVE); - offset += 4; - } - - // Amount - write_uint64_be(signature_base + offset, payment->amount); - offset += 8; - // 12. Extension - write_uint32_be(signature_base + offset, 0); - offset += 4; + // 2. Combine: network_hash + truncated_xdr (only signature-relevant part) + memcpy(signature_base, network_hash, 32); + memcpy(signature_base + 32, transaction_xdr, signature_data_len); - *base_len = offset; + *base_len = 32 + signature_data_len; return 0; } @@ -640,8 +525,12 @@ static int stellar_create_signature(stellar_transaction_t *tx, ? TESTNET_PASSPHRASE : MAINNET_PASSPHRASE; - int result = - create_signature_base(passphrase, tx, payment, signature_base, &base_len); + int result = create_signature_base(passphrase, + stellar_txn_context->transaction, + stellar_txn_context->signature_data_len, + signature_base, + &base_len); + if (result != 0) { return result; } @@ -658,7 +547,7 @@ static int stellar_create_signature(stellar_transaction_t *tx, return 0; } -static bool sign_txn(der_sig_t *der_signature) { +static bool sign_txn(stellar_sig_t *der_signature) { uint8_t seed[64] = {0}; uint8_t signature[STELLAR_SIGNATURE_SIZE] = {0}; @@ -715,7 +604,7 @@ static bool sign_txn(der_sig_t *der_signature) { } static bool send_signature(stellar_query_t *query, - const der_sig_t *der_signature) { + const stellar_sig_t *der_signature) { stellar_result_t result = init_stellar_result(STELLAR_RESULT_SIGN_TXN_TAG); result.sign_txn.which_response = STELLAR_SIGN_TXN_RESPONSE_SIGNATURE_TAG; @@ -724,8 +613,9 @@ static bool send_signature(stellar_query_t *query, return false; } - memcpy( - &result.sign_txn.signature.signature, der_signature, sizeof(der_sig_t)); + memcpy(&result.sign_txn.signature.signature, + der_signature, + sizeof(stellar_sig_t)); stellar_send_result(&result); return true; @@ -739,7 +629,7 @@ void stellar_sign_transaction(stellar_query_t *query) { (stellar_txn_context_t *)malloc(sizeof(stellar_txn_context_t)); memzero(stellar_txn_context, sizeof(stellar_txn_context_t)); - der_sig_t der_signature = {0}; + stellar_sig_t der_signature = {0}; if (handle_initiate_query(query) && fetch_valid_input(query) && get_user_verification() && sign_txn(&der_signature) && diff --git a/apps/stellar_app/stellar_txn_helpers.c b/apps/stellar_app/stellar_txn_helpers.c index 29812e324..639d758c1 100644 --- a/apps/stellar_app/stellar_txn_helpers.c +++ b/apps/stellar_app/stellar_txn_helpers.c @@ -97,34 +97,24 @@ * STATIC FUNCTIONS *****************************************************************************/ -static uint32_t read_uint32_be_offset(const uint8_t *data, int *offset) { - uint32_t value = U32_READ_BE_ARRAY(data + *offset); - *offset += 4; - return value; -} - -static uint64_t read_uint64_be_offset(const uint8_t *data, int *offset) { - uint64_t high = read_uint32_be_offset(data, offset); - uint64_t low = read_uint32_be_offset(data, offset); - return (high << 32) | low; -} - -static void read_account_offset(const uint8_t *data, - int *offset, - uint8_t *account) { +static void read_stellar_account(const uint8_t *data, + int *offset, + uint8_t *account) { memcpy(account, data + *offset, STELLAR_PUBKEY_RAW_SIZE); *offset += STELLAR_PUBKEY_RAW_SIZE; } -static int read_string_offset(const uint8_t *data, - int *offset, - char *str, - int max_len, - int data_len) { +static int read_xdr_string(const uint8_t *data, + int *offset, + char *str, + int max_len, + int data_len) { if (*offset + 4 > data_len) return -1; - uint32_t len = read_uint32_be_offset(data, offset); + uint32_t len = U32_READ_BE_ARRAY(data + *offset); + *offset += 4; + if (len >= max_len || *offset + len > data_len) return -1; @@ -148,7 +138,8 @@ static int parse_memo_data(const uint8_t *xdr, int *offset, int xdr_len, stellar_transaction_t *tx) { - tx->memo_type = (stellar_memo_type_t)read_uint32_be_offset(xdr, offset); + tx->memo_type = (stellar_memo_type_t)U32_READ_BE_ARRAY(xdr + *offset); + *offset += 4; switch (tx->memo_type) { case STELLAR_MEMO_NONE: @@ -156,8 +147,8 @@ static int parse_memo_data(const uint8_t *xdr, case STELLAR_MEMO_TEXT: { char temp_memo[64]; - int memo_len = read_string_offset( - xdr, offset, temp_memo, sizeof(temp_memo), xdr_len); + int memo_len = + read_xdr_string(xdr, offset, temp_memo, sizeof(temp_memo), xdr_len); if (memo_len < 0) { return -1; } @@ -166,7 +157,8 @@ static int parse_memo_data(const uint8_t *xdr, } break; case STELLAR_MEMO_ID: - tx->memo.id = read_uint64_be_offset(xdr, offset); + tx->memo.id = U64_READ_BE_ARRAY(xdr + *offset); + *offset += 8; break; case STELLAR_MEMO_HASH: @@ -191,23 +183,38 @@ static int parse_operation_data(const uint8_t *xdr, stellar_transaction_t *tx, stellar_payment_t *payment) { // Parse Operations count - tx->operation_count = read_uint32_be_offset(xdr, offset); + tx->operation_count = U32_READ_BE_ARRAY(xdr + *offset); + *offset += 4; - if (tx->operation_count == 0) { + if (tx->operation_count != 1) { return -1; } // Parse First Operation - uint32_t has_source_account = read_uint32_be_offset(xdr, offset); + uint32_t has_source_account = U32_READ_BE_ARRAY(xdr + *offset); + *offset += 4; if (has_source_account == 1) { - *offset += 36; // Skip source account (4 bytes type + 32 bytes key) + uint32_t op_source_type = U32_READ_BE_ARRAY(xdr + *offset); + *offset += 4; + + if (op_source_type != STELLAR_KEY_TYPE_ED25519) { + return -1; + } + + if (memcmp(xdr + *offset, tx->source_account, STELLAR_PUBKEY_RAW_SIZE) != + 0) { + return -1; + } + + *offset += STELLAR_PUBKEY_RAW_SIZE; } else if (has_source_account != 0) { return -1; } // Parse operation type - uint32_t operation_type = read_uint32_be_offset(xdr, offset); + uint32_t operation_type = U32_READ_BE_ARRAY(xdr + *offset); + *offset += 4; if (operation_type != STELLAR_OPERATION_PAYMENT && operation_type != STELLAR_OPERATION_CREATE_ACCOUNT) { @@ -216,22 +223,25 @@ static int parse_operation_data(const uint8_t *xdr, tx->operation_type = (stellar_operation_type_t)operation_type; // Parse destination account (common for both operations) - uint32_t dest_account_type = read_uint32_be_offset(xdr, offset); + uint32_t dest_account_type = U32_READ_BE_ARRAY(xdr + *offset); + *offset += 4; if (dest_account_type != STELLAR_KEY_TYPE_ED25519) { return -1; } - read_account_offset(xdr, offset, payment->destination); + read_stellar_account(xdr, offset, payment->destination); // For PAYMENT operations, we need to parse the asset type if (operation_type == STELLAR_OPERATION_PAYMENT) { - uint32_t asset_type = read_uint32_be_offset(xdr, offset); + uint32_t asset_type = U32_READ_BE_ARRAY(xdr + *offset); + *offset += 4; if (asset_type != STELLAR_ASSET_TYPE_NATIVE) { return -1; } } // Parse amount (common for both operations) - payment->amount = read_uint64_be_offset(xdr, offset); + payment->amount = U64_READ_BE_ARRAY(xdr + *offset); + *offset += 8; return 0; } @@ -239,7 +249,8 @@ static int parse_operation_data(const uint8_t *xdr, int stellar_parse_transaction(const uint8_t *xdr, int xdr_len, stellar_transaction_t *tx, - stellar_payment_t *payment) { + stellar_payment_t *payment, + int *signature_data_len) { if (!xdr || !tx || !payment || xdr_len < 60) { return -1; } @@ -250,27 +261,32 @@ int stellar_parse_transaction(const uint8_t *xdr, memset(payment, 0, sizeof(stellar_payment_t)); // Parse Envelope Type (4 bytes) - uint32_t envelope_type = read_uint32_be_offset(xdr, &offset); + uint32_t envelope_type = U32_READ_BE_ARRAY(xdr + offset); + offset += 4; if (envelope_type != STELLAR_ENVELOPE_TYPE_TX) { return -1; } // Parse Source Account - uint32_t source_account_type = read_uint32_be_offset(xdr, &offset); + uint32_t source_account_type = U32_READ_BE_ARRAY(xdr + offset); + offset += 4; if (source_account_type != STELLAR_KEY_TYPE_ED25519) { return -1; } - read_account_offset(xdr, &offset, tx->source_account); + read_stellar_account(xdr, &offset, tx->source_account); // Parse Fee (4 bytes) - tx->fee = read_uint32_be_offset(xdr, &offset); + tx->fee = U32_READ_BE_ARRAY(xdr + offset); + offset += 4; // Parse Sequence Number (8 bytes) - tx->sequence_number = read_uint64_be_offset(xdr, &offset); + tx->sequence_number = U64_READ_BE_ARRAY(xdr + offset); + offset += 8; // Parse Preconditions - uint32_t preconditions_type = read_uint32_be_offset(xdr, &offset); + uint32_t preconditions_type = U32_READ_BE_ARRAY(xdr + offset); + offset += 4; if (preconditions_type == 1) { offset += 16; // Skip time bounds (8 + 8 bytes) @@ -291,8 +307,9 @@ int stellar_parse_transaction(const uint8_t *xdr, // Skip transaction extension if (offset + 4 <= xdr_len) { - read_uint32_be_offset(xdr, &offset); + offset += 4; // Just skip the extension, don't need to read the value } + *signature_data_len = offset; return 0; } \ No newline at end of file diff --git a/apps/stellar_app/stellar_txn_helpers.h b/apps/stellar_app/stellar_txn_helpers.h index 51e1302aa..f9e4c3703 100644 --- a/apps/stellar_app/stellar_txn_helpers.h +++ b/apps/stellar_app/stellar_txn_helpers.h @@ -53,6 +53,7 @@ int stellar_parse_transaction(const uint8_t *xdr, int xdr_len, stellar_transaction_t *tx, - stellar_payment_t *payment); + stellar_payment_t *payment, + int *signature_data_len); #endif /* STELLAR_TXN_HELPERS_H */ diff --git a/common/libraries/util/utils.h b/common/libraries/util/utils.h index 8e25a2451..b58ce1a41 100644 --- a/common/libraries/util/utils.h +++ b/common/libraries/util/utils.h @@ -481,18 +481,4 @@ uint8_t string_to_escaped_string(const char *input, char *escaped_string, size_t out_len); -/** - * @brief Write 32-bit value in big-endian format - * @param buffer Output buffer - * @param value 32-bit value to write - */ -void write_uint32_be(uint8_t *buffer, uint32_t value); - -/** - * @brief Write 64-bit value in big-endian format - * @param buffer Output buffer - * @param value 64-bit value to write - */ -void write_uint64_be(uint8_t *buffer, uint64_t value); - -#endif +#endif \ No newline at end of file diff --git a/common/proto-options/stellar/sign_txn.options b/common/proto-options/stellar/sign_txn.options index 7cfcfad4e..049a8a4c8 100644 --- a/common/proto-options/stellar/sign_txn.options +++ b/common/proto-options/stellar/sign_txn.options @@ -1,4 +1,4 @@ # Options for file common/cypherock-common/proto/stellar/sign_txn.proto stellar.SignTxnInitiateRequest.wallet_id type:FT_STATIC max_size:32 fixed_length:true stellar.SignTxnInitiateRequest.derivation_path type:FT_STATIC max_count:3 fixed_length:true -stellar.SignTxnSignatureResponse.signature type:FT_STATIC max_size:2048 fixed_length:false +stellar.SignTxnSignatureResponse.signature type:FT_STATIC max_size:64 fixed_length:false diff --git a/src/constant_texts.c b/src/constant_texts.c index 1fbd5508a..ff1a95784 100644 --- a/src/constant_texts.c +++ b/src/constant_texts.c @@ -586,8 +586,9 @@ const char *ui_text_send_logs_prompt = "Send logs to the cySync app?"; #endif // Stellar memo display texts -const char *ui_text_stellar_memo_text = "Memo: \"%s\""; -const char *ui_text_stellar_memo_id = "Memo ID: %llu"; -const char *ui_text_stellar_memo_hash = "Memo Hash: %s"; -const char *ui_text_stellar_operation = "Operation: %s"; -const char *ui_text_stellar_memo_unknown = "Memo: (unknown type %u)"; \ No newline at end of file +const char *ui_text_memo_text = "Memo: \"%s\""; +const char *ui_text_memo_id = "Memo ID: %llu"; +const char *ui_text_memo_hash = "Memo Hash: %s"; +const char *ui_text_operation = "Operation: %s"; +const char *ui_text_memo_unknown = "Memo: (unknown type %u)"; +const char *ui_text_memo_hash_prefix = "Memo Hash: "; \ No newline at end of file diff --git a/src/constant_texts.h b/src/constant_texts.h index e64ecf4a3..cc3054252 100644 --- a/src/constant_texts.h +++ b/src/constant_texts.h @@ -419,17 +419,11 @@ extern const char *ui_text_verify_account_id; extern const char *ui_text_send_logs_prompt; #endif -// Stellar memo display texts -#define UI_TEXT_STELLAR_MEMO_TEXT "Memo: \"%s\"" -#define UI_TEXT_STELLAR_MEMO_ID "Memo ID: %llu" -#define UI_TEXT_STELLAR_MEMO_HASH "Memo Hash: %s" -#define UI_TEXT_STELLAR_OPERATION "Operation: %s" -#define UI_TEXT_STELLAR_MEMO_UNKNOWN "Memo: (unknown type %u)" - -extern const char *ui_text_stellar_memo_text; -extern const char *ui_text_stellar_memo_id; -extern const char *ui_text_stellar_memo_hash; -extern const char *ui_text_stellar_operation; -extern const char *ui_text_stellar_memo_unknown; +extern const char *ui_text_memo_text; +extern const char *ui_text_memo_id; +extern const char *ui_text_memo_hash; +extern const char *ui_text_operation; +extern const char *ui_text_memo_unknown; +extern const char *ui_text_memo_hash_prefix; #endif // CONSTANT_TEXTS_H \ No newline at end of file From 3578a930163936d2c4322afada9d241b2fe4d016 Mon Sep 17 00:00:00 2001 From: Keyur279 Date: Mon, 4 Aug 2025 16:49:22 +0530 Subject: [PATCH 15/23] chore: Remove display text for operation --- src/constant_texts.c | 1 - src/constant_texts.h | 1 - 2 files changed, 2 deletions(-) diff --git a/src/constant_texts.c b/src/constant_texts.c index ff1a95784..52f9c3f2a 100644 --- a/src/constant_texts.c +++ b/src/constant_texts.c @@ -589,6 +589,5 @@ const char *ui_text_send_logs_prompt = "Send logs to the cySync app?"; const char *ui_text_memo_text = "Memo: \"%s\""; const char *ui_text_memo_id = "Memo ID: %llu"; const char *ui_text_memo_hash = "Memo Hash: %s"; -const char *ui_text_operation = "Operation: %s"; const char *ui_text_memo_unknown = "Memo: (unknown type %u)"; const char *ui_text_memo_hash_prefix = "Memo Hash: "; \ No newline at end of file diff --git a/src/constant_texts.h b/src/constant_texts.h index cc3054252..581a6b86d 100644 --- a/src/constant_texts.h +++ b/src/constant_texts.h @@ -422,7 +422,6 @@ extern const char *ui_text_send_logs_prompt; extern const char *ui_text_memo_text; extern const char *ui_text_memo_id; extern const char *ui_text_memo_hash; -extern const char *ui_text_operation; extern const char *ui_text_memo_unknown; extern const char *ui_text_memo_hash_prefix; From fa40580e68c9a64d27684adcbfb93c24a864f852 Mon Sep 17 00:00:00 2001 From: Muzaffar Ahmad Bhat Date: Tue, 5 Aug 2025 01:23:32 +0530 Subject: [PATCH 16/23] fix: Set stellar signature response fix length and resolve reviews --- apps/stellar_app/stellar_txn.c | 86 ++++++++----------- common/proto-options/stellar/sign_txn.options | 2 +- 2 files changed, 36 insertions(+), 52 deletions(-) diff --git a/apps/stellar_app/stellar_txn.c b/apps/stellar_app/stellar_txn.c index ffda03c42..4e735b09a 100644 --- a/apps/stellar_app/stellar_txn.c +++ b/apps/stellar_app/stellar_txn.c @@ -95,7 +95,7 @@ /***************************************************************************** * PRIVATE TYPEDEFS *****************************************************************************/ -typedef stellar_sign_txn_signature_response_signature_t stellar_sig_t; +typedef stellar_sign_txn_signature_response_t stellar_sig_t; /***************************************************************************** * STATIC FUNCTION PROTOTYPES @@ -185,13 +185,13 @@ static bool get_user_verification(void); * @details Seed reconstruction takes place within this function. Returns only * the 64-byte signature, not the full transaction envelope. * - * @param signature_buffer Reference to buffer where the signature will be + * @param signature Reference to buffer where the signature will be * populated * @return true If the signature was computed successfully * @return false If signature could not be computed - maybe due to some error * during seed reconstruction phase */ -static bool sign_txn(stellar_sig_t *der_signature); +static bool sign_txn(uint8_t *signature); /** * @brief Creates signature base data for Stellar transaction signing @@ -199,34 +199,28 @@ static bool sign_txn(stellar_sig_t *der_signature); * * @param network_passphrase Network passphrase string (mainnet/testnet) * @param transaction_xdr Raw XDR transaction data - * @param signature_data_len Length of signature-relevant XDR data - * @param signature_base Output buffer for signature base data - * @param base_len Output length of signature base data + * @param txn_signature_data_len Length of signature-relevant XDR data + * @param output Output buffer for signature base data * @return int 0 on success, non-zero on error */ static int create_signature_base(const char *network_passphrase, uint8_t *transaction_xdr, - size_t signature_data_len, - uint8_t *signature_base, - int *base_len); + size_t txn_signature_data_len, + uint8_t *output); /** * @brief Creates ED25519 signature for Stellar transaction * @details Signs the transaction hash using ED25519 algorithm * See * https://github.com/trezor/trezor-firmware/blob/main/core/src/apps/stellar/sign_tx.py * - * @param tx Parsed transaction structure - * @param payment Parsed payment operation structure * @param private_key ED25519 private key for signing * @param public_key ED25519 public key for verification * @param network Target network (mainnet/testnet) * @param signature Output buffer for 64-byte signature * @return int 0 on success, non-zero on error */ -static int stellar_create_signature(stellar_transaction_t *tx, - stellar_payment_t *payment, - const uint8_t *private_key, +static int stellar_create_signature(const uint8_t *private_key, const uint8_t *public_key, stellar_network_t network, uint8_t *signature); @@ -243,7 +237,7 @@ static int stellar_create_signature(stellar_transaction_t *tx, * or invalid request received from the host */ static bool send_signature(stellar_query_t *query, - const stellar_sig_t *der_signature); + const stellar_sig_t *signature); /***************************************************************************** * STATIC VARIABLES @@ -494,62 +488,58 @@ static bool get_user_verification(void) { static int create_signature_base(const char *network_passphrase, uint8_t *transaction_xdr, - size_t signature_data_len, - uint8_t *signature_base, - int *base_len) { - // 1. Hash network passphrase - uint8_t network_hash[32]; + size_t txn_signature_data_len, + uint8_t *output) { + // Hash network passphrase + uint8_t network_hash[SHA256_DIGEST_LENGTH] = {0}; sha256_Raw((const uint8_t *)network_passphrase, strlen(network_passphrase), network_hash); - // 2. Combine: network_hash + truncated_xdr (only signature-relevant part) - memcpy(signature_base, network_hash, 32); - memcpy(signature_base + 32, transaction_xdr, signature_data_len); + // Combine: network_hash + truncated_xdr (only signature-relevant part) + memcpy(output, network_hash, SHA256_DIGEST_LENGTH); + memcpy(output + SHA256_DIGEST_LENGTH, transaction_xdr, txn_signature_data_len); - *base_len = 32 + signature_data_len; return 0; } -static int stellar_create_signature(stellar_transaction_t *tx, - stellar_payment_t *payment, - const uint8_t *private_key, +static int stellar_create_signature(const uint8_t *private_key, const uint8_t *public_key, stellar_network_t network, uint8_t *signature) { - uint8_t signature_base[1024] = {0}; - int base_len = 0; + size_t txn_signature_data_len = stellar_txn_context->signature_data_len; + size_t base_len = SHA256_DIGEST_LENGTH + txn_signature_data_len; + uint8_t signature_base[base_len]; + memzero(signature_base, base_len); - // 1. Create signature base + // Create signature base const char *passphrase = (network == STELLAR_NETWORK_TESTNET) ? TESTNET_PASSPHRASE : MAINNET_PASSPHRASE; int result = create_signature_base(passphrase, stellar_txn_context->transaction, - stellar_txn_context->signature_data_len, - signature_base, - &base_len); + txn_signature_data_len, + signature_base); if (result != 0) { return result; } - // 2. Hash the signature base (this is what we actually sign) - uint8_t transaction_hash[32]; + // Hash the signature base + uint8_t transaction_hash[SHA256_DIGEST_LENGTH] = {0}; sha256_Raw(signature_base, base_len, transaction_hash); - // 3. Sign the hash (32 bytes), not the raw signature base + // Sign the hash ed25519_signature sig = {0}; - ed25519_sign(transaction_hash, 32, private_key, public_key, sig); + ed25519_sign(transaction_hash, SHA256_DIGEST_LENGTH, private_key, public_key, sig); memcpy(signature, sig, STELLAR_SIGNATURE_SIZE); return 0; } -static bool sign_txn(stellar_sig_t *der_signature) { +static bool sign_txn(uint8_t *signature) { uint8_t seed[64] = {0}; - uint8_t signature[STELLAR_SIGNATURE_SIZE] = {0}; // Reconstruct seed if (!reconstruct_seed( @@ -577,8 +567,6 @@ static bool sign_txn(stellar_sig_t *der_signature) { // Create signature only (not full envelope) int result = stellar_create_signature( - stellar_txn_context->txn, - stellar_txn_context->payment, node.private_key, node.public_key + 1, // Skip first byte for Stellar raw public key STELLAR_NETWORK_MAINNET, @@ -591,10 +579,6 @@ static bool sign_txn(stellar_sig_t *der_signature) { return false; } - // Copy the 64-byte signature to output buffer - memcpy(der_signature->bytes, signature, STELLAR_SIGNATURE_SIZE); - der_signature->size = STELLAR_SIGNATURE_SIZE; - // Clean up sensitive data memzero(seed, sizeof(seed)); memzero(&node, sizeof(HDNode)); @@ -604,7 +588,7 @@ static bool sign_txn(stellar_sig_t *der_signature) { } static bool send_signature(stellar_query_t *query, - const stellar_sig_t *der_signature) { + const stellar_sig_t *signature) { stellar_result_t result = init_stellar_result(STELLAR_RESULT_SIGN_TXN_TAG); result.sign_txn.which_response = STELLAR_SIGN_TXN_RESPONSE_SIGNATURE_TAG; @@ -613,8 +597,8 @@ static bool send_signature(stellar_query_t *query, return false; } - memcpy(&result.sign_txn.signature.signature, - der_signature, + memcpy(&result.sign_txn.signature, + signature, sizeof(stellar_sig_t)); stellar_send_result(&result); @@ -629,11 +613,11 @@ void stellar_sign_transaction(stellar_query_t *query) { (stellar_txn_context_t *)malloc(sizeof(stellar_txn_context_t)); memzero(stellar_txn_context, sizeof(stellar_txn_context_t)); - stellar_sig_t der_signature = {0}; + stellar_sig_t sig = {0}; if (handle_initiate_query(query) && fetch_valid_input(query) && - get_user_verification() && sign_txn(&der_signature) && - send_signature(query, &der_signature)) { + get_user_verification() && sign_txn(sig.signature) && + send_signature(query, &sig)) { delay_scr_init(ui_text_check_cysync, DELAY_TIME); } diff --git a/common/proto-options/stellar/sign_txn.options b/common/proto-options/stellar/sign_txn.options index 049a8a4c8..6f22590da 100644 --- a/common/proto-options/stellar/sign_txn.options +++ b/common/proto-options/stellar/sign_txn.options @@ -1,4 +1,4 @@ # Options for file common/cypherock-common/proto/stellar/sign_txn.proto stellar.SignTxnInitiateRequest.wallet_id type:FT_STATIC max_size:32 fixed_length:true stellar.SignTxnInitiateRequest.derivation_path type:FT_STATIC max_count:3 fixed_length:true -stellar.SignTxnSignatureResponse.signature type:FT_STATIC max_size:64 fixed_length:false +stellar.SignTxnSignatureResponse.signature type:FT_STATIC max_size:64 fixed_length:true From 39d24ee835e5a09234fc9a27f5a9e6e431e58658 Mon Sep 17 00:00:00 2001 From: Muzaffar Ahmad Bhat Date: Tue, 5 Aug 2025 02:06:05 +0530 Subject: [PATCH 17/23] fix: Stellar txn structure and data types --- apps/stellar_app/stellar_context.h | 23 ++++--- apps/stellar_app/stellar_priv.h | 5 +- apps/stellar_app/stellar_txn.c | 29 +++----- apps/stellar_app/stellar_txn_helpers.c | 93 +++++++++++++------------- apps/stellar_app/stellar_txn_helpers.h | 11 ++- 5 files changed, 79 insertions(+), 82 deletions(-) diff --git a/apps/stellar_app/stellar_context.h b/apps/stellar_app/stellar_context.h index d25169d8d..0fda112d9 100644 --- a/apps/stellar_app/stellar_context.h +++ b/apps/stellar_app/stellar_context.h @@ -68,27 +68,32 @@ typedef enum { STELLAR_OPERATION_PAYMENT = 1 } stellar_operation_type_t; +// Custom generic structure for Stellar operation data +// So far we only support create account and payment operations with native +// asset only +typedef struct { + stellar_operation_type_t type; + uint8_t destination[STELLAR_PUBKEY_RAW_SIZE]; + uint64_t amount; +} stellar_operation_data_t; + // Stellar transaction structures -// See https://developers.stellar.org/docs/learn/encyclopedia/data-format/xdr +// See +// https://github.com/stellar/stellar-xdr/blob/curr/Stellar-transaction.x#L911 typedef struct { - uint8_t source_account[32]; + uint8_t source_account[STELLAR_PUBKEY_RAW_SIZE]; uint64_t sequence_number; uint32_t fee; - uint32_t operation_count; - stellar_operation_type_t operation_type; stellar_memo_type_t memo_type; union { char text[29]; // STELLAR_MEMO_TEXT (max 28 bytes + 1 byte delimiter) uint64_t id; // STELLAR_MEMO_ID uint8_t hash[32]; // STELLAR_MEMO_HASH or STELLAR_MEMO_RETURN(32 bytes) } memo; + uint32_t operation_count; + stellar_operation_data_t operations[1]; // Only one operation supported } stellar_transaction_t; -typedef struct { - uint8_t destination[32]; - uint64_t amount; -} stellar_payment_t; - typedef enum { STELLAR_NETWORK_MAINNET = 0, STELLAR_NETWORK_TESTNET = 1 diff --git a/apps/stellar_app/stellar_priv.h b/apps/stellar_app/stellar_priv.h index c08157598..d7ed9c3c5 100644 --- a/apps/stellar_app/stellar_priv.h +++ b/apps/stellar_app/stellar_priv.h @@ -33,8 +33,9 @@ typedef struct { // decoded transaction structures stellar_transaction_t *txn; - stellar_payment_t *payment; - size_t signature_data_len; + + // holds the length of the xdr txn used for signing + uint32_t txn_signature_data_len; } stellar_txn_context_t; /***************************************************************************** diff --git a/apps/stellar_app/stellar_txn.c b/apps/stellar_app/stellar_txn.c index 4e735b09a..29a007d0e 100644 --- a/apps/stellar_app/stellar_txn.c +++ b/apps/stellar_app/stellar_txn.c @@ -371,20 +371,16 @@ static bool fetch_valid_input(stellar_query_t *query) { stellar_txn_context->txn = (stellar_transaction_t *)malloc(sizeof(stellar_transaction_t)); - stellar_txn_context->payment = - (stellar_payment_t *)malloc(sizeof(stellar_payment_t)); - int signature_data_len = 0; if (stellar_parse_transaction(stellar_txn_context->transaction, total_size, stellar_txn_context->txn, - stellar_txn_context->payment, - &signature_data_len) != 0) { + &stellar_txn_context->txn_signature_data_len) != + 0) { stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, ERROR_DATA_FLOW_INVALID_DATA); return false; } - stellar_txn_context->signature_data_len = signature_data_len; return true; } @@ -429,12 +425,12 @@ static bool show_memo_details(const stellar_transaction_t *decoded_txn) { static bool get_user_verification(void) { const stellar_transaction_t *decoded_txn = stellar_txn_context->txn; - const stellar_payment_t *payment = stellar_txn_context->payment; char to_address[STELLAR_ADDRESS_LENGTH] = ""; // Generate addresses for display - if (!stellar_generate_address(payment->destination, to_address)) { + if (!stellar_generate_address(decoded_txn->operations[0].destination, + to_address)) { stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 2); return false; } @@ -454,7 +450,7 @@ static bool get_user_verification(void) { // Show amount char amount_string[30] = {'\0'}; - double decimal_amount = (double)payment->amount; + double decimal_amount = (double)decoded_txn->operations[0].amount; decimal_amount *= 1e-7; // Convert stroops to XLM snprintf(amount_string, sizeof(amount_string), "%.7f", decimal_amount); @@ -498,7 +494,8 @@ static int create_signature_base(const char *network_passphrase, // Combine: network_hash + truncated_xdr (only signature-relevant part) memcpy(output, network_hash, SHA256_DIGEST_LENGTH); - memcpy(output + SHA256_DIGEST_LENGTH, transaction_xdr, txn_signature_data_len); + memcpy( + output + SHA256_DIGEST_LENGTH, transaction_xdr, txn_signature_data_len); return 0; } @@ -507,7 +504,7 @@ static int stellar_create_signature(const uint8_t *private_key, const uint8_t *public_key, stellar_network_t network, uint8_t *signature) { - size_t txn_signature_data_len = stellar_txn_context->signature_data_len; + size_t txn_signature_data_len = stellar_txn_context->txn_signature_data_len; size_t base_len = SHA256_DIGEST_LENGTH + txn_signature_data_len; uint8_t signature_base[base_len]; memzero(signature_base, base_len); @@ -532,7 +529,8 @@ static int stellar_create_signature(const uint8_t *private_key, // Sign the hash ed25519_signature sig = {0}; - ed25519_sign(transaction_hash, SHA256_DIGEST_LENGTH, private_key, public_key, sig); + ed25519_sign( + transaction_hash, SHA256_DIGEST_LENGTH, private_key, public_key, sig); memcpy(signature, sig, STELLAR_SIGNATURE_SIZE); return 0; @@ -597,9 +595,7 @@ static bool send_signature(stellar_query_t *query, return false; } - memcpy(&result.sign_txn.signature, - signature, - sizeof(stellar_sig_t)); + memcpy(&result.sign_txn.signature, signature, sizeof(stellar_sig_t)); stellar_send_result(&result); return true; @@ -629,9 +625,6 @@ void stellar_sign_transaction(stellar_query_t *query) { if (stellar_txn_context->txn) { free(stellar_txn_context->txn); } - if (stellar_txn_context->payment) { - free(stellar_txn_context->payment); - } free(stellar_txn_context); stellar_txn_context = NULL; } diff --git a/apps/stellar_app/stellar_txn_helpers.c b/apps/stellar_app/stellar_txn_helpers.c index 639d758c1..d6a2d57d6 100644 --- a/apps/stellar_app/stellar_txn_helpers.c +++ b/apps/stellar_app/stellar_txn_helpers.c @@ -98,30 +98,33 @@ *****************************************************************************/ static void read_stellar_account(const uint8_t *data, - int *offset, + uint32_t *offset, uint8_t *account) { memcpy(account, data + *offset, STELLAR_PUBKEY_RAW_SIZE); *offset += STELLAR_PUBKEY_RAW_SIZE; } -static int read_xdr_string(const uint8_t *data, - int *offset, - char *str, - int max_len, - int data_len) { - if (*offset + 4 > data_len) - return -1; +static uint32_t read_xdr_string(const uint8_t *data, + uint32_t *offset, + char *str, + uint32_t max_len, + uint32_t data_len) { + if (*offset + 4 > data_len) { + return 0; + } uint32_t len = U32_READ_BE_ARRAY(data + *offset); *offset += 4; - if (len >= max_len || *offset + len > data_len) - return -1; + if (len >= max_len || *offset + len > data_len) { + return 0; + } // Calculate padded length (round up to 4-byte boundary) int padded_len = ((len + 3) / 4) * 4; - if (*offset + padded_len > data_len) - return -1; + if (*offset + padded_len > data_len) { + return 0; + } memcpy(str, data + *offset, len); str[len] = '\0'; @@ -135,29 +138,29 @@ static int read_xdr_string(const uint8_t *data, *****************************************************************************/ static int parse_memo_data(const uint8_t *xdr, - int *offset, - int xdr_len, - stellar_transaction_t *tx) { - tx->memo_type = (stellar_memo_type_t)U32_READ_BE_ARRAY(xdr + *offset); + uint32_t *offset, + uint32_t xdr_len, + stellar_transaction_t *txn) { + txn->memo_type = (stellar_memo_type_t)U32_READ_BE_ARRAY(xdr + *offset); *offset += 4; - switch (tx->memo_type) { + switch (txn->memo_type) { case STELLAR_MEMO_NONE: break; case STELLAR_MEMO_TEXT: { char temp_memo[64]; - int memo_len = + uint32_t memo_len = read_xdr_string(xdr, offset, temp_memo, sizeof(temp_memo), xdr_len); - if (memo_len < 0) { + if (memo_len == 0 || memo_len >= sizeof(txn->memo.text)) { return -1; } - strncpy(tx->memo.text, temp_memo, sizeof(tx->memo.text) - 1); - tx->memo.text[sizeof(tx->memo.text) - 1] = '\0'; + strncpy(txn->memo.text, temp_memo, memo_len); + txn->memo.text[memo_len] = '\0'; } break; case STELLAR_MEMO_ID: - tx->memo.id = U64_READ_BE_ARRAY(xdr + *offset); + txn->memo.id = U64_READ_BE_ARRAY(xdr + *offset); *offset += 8; break; @@ -166,7 +169,7 @@ static int parse_memo_data(const uint8_t *xdr, if (*offset + 32 > xdr_len) { return -1; } - memcpy(tx->memo.hash, xdr + *offset, 32); + memcpy(txn->memo.hash, xdr + *offset, 32); *offset += 32; break; @@ -178,15 +181,13 @@ static int parse_memo_data(const uint8_t *xdr, } static int parse_operation_data(const uint8_t *xdr, - int *offset, - int xdr_len, - stellar_transaction_t *tx, - stellar_payment_t *payment) { + uint32_t *offset, + stellar_transaction_t *txn) { // Parse Operations count - tx->operation_count = U32_READ_BE_ARRAY(xdr + *offset); + txn->operation_count = U32_READ_BE_ARRAY(xdr + *offset); *offset += 4; - if (tx->operation_count != 1) { + if (txn->operation_count != 1) { return -1; } @@ -202,7 +203,7 @@ static int parse_operation_data(const uint8_t *xdr, return -1; } - if (memcmp(xdr + *offset, tx->source_account, STELLAR_PUBKEY_RAW_SIZE) != + if (memcmp(xdr + *offset, txn->source_account, STELLAR_PUBKEY_RAW_SIZE) != 0) { return -1; } @@ -220,7 +221,7 @@ static int parse_operation_data(const uint8_t *xdr, operation_type != STELLAR_OPERATION_CREATE_ACCOUNT) { return -1; } - tx->operation_type = (stellar_operation_type_t)operation_type; + txn->operations[0].type = (stellar_operation_type_t)operation_type; // Parse destination account (common for both operations) uint32_t dest_account_type = U32_READ_BE_ARRAY(xdr + *offset); @@ -228,7 +229,7 @@ static int parse_operation_data(const uint8_t *xdr, if (dest_account_type != STELLAR_KEY_TYPE_ED25519) { return -1; } - read_stellar_account(xdr, offset, payment->destination); + read_stellar_account(xdr, offset, txn->operations[0].destination); // For PAYMENT operations, we need to parse the asset type if (operation_type == STELLAR_OPERATION_PAYMENT) { @@ -240,25 +241,23 @@ static int parse_operation_data(const uint8_t *xdr, } // Parse amount (common for both operations) - payment->amount = U64_READ_BE_ARRAY(xdr + *offset); + txn->operations[0].amount = U64_READ_BE_ARRAY(xdr + *offset); *offset += 8; return 0; } int stellar_parse_transaction(const uint8_t *xdr, - int xdr_len, - stellar_transaction_t *tx, - stellar_payment_t *payment, - int *signature_data_len) { - if (!xdr || !tx || !payment || xdr_len < 60) { + uint32_t xdr_len, + stellar_transaction_t *txn, + uint32_t *txn_signature_data_len) { + if (!xdr || !txn || xdr_len < 60) { return -1; } - int offset = 0; + uint32_t offset = 0; - memset(tx, 0, sizeof(stellar_transaction_t)); - memset(payment, 0, sizeof(stellar_payment_t)); + memset(txn, 0, sizeof(stellar_transaction_t)); // Parse Envelope Type (4 bytes) uint32_t envelope_type = U32_READ_BE_ARRAY(xdr + offset); @@ -274,14 +273,14 @@ int stellar_parse_transaction(const uint8_t *xdr, return -1; } - read_stellar_account(xdr, &offset, tx->source_account); + read_stellar_account(xdr, &offset, txn->source_account); // Parse Fee (4 bytes) - tx->fee = U32_READ_BE_ARRAY(xdr + offset); + txn->fee = U32_READ_BE_ARRAY(xdr + offset); offset += 4; // Parse Sequence Number (8 bytes) - tx->sequence_number = U64_READ_BE_ARRAY(xdr + offset); + txn->sequence_number = U64_READ_BE_ARRAY(xdr + offset); offset += 8; // Parse Preconditions @@ -295,12 +294,12 @@ int stellar_parse_transaction(const uint8_t *xdr, } // Parse Memo - if (parse_memo_data(xdr, &offset, xdr_len, tx) != 0) { + if (parse_memo_data(xdr, &offset, xdr_len, txn) != 0) { return -1; } // Parse Operations - int result = parse_operation_data(xdr, &offset, xdr_len, tx, payment); + int result = parse_operation_data(xdr, &offset, txn); if (result != 0) { return result; } @@ -310,6 +309,6 @@ int stellar_parse_transaction(const uint8_t *xdr, offset += 4; // Just skip the extension, don't need to read the value } - *signature_data_len = offset; + *txn_signature_data_len = offset; return 0; } \ No newline at end of file diff --git a/apps/stellar_app/stellar_txn_helpers.h b/apps/stellar_app/stellar_txn_helpers.h index f9e4c3703..e7ef65a48 100644 --- a/apps/stellar_app/stellar_txn_helpers.h +++ b/apps/stellar_app/stellar_txn_helpers.h @@ -43,17 +43,16 @@ * @param xdr_len Size in bytes of the XDR transaction * @param tx Reference to buffer where decoded transaction information will be * populated. It can be used at a later stage for user verification. - * @param payment Reference to buffer where decoded payment/create account - * operation will be populated. + * @param txn_signature_data_len Pointer to store the length of the XDR data + * used for signing * @return int 0 if the parsing was successful, negative value if parsing failed * @retval 0 If the parsing was successful * @retval -1 If the parsing failed - could be due to unsupported transaction, * data type, or missing information */ int stellar_parse_transaction(const uint8_t *xdr, - int xdr_len, - stellar_transaction_t *tx, - stellar_payment_t *payment, - int *signature_data_len); + uint32_t xdr_len, + stellar_transaction_t *txn, + uint32_t *txn_signature_data_len); #endif /* STELLAR_TXN_HELPERS_H */ From 9aa8d6366fce3f99c687a6ae0796b13ee9472b4b Mon Sep 17 00:00:00 2001 From: Muzaffar Ahmad Bhat Date: Tue, 5 Aug 2025 02:14:20 +0530 Subject: [PATCH 18/23] fix: Stellar verify fee --- apps/stellar_app/stellar_txn.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/stellar_app/stellar_txn.c b/apps/stellar_app/stellar_txn.c index 29a007d0e..a34f5ef42 100644 --- a/apps/stellar_app/stellar_txn.c +++ b/apps/stellar_app/stellar_txn.c @@ -452,7 +452,7 @@ static bool get_user_verification(void) { char amount_string[30] = {'\0'}; double decimal_amount = (double)decoded_txn->operations[0].amount; decimal_amount *= 1e-7; // Convert stroops to XLM - snprintf(amount_string, sizeof(amount_string), "%.7f", decimal_amount); + snprintf(amount_string, sizeof(amount_string), "%.*g", 7, decimal_amount); char display[100] = {'\0'}; snprintf(display, @@ -466,9 +466,18 @@ static bool get_user_verification(void) { } // Show fee + char fee_string[30] = {'\0'}; + double decimal_fee = (double)decoded_txn->fee; + decimal_fee *= 1e-7; // Convert stroops to XLM + snprintf(fee_string, sizeof(fee_string), "%.*g", 7, decimal_fee); + char fee_display[50] = {'\0'}; - snprintf( - fee_display, sizeof(fee_display), "Fee: %lu stroops", decoded_txn->fee); + snprintf(fee_display, + sizeof(fee_display), + UI_TEXT_VERIFY_FEE, + fee_string, + STELLAR_LUNIT); + if (!core_confirmation(fee_display, stellar_send_error)) { return false; } From 0e30211b23b8e7612e30b2019f9998ed363dda86 Mon Sep 17 00:00:00 2001 From: Muzaffar Ahmad Bhat Date: Tue, 5 Aug 2025 02:38:38 +0530 Subject: [PATCH 19/23] fix: Stellar verify memo and review comments --- apps/stellar_app/stellar_helpers.c | 2 +- apps/stellar_app/stellar_txn.c | 87 +++++++++++++------------- apps/stellar_app/stellar_txn_helpers.c | 4 +- src/constant_texts.c | 9 +-- src/constant_texts.h | 8 +-- 5 files changed, 48 insertions(+), 62 deletions(-) diff --git a/apps/stellar_app/stellar_helpers.c b/apps/stellar_app/stellar_helpers.c index 9daff601b..1f35f1747 100644 --- a/apps/stellar_app/stellar_helpers.c +++ b/apps/stellar_app/stellar_helpers.c @@ -142,7 +142,7 @@ bool stellar_generate_address(const uint8_t *public_key, char *address) { // Stellar address encoding (StrKey format) // See // https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0023.md - uint8_t payload[35]; + uint8_t payload[35] = {0}; payload[0] = 0x30; // Account ID version byte (6 << 3 | 0 = STRKEY_PUBKEY // OR STRKEY_ALG_ED25519) memcpy(payload + 1, public_key, STELLAR_PUBKEY_RAW_SIZE); diff --git a/apps/stellar_app/stellar_txn.c b/apps/stellar_app/stellar_txn.c index a34f5ef42..234422e30 100644 --- a/apps/stellar_app/stellar_txn.c +++ b/apps/stellar_app/stellar_txn.c @@ -385,42 +385,42 @@ static bool fetch_valid_input(stellar_query_t *query) { return true; } -static bool show_memo_details(const stellar_transaction_t *decoded_txn) { - if (decoded_txn->memo_type == STELLAR_MEMO_TEXT) { - char memo_display[100] = {'\0'}; - snprintf(memo_display, - sizeof(memo_display), - ui_text_memo_text, - decoded_txn->memo.text); - return core_confirmation(memo_display, stellar_send_error); - } else if (decoded_txn->memo_type == STELLAR_MEMO_ID) { - char memo_display[50] = {'\0'}; - snprintf(memo_display, - sizeof(memo_display), - ui_text_memo_id, - decoded_txn->memo.id); - return core_confirmation(memo_display, stellar_send_error); - } else if (decoded_txn->memo_type == STELLAR_MEMO_NONE) { - // Don't show anything for MEMO_NONE - return true; - } else if (decoded_txn->memo_type == STELLAR_MEMO_HASH || - decoded_txn->memo_type == STELLAR_MEMO_RETURN) { - char memo_hash[80] = {'\0'}; - char temp[3]; - strcpy(memo_hash, ui_text_memo_hash_prefix); - for (int i = 0; i < 32; i++) { - snprintf(temp, sizeof(temp), "%02x", decoded_txn->memo.hash[i]); - strcat(memo_hash, temp); +static bool verify_memo_details(const stellar_transaction_t *decoded_txn) { + char memo_display[100] = {'\0'}; + switch (decoded_txn->memo_type) { + case STELLAR_MEMO_NONE: { + return true; + } + case STELLAR_MEMO_TEXT: { + (void)snprintf(memo_display, + sizeof(memo_display), + UI_TEXT_VERIFY_MEMO, + decoded_txn->memo.text); + break; + } + case STELLAR_MEMO_ID: { + (void)snprintf(memo_display, + sizeof(memo_display), + UI_TEXT_VERIFY_MEMO_ID, + decoded_txn->memo.id); + break; + } + case STELLAR_MEMO_HASH: + case STELLAR_MEMO_RETURN: { + char hex_hash[32 * 2 + 1] = ""; + byte_array_to_hex_string( + decoded_txn->memo.hash, 32, hex_hash, sizeof(hex_hash)); + (void)snprintf(memo_display, + sizeof(memo_display), + UI_TEXT_VERIFY_MEMO_HASH, + hex_hash); + break; + } + default: { + return false; } - return core_confirmation(memo_hash, stellar_send_error); - } else { - char memo_display[50] = {'\0'}; - snprintf(memo_display, - sizeof(memo_display), - ui_text_memo_unknown, - decoded_txn->memo_type); - return core_confirmation(memo_display, stellar_send_error); } + return core_confirmation(memo_display, stellar_send_error); } static bool get_user_verification(void) { @@ -442,13 +442,13 @@ static bool get_user_verification(void) { } } - // Show destination address + // Verify destination address if (!core_scroll_page( ui_text_verify_address, to_address, stellar_send_error)) { return false; } - // Show amount + // Verify amount char amount_string[30] = {'\0'}; double decimal_amount = (double)decoded_txn->operations[0].amount; decimal_amount *= 1e-7; // Convert stroops to XLM @@ -465,7 +465,7 @@ static bool get_user_verification(void) { return false; } - // Show fee + // Verify fee char fee_string[30] = {'\0'}; double decimal_fee = (double)decoded_txn->fee; decimal_fee *= 1e-7; // Convert stroops to XLM @@ -483,7 +483,7 @@ static bool get_user_verification(void) { } // Handle memo display - if (!show_memo_details(decoded_txn)) { + if (!verify_memo_details(decoded_txn)) { return false; } @@ -579,18 +579,15 @@ static bool sign_txn(uint8_t *signature) { STELLAR_NETWORK_MAINNET, signature); + // Clean up sensitive data + memzero(seed, sizeof(seed)); + memzero(&node, sizeof(HDNode)); + if (result != 0) { stellar_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 2); - memzero(seed, sizeof(seed)); - memzero(&node, sizeof(HDNode)); return false; } - // Clean up sensitive data - memzero(seed, sizeof(seed)); - memzero(&node, sizeof(HDNode)); - memzero(signature, sizeof(signature)); - return true; } diff --git a/apps/stellar_app/stellar_txn_helpers.c b/apps/stellar_app/stellar_txn_helpers.c index d6a2d57d6..720704049 100644 --- a/apps/stellar_app/stellar_txn_helpers.c +++ b/apps/stellar_app/stellar_txn_helpers.c @@ -121,7 +121,7 @@ static uint32_t read_xdr_string(const uint8_t *data, } // Calculate padded length (round up to 4-byte boundary) - int padded_len = ((len + 3) / 4) * 4; + uint32_t padded_len = ((len + 3) / 4) * 4; if (*offset + padded_len > data_len) { return 0; } @@ -149,7 +149,7 @@ static int parse_memo_data(const uint8_t *xdr, break; case STELLAR_MEMO_TEXT: { - char temp_memo[64]; + char temp_memo[64] = ""; uint32_t memo_len = read_xdr_string(xdr, offset, temp_memo, sizeof(temp_memo), xdr_len); if (memo_len == 0 || memo_len >= sizeof(txn->memo.text)) { diff --git a/src/constant_texts.c b/src/constant_texts.c index 52f9c3f2a..31e18290a 100644 --- a/src/constant_texts.c +++ b/src/constant_texts.c @@ -583,11 +583,4 @@ const char *ui_text_verify_account_id = "Verify account id"; #ifdef ALLOW_LOG_EXPORT const char *ui_text_send_logs_prompt = "Send logs to the cySync app?"; -#endif - -// Stellar memo display texts -const char *ui_text_memo_text = "Memo: \"%s\""; -const char *ui_text_memo_id = "Memo ID: %llu"; -const char *ui_text_memo_hash = "Memo Hash: %s"; -const char *ui_text_memo_unknown = "Memo: (unknown type %u)"; -const char *ui_text_memo_hash_prefix = "Memo Hash: "; \ No newline at end of file +#endif \ No newline at end of file diff --git a/src/constant_texts.h b/src/constant_texts.h index 581a6b86d..fda9dedc7 100644 --- a/src/constant_texts.h +++ b/src/constant_texts.h @@ -61,6 +61,8 @@ #define UI_TEXT_SIGN_MSG_PROMPT "Sign message on %s from %s" #define UI_TEXT_SIGN_DATA_PROMPT "Sign data on %s from %s" #define UI_TEXT_VERIFY_MEMO "Verify Memo\n%s" +#define UI_TEXT_VERIFY_MEMO_ID "Verify Memo ID\n%llu" +#define UI_TEXT_VERIFY_MEMO_HASH "Verify Memo Hash\n%s" #define UI_TEXT_CALLDATA "Calldata" // product hash @@ -419,10 +421,4 @@ extern const char *ui_text_verify_account_id; extern const char *ui_text_send_logs_prompt; #endif -extern const char *ui_text_memo_text; -extern const char *ui_text_memo_id; -extern const char *ui_text_memo_hash; -extern const char *ui_text_memo_unknown; -extern const char *ui_text_memo_hash_prefix; - #endif // CONSTANT_TEXTS_H \ No newline at end of file From ba1e16b9860f3c5305fd47af88585e4336127baa Mon Sep 17 00:00:00 2001 From: Muzaffar Ahmad Bhat Date: Tue, 5 Aug 2025 14:07:06 +0530 Subject: [PATCH 20/23] fix: Added stellar xdr string padding reference --- apps/stellar_app/stellar_txn_helpers.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/stellar_app/stellar_txn_helpers.c b/apps/stellar_app/stellar_txn_helpers.c index 720704049..e795fa8ae 100644 --- a/apps/stellar_app/stellar_txn_helpers.c +++ b/apps/stellar_app/stellar_txn_helpers.c @@ -120,7 +120,9 @@ static uint32_t read_xdr_string(const uint8_t *data, return 0; } - // Calculate padded length (round up to 4-byte boundary) + // Calculate padded length for 4-byte XDR alignment + // See + // https://github.com/stellar/js-xdr/blob/master/src/serialization/xdr-writer.js#L110 uint32_t padded_len = ((len + 3) / 4) * 4; if (*offset + padded_len > data_len) { return 0; @@ -128,7 +130,7 @@ static uint32_t read_xdr_string(const uint8_t *data, memcpy(str, data + *offset, len); str[len] = '\0'; - *offset += padded_len; // Skip data + padding in one go + *offset += padded_len; return len; } From dcae63c3a51c51300cc133aada82ac7ff280d7d6 Mon Sep 17 00:00:00 2001 From: Muzaffar Ahmad Bhat Date: Tue, 5 Aug 2025 18:25:33 +0530 Subject: [PATCH 21/23] fix: Stellar txn preconditions and extension --- apps/stellar_app/stellar_context.h | 52 ++++++++++++++++++++++++++ apps/stellar_app/stellar_txn_helpers.c | 27 +++++++++---- 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/apps/stellar_app/stellar_context.h b/apps/stellar_app/stellar_context.h index 0fda112d9..831709f04 100644 --- a/apps/stellar_app/stellar_context.h +++ b/apps/stellar_app/stellar_context.h @@ -77,6 +77,56 @@ typedef struct { uint64_t amount; } stellar_operation_data_t; +// Stellar preconditions types +// See +// https://developers.stellar.org/docs/learn/fundamentals/transactions/operations-and-transactions#preconditions +// See +// https://github.com/stellar/js-stellar-base/blob/master/xdr/curr/Stellar-transaction.x#L804 +typedef enum { + STELLAR_PRECOND_NONE = 0, + STELLAR_PRECOND_TIME = 1, + STELLAR_PRECOND_V2 = 2 +} stellar_preconditions_type_t; + +// Stellar time bounds structure +// See +// https://developers.stellar.org/docs/learn/fundamentals/transactions/operations-and-transactions#time-bounds +// See +// https://github.com/stellar/js-stellar-base/blob/master/xdr/curr/Stellar-transaction.x#L759 +typedef struct { + uint64_t min_time; + uint64_t max_time; +} stellar_time_bounds_t; + +// Stellar preconditions structure +// See +// https://developers.stellar.org/docs/learn/fundamentals/transactions/operations +typedef struct { + stellar_preconditions_type_t type; + union { + stellar_time_bounds_t + time_bounds; // For PRECOND_TIME, only PRECOND_TIME is supported + // For PRECOND_V2, we can add more fields in future if needed + } preconditions; +} stellar_preconditions_t; + +// Stellar transaction extension types +// See +// https://github.com/stellar/js-stellar-base/blob/master/xdr/curr/Stellar-transaction.x#L929 +typedef enum { + STELLAR_EXT_TYPE_EMPTY = 0, // Empty extension, only supported type + // Other extension types can be added in future if needed +} stellar_ext_type_t; + +// Stellar transaction extension structure +// See +// https://github.com/stellar/js-stellar-base/blob/master/xdr/curr/Stellar-transaction.x#L929 +typedef struct { + stellar_ext_type_t + type; // Currently only STELLAR_EXT_TYPE_EMPTY is supported + // Other fields can be added in future if needed +} stellar_transaction_extension_t; + // Stellar transaction structures // See // https://github.com/stellar/stellar-xdr/blob/curr/Stellar-transaction.x#L911 @@ -92,6 +142,8 @@ typedef struct { } memo; uint32_t operation_count; stellar_operation_data_t operations[1]; // Only one operation supported + stellar_preconditions_t preconditions; + stellar_transaction_extension_t ext; } stellar_transaction_t; typedef enum { diff --git a/apps/stellar_app/stellar_txn_helpers.c b/apps/stellar_app/stellar_txn_helpers.c index e795fa8ae..a7a057753 100644 --- a/apps/stellar_app/stellar_txn_helpers.c +++ b/apps/stellar_app/stellar_txn_helpers.c @@ -286,12 +286,20 @@ int stellar_parse_transaction(const uint8_t *xdr, offset += 8; // Parse Preconditions - uint32_t preconditions_type = U32_READ_BE_ARRAY(xdr + offset); + txn->preconditions.type = U32_READ_BE_ARRAY(xdr + offset); offset += 4; - if (preconditions_type == 1) { - offset += 16; // Skip time bounds (8 + 8 bytes) - } else if (preconditions_type != 0) { + if (txn->preconditions.type == STELLAR_PRECOND_TIME) { + if (offset + 16 > xdr_len) { + return -1; + } + txn->preconditions.preconditions.time_bounds.min_time = + U64_READ_BE_ARRAY(xdr + offset); + offset += 8; + txn->preconditions.preconditions.time_bounds.max_time = + U64_READ_BE_ARRAY(xdr + offset); + offset += 8; + } else if (txn->preconditions.type != STELLAR_PRECOND_NONE) { return -1; } @@ -306,9 +314,14 @@ int stellar_parse_transaction(const uint8_t *xdr, return result; } - // Skip transaction extension - if (offset + 4 <= xdr_len) { - offset += 4; // Just skip the extension, don't need to read the value + // Parse transaction extension + if (offset + 4 > xdr_len) { + return -1; + } + txn->ext.type = U32_READ_BE_ARRAY(xdr + offset); + offset += 4; + if (txn->ext.type != STELLAR_EXT_TYPE_EMPTY) { + return -1; // Only empty extension is supported } *txn_signature_data_len = offset; From 932f0f687e1d198fdc1f7a655f128c580e10c2c5 Mon Sep 17 00:00:00 2001 From: Muzaffar Ahmad Bhat Date: Wed, 6 Aug 2025 09:46:55 +0530 Subject: [PATCH 22/23] fix: Complete stellar txn parsing --- apps/stellar_app/stellar_priv.h | 2 +- apps/stellar_app/stellar_txn.c | 18 ++++++++---------- apps/stellar_app/stellar_txn_helpers.c | 15 +++++++++++++-- apps/stellar_app/stellar_txn_helpers.h | 4 ++-- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/apps/stellar_app/stellar_priv.h b/apps/stellar_app/stellar_priv.h index d7ed9c3c5..ce48ef616 100644 --- a/apps/stellar_app/stellar_priv.h +++ b/apps/stellar_app/stellar_priv.h @@ -35,7 +35,7 @@ typedef struct { stellar_transaction_t *txn; // holds the length of the xdr txn used for signing - uint32_t txn_signature_data_len; + uint32_t tagged_txn_len; } stellar_txn_context_t; /***************************************************************************** diff --git a/apps/stellar_app/stellar_txn.c b/apps/stellar_app/stellar_txn.c index 234422e30..f781d0933 100644 --- a/apps/stellar_app/stellar_txn.c +++ b/apps/stellar_app/stellar_txn.c @@ -199,14 +199,14 @@ static bool sign_txn(uint8_t *signature); * * @param network_passphrase Network passphrase string (mainnet/testnet) * @param transaction_xdr Raw XDR transaction data - * @param txn_signature_data_len Length of signature-relevant XDR data + * @param tagged_txn_len Length of signature-relevant tagged txn XDR * @param output Output buffer for signature base data * @return int 0 on success, non-zero on error */ static int create_signature_base(const char *network_passphrase, uint8_t *transaction_xdr, - size_t txn_signature_data_len, + size_t tagged_txn_len, uint8_t *output); /** * @brief Creates ED25519 signature for Stellar transaction @@ -375,8 +375,7 @@ static bool fetch_valid_input(stellar_query_t *query) { if (stellar_parse_transaction(stellar_txn_context->transaction, total_size, stellar_txn_context->txn, - &stellar_txn_context->txn_signature_data_len) != - 0) { + &stellar_txn_context->tagged_txn_len) != 0) { stellar_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, ERROR_DATA_FLOW_INVALID_DATA); return false; @@ -493,7 +492,7 @@ static bool get_user_verification(void) { static int create_signature_base(const char *network_passphrase, uint8_t *transaction_xdr, - size_t txn_signature_data_len, + size_t tagged_txn_len, uint8_t *output) { // Hash network passphrase uint8_t network_hash[SHA256_DIGEST_LENGTH] = {0}; @@ -503,8 +502,7 @@ static int create_signature_base(const char *network_passphrase, // Combine: network_hash + truncated_xdr (only signature-relevant part) memcpy(output, network_hash, SHA256_DIGEST_LENGTH); - memcpy( - output + SHA256_DIGEST_LENGTH, transaction_xdr, txn_signature_data_len); + memcpy(output + SHA256_DIGEST_LENGTH, transaction_xdr, tagged_txn_len); return 0; } @@ -513,8 +511,8 @@ static int stellar_create_signature(const uint8_t *private_key, const uint8_t *public_key, stellar_network_t network, uint8_t *signature) { - size_t txn_signature_data_len = stellar_txn_context->txn_signature_data_len; - size_t base_len = SHA256_DIGEST_LENGTH + txn_signature_data_len; + size_t tagged_txn_len = stellar_txn_context->tagged_txn_len; + size_t base_len = SHA256_DIGEST_LENGTH + tagged_txn_len; uint8_t signature_base[base_len]; memzero(signature_base, base_len); @@ -525,7 +523,7 @@ static int stellar_create_signature(const uint8_t *private_key, int result = create_signature_base(passphrase, stellar_txn_context->transaction, - txn_signature_data_len, + tagged_txn_len, signature_base); if (result != 0) { diff --git a/apps/stellar_app/stellar_txn_helpers.c b/apps/stellar_app/stellar_txn_helpers.c index a7a057753..ba780587e 100644 --- a/apps/stellar_app/stellar_txn_helpers.c +++ b/apps/stellar_app/stellar_txn_helpers.c @@ -252,7 +252,7 @@ static int parse_operation_data(const uint8_t *xdr, int stellar_parse_transaction(const uint8_t *xdr, uint32_t xdr_len, stellar_transaction_t *txn, - uint32_t *txn_signature_data_len) { + uint32_t *tagged_txn_len) { if (!xdr || !txn || xdr_len < 60) { return -1; } @@ -324,6 +324,17 @@ int stellar_parse_transaction(const uint8_t *xdr, return -1; // Only empty extension is supported } - *txn_signature_data_len = offset; + *tagged_txn_len = offset; + + uint32_t signature_count = U32_READ_BE_ARRAY(xdr + offset); + offset += 4; + if (signature_count != 0) { + return -1; // No signatures expected in unsigned transaction + } + + if (offset != xdr_len) { + return -1; // Extra data in XDR + } + return 0; } \ No newline at end of file diff --git a/apps/stellar_app/stellar_txn_helpers.h b/apps/stellar_app/stellar_txn_helpers.h index e7ef65a48..6b25e5e7a 100644 --- a/apps/stellar_app/stellar_txn_helpers.h +++ b/apps/stellar_app/stellar_txn_helpers.h @@ -43,7 +43,7 @@ * @param xdr_len Size in bytes of the XDR transaction * @param tx Reference to buffer where decoded transaction information will be * populated. It can be used at a later stage for user verification. - * @param txn_signature_data_len Pointer to store the length of the XDR data + * @param tagged_txn_len Pointer to store the length of the tagged txn xdr * used for signing * @return int 0 if the parsing was successful, negative value if parsing failed * @retval 0 If the parsing was successful @@ -53,6 +53,6 @@ int stellar_parse_transaction(const uint8_t *xdr, uint32_t xdr_len, stellar_transaction_t *txn, - uint32_t *txn_signature_data_len); + uint32_t *tagged_txn_len); #endif /* STELLAR_TXN_HELPERS_H */ From 6985f9c4ac643ad4dca7560a0dbcb89e83f50e5a Mon Sep 17 00:00:00 2001 From: Muzaffar Ahmad Bhat Date: Fri, 8 Aug 2025 11:29:01 +0530 Subject: [PATCH 23/23] fix: Stellar amount and fee display format --- apps/stellar_app/stellar_txn.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/stellar_app/stellar_txn.c b/apps/stellar_app/stellar_txn.c index f781d0933..a1246a7b6 100644 --- a/apps/stellar_app/stellar_txn.c +++ b/apps/stellar_app/stellar_txn.c @@ -451,7 +451,7 @@ static bool get_user_verification(void) { char amount_string[30] = {'\0'}; double decimal_amount = (double)decoded_txn->operations[0].amount; decimal_amount *= 1e-7; // Convert stroops to XLM - snprintf(amount_string, sizeof(amount_string), "%.*g", 7, decimal_amount); + snprintf(amount_string, sizeof(amount_string), "%.7f", decimal_amount); char display[100] = {'\0'}; snprintf(display, @@ -468,7 +468,7 @@ static bool get_user_verification(void) { char fee_string[30] = {'\0'}; double decimal_fee = (double)decoded_txn->fee; decimal_fee *= 1e-7; // Convert stroops to XLM - snprintf(fee_string, sizeof(fee_string), "%.*g", 7, decimal_fee); + snprintf(fee_string, sizeof(fee_string), "%.7f", decimal_fee); char fee_display[50] = {'\0'}; snprintf(fee_display,