diff --git a/apps/btc_family/btc_pub_key.c b/apps/btc_family/btc_pub_key.c index 6c56aac18..f3ff53489 100644 --- a/apps/btc_family/btc_pub_key.c +++ b/apps/btc_family/btc_pub_key.c @@ -60,12 +60,17 @@ * INCLUDES *****************************************************************************/ +#include + #include "bip32.h" #include "btc_api.h" +#include "btc_app.h" #include "btc_helpers.h" #include "btc_priv.h" #include "coin_utils.h" +#include "composable_app_queue.h" #include "curves.h" +#include "exchange_main.h" #include "reconstruct_wallet_flow.h" #include "status_api.h" #include "ui_core_confirm.h" @@ -152,6 +157,7 @@ static void send_public_key(const uint8_t *public_key); /***************************************************************************** * STATIC VARIABLES *****************************************************************************/ +static bool sign_address = false; static bool check_which_request(const btc_query_t *query, pb_size_t which_request) { @@ -173,6 +179,17 @@ static bool validate_request_data(btc_get_public_key_request_t *request) { ERROR_DATA_FLOW_INVALID_DATA); status = false; } + + caq_node_data_t data = {.applet_id = get_btc_app_desc()->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_RECEIVE; + + sign_address = exchange_app_validate_caq(data); + return status; } @@ -283,6 +300,11 @@ void btc_get_pub_key(btc_query_t *query) { delay_scr_init(ui_text_processing, DELAY_SHORT); size_t length = btc_get_address(seed, path, path_length, public_key, msg); memzero(seed, sizeof(seed)); + + if (sign_address) { + exchange_sign_address(msg, sizeof(msg)); + } + if (0 < length && true == core_scroll_page(ui_text_receive_on, msg, btc_send_error)) { set_app_flow_status(BTC_GET_PUBLIC_KEY_STATUS_VERIFY); diff --git a/apps/btc_family/btc_txn.c b/apps/btc_family/btc_txn.c index 570a8cf71..b5422a84d 100644 --- a/apps/btc_family/btc_txn.c +++ b/apps/btc_family/btc_txn.c @@ -66,6 +66,7 @@ #include "bip32.h" #include "btc_api.h" +#include "btc_app.h" #include "btc_helpers.h" #include "btc_inputs_validator.h" #include "btc_priv.h" @@ -74,8 +75,10 @@ #include "byte_stream.h" #include "coin_utils.h" #include "common.pb.h" +#include "composable_app_queue.h" #include "constant_texts.h" #include "curves.h" +#include "exchange_main.h" #include "reconstruct_wallet_flow.h" #include "status_api.h" #include "ui_core_confirm.h" @@ -274,7 +277,7 @@ static bool send_script_sig(btc_query_t *query, const scrip_sig_t *sigs); /***************************************************************************** * STATIC VARIABLES *****************************************************************************/ - +static bool use_signature_verification = false; static btc_txn_context_t *btc_txn_context = NULL; /***************************************************************************** @@ -305,6 +308,17 @@ static bool validate_request_data(const btc_sign_txn_request_t *request) { ERROR_DATA_FLOW_INVALID_DATA); status = false; } + + caq_node_data_t data = {.applet_id = get_btc_app_desc()->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; } @@ -572,6 +586,14 @@ static bool get_user_verification() { btc_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, status); return false; } + + if (use_signature_verification) { + if (!exchange_validate_stored_signature(address, sizeof(address))) { + btc_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, status); + return false; + } + } + if (!core_scroll_page(title, address, btc_send_error) || !core_scroll_page(title, value, btc_send_error)) { return false; diff --git a/apps/constellation_app/constellation_api.c b/apps/constellation_app/constellation_api.c new file mode 100644 index 000000000..d11cd0099 --- /dev/null +++ b/apps/constellation_app/constellation_api.c @@ -0,0 +1,200 @@ +/** + * @file constellation_api.c + * @author Cypherock X1 Team + * @brief Defines helpers apis for CONSTELLATION 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 "constellation_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_constellation_query(const uint8_t *data, + uint16_t data_size, + constellation_query_t *query_out) { + if (NULL == data || NULL == query_out || 0 == data_size) { + constellation_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(constellation_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, CONSTELLATION_QUERY_FIELDS, query_out); + + /* Send error to host if status is false*/ + if (false == status) { + constellation_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_DECODING_FAILED); + } + + return status; +} + +bool encode_constellation_result(const constellation_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, CONSTELLATION_RESULT_FIELDS, result); + + if (true == status) { + *bytes_written_out = stream.bytes_written; + } + + return status; +} + +bool check_constellation_query(const constellation_query_t *query, + pb_size_t exp_query_tag) { + if ((NULL == query) || (exp_query_tag != query->which_request)) { + constellation_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_QUERY); + return false; + } + return true; +} + +constellation_result_t init_constellation_result(pb_size_t result_tag) { + constellation_result_t result = CONSTELLATION_RESULT_INIT_ZERO; + result.which_response = result_tag; + return result; +} + +void constellation_send_error(pb_size_t which_error, uint32_t error_code) { + constellation_result_t result = + init_constellation_result(CONSTELLATION_RESULT_COMMON_ERROR_TAG); + result.common_error = init_common_error(which_error, error_code); + constellation_send_result(&result); +} + +void constellation_send_result(const constellation_result_t *result) { + // TODO: Set all option files + uint8_t buffer[1700] = {0}; + size_t bytes_encoded = 0; + ASSERT(encode_constellation_result( + result, buffer, sizeof(buffer), &bytes_encoded)); + send_response_to_host(&buffer[0], bytes_encoded); +} + +bool constellation_get_query(constellation_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_constellation_query( + event.usb_event.p_msg, event.usb_event.msg_size, query)) { + return false; + } + + if (!check_constellation_query(query, exp_query_tag)) { + return false; + } + + return true; +} diff --git a/apps/constellation_app/constellation_api.h b/apps/constellation_app/constellation_api.h new file mode 100644 index 000000000..57565db62 --- /dev/null +++ b/apps/constellation_app/constellation_api.h @@ -0,0 +1,119 @@ +/** + * @file constellation_api.h + * @author Cypherock X1 Team + * @brief Header file to export some helper functions for the CONSTELLATION + * app + * @copyright Copyright (c) 2025 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + */ +#ifndef CONSTELLATION_API_H +#define CONSTELLATION_API_H + +/***************************************************************************** + * INCLUDES + *****************************************************************************/ + +#include +#include + +/***************************************************************************** + * MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * EXPORTED VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTION PROTOTYPES + *****************************************************************************/ + +/** + * @brief API to decode query from host with `CONSTELLATION_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 constellation_query_t obj to copy the decoded + * result to + * @return bool True if decoding was successful, else false + */ +bool decode_constellation_query(const uint8_t *data, + uint16_t data_size, + constellation_query_t *query_out); + +/** + * @brief Encodes the CONSTELLATION result with `CONSTELLATION_RESULT_FIELDS` to + * byte-stream + * + * @param[in] result: object of populated @ref constellation_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_constellation_result(const constellation_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 + * `constellation_query_t` matches against the expected tag. + * + * @param query The query of type `constellation_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_constellation_query(const constellation_query_t *query, + pb_size_t exp_query_tag); + +/** + * @brief Returns zero initialized object of type + * constellation_result_t result_tag set in result.which_response field + * + * @param result_tag Result tag to be set in the constellation_result_t result + * @return constellation_result_t Result object of type constellation_result_t + */ +constellation_result_t init_constellation_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 constellation_send_error(pb_size_t which_error, uint32_t error_code); + +/** + * @brief This API encodes constellation_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_constellation_result internally. + * + * @param result The result which needs to be sent to the host. + */ +void constellation_send_result(const constellation_result_t *result); + +/** + * @brief This API receives request of type constellation_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 constellation_get_query(constellation_query_t *query, + pb_size_t exp_query_tag); + +#endif diff --git a/apps/constellation_app/constellation_context.h b/apps/constellation_app/constellation_context.h new file mode 100644 index 000000000..99999c4b8 --- /dev/null +++ b/apps/constellation_app/constellation_context.h @@ -0,0 +1,63 @@ +/** + * @file constellation_context.h + * @author Cypherock X1 Team + * @brief Header file defining typedefs and MACROS for the CONSTELLATION app + * @copyright Copyright (c) 2025 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + */ +#ifndef CONSTELLATION_CONTEXT_H +#define CONSTELLATION_CONTEXT_H + +/***************************************************************************** + * INCLUDES + *****************************************************************************/ +#include +#include + +/***************************************************************************** + * MACROS AND DEFINES + *****************************************************************************/ +#define CONSTELLATION_NAME "CONSTELLATION" +#define CONSTELLATION_LUNIT "DAG" + +#define CONSTELLATION_IMPLICIT_ACCOUNT_DEPTH 5 + +#define CONSTELLATION_PURPOSE_INDEX 0x8000002C // 44' +#define CONSTELLATION_COIN_INDEX 0x80000000 + 1137 // 1137' +#define CONSTELLATION_ACCOUNT_INDEX 0x80000000 // 0' +#define CONSTELLATION_CHANGE_INDEX 0x00000000 // 0 + +#define CONSTELLATION_PUB_KEY_SIZE 65 +#define CONSTELLATION_ACCOUNT_ADDRESS_SIZE 40 +#define PKCS_PREFIX_SIZE 23 +#define PKCS_PREFIXED_PUBKEY_SIZE PKCS_PREFIX_SIZE + CONSTELLATION_PUB_KEY_SIZE +#define SHA256_DIGEST_SIZE 32 +#define BS58_ENCODED_SIZE 45 +#define CONSTELLATION_TXN_HASH_LENGTH 64 +#define CONSTELLATION_SIGN_MSG_PREFIX_LENGTH 31 +#define CONSTELLATION_SIGN_DATA_PREFIX_LENGTH 28 + +#define MAX_ALLOWED_SIZE 20480 + +/** + * TODO: update the size of msg data same as MAX_ALLOWED_SIZE. + * Constraints : The LVGL buffer cannot handle more than 3Kb data size which + * puts a limit on how much data can be displayed on the device. Possible fix is + * to show the long messages in chunks in line with max LVGL buffer size. + */ +#define MAX_MSG_DATA_SIZE 3072 + +/***************************************************************************** + * TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * EXPORTED VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTION PROTOTYPES + *****************************************************************************/ + +#endif /* CONSTELLATION_CONTEXT_H */ \ No newline at end of file diff --git a/apps/constellation_app/constellation_helpers.c b/apps/constellation_app/constellation_helpers.c new file mode 100644 index 000000000..d644029a2 --- /dev/null +++ b/apps/constellation_app/constellation_helpers.c @@ -0,0 +1,265 @@ +/** + * @file constellation_helpers.c + * @author Cypherock X1 Team + * @brief Utilities specific to Constellation chains + * @copyright Copyright (c) 2025 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 "constellation_helpers.h" + +#include +#include +#include + +#include "coin_utils.h" +#include "constellation_context.h" + +/***************************************************************************** + * EXTERN VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTION PROTOTYPES + *****************************************************************************/ + +/***************************************************************************** + * STATIC VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTIONS + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTIONS + *****************************************************************************/ + +bool constellation_derivation_path_guard(const uint32_t *path, uint8_t levels) { + bool status = false; + if (levels != CONSTELLATION_IMPLICIT_ACCOUNT_DEPTH) { + return status; + } + + uint32_t purpose = path[0], coin = path[1], account = path[2], + change = path[3], address = path[4]; + + // m/44'/144'/0'/0/i + status = (CONSTELLATION_PURPOSE_INDEX == purpose && + CONSTELLATION_COIN_INDEX == coin && + CONSTELLATION_ACCOUNT_INDEX == account && + CONSTELLATION_CHANGE_INDEX == change && is_non_hardened(address)); + + return status; +} + +/// Ref: +// https://github.com/StardustCollective/dag4.js/blob/main/packages/dag4-keystore/src/transaction-v2.ts#L113 +size_t encode_txn(const constellation_transaction_t *txn, char *output) { + char temp[1024] = ""; + char buffer[32] = ""; + size_t output_len = 0; + + // Parent count (always "2") + strcat(temp, "2"); + output_len++; + + // Source address length and value + size_t src_length = strnlen(txn->source, CONSTELLATION_ACCOUNT_ADDRESS_SIZE); + size_t src_length_string_len = + snprintf(buffer, sizeof(buffer), "%u", src_length); + strncat(temp, buffer, src_length_string_len); + strncat(temp, txn->source, src_length); + output_len += src_length_string_len + src_length; + + // Destination address length and value + size_t dest_length = + strnlen(txn->destination, CONSTELLATION_ACCOUNT_ADDRESS_SIZE); + size_t dest_length_string_len = + snprintf(buffer, sizeof(buffer), "%u", dest_length); + strncat(temp, buffer, dest_length_string_len); + strncat(temp, txn->destination, dest_length); + output_len += dest_length_string_len + dest_length; + + // Amount length and value (hex string) + char hex_str_amount[16] = ""; + size_t hex_str_amount_length = + snprintf(hex_str_amount, sizeof(hex_str_amount), "%llx", txn->amount); + size_t hex_str_amount_length_string_len = + snprintf(buffer, sizeof(buffer), "%u", hex_str_amount_length); + strncat(temp, buffer, hex_str_amount_length_string_len); + strncat(temp, hex_str_amount, hex_str_amount_length); + output_len += hex_str_amount_length_string_len + hex_str_amount_length; + + // Parent hash length and value + size_t parent_hash_length = + strnlen(txn->parent.hash, CONSTELLATION_TXN_HASH_LENGTH); + size_t parent_hash_length_string_len = + snprintf(buffer, sizeof(buffer), "%u", parent_hash_length); + strncat(temp, buffer, parent_hash_length_string_len); + strncat(temp, txn->parent.hash, parent_hash_length); + output_len += parent_hash_length_string_len + parent_hash_length; + + // Ordinal length and value + char str_ordinal[32] = ""; + size_t str_ordinal_length = + snprintf(str_ordinal, sizeof(str_ordinal), "%lu", txn->parent.ordinal); + size_t str_ordinal_length_string_len = + snprintf(buffer, sizeof(buffer), "%u", str_ordinal_length); + strncat(temp, buffer, str_ordinal_length_string_len); + strncat(temp, str_ordinal, str_ordinal_length); + output_len += str_ordinal_length_string_len + str_ordinal_length; + + // Fee length and value + char str_fee[32] = ""; + size_t str_fee_length = snprintf(str_fee, sizeof(str_fee), "%lu", txn->fee); + size_t str_fee_length_string_len = + snprintf(buffer, sizeof(buffer), "%u", str_fee_length); + strncat(temp, buffer, str_fee_length_string_len); + strncat(temp, str_fee, str_fee_length); + output_len += str_fee_length_string_len + str_fee_length; + + // Salt length and value + size_t salt_length = strnlen(txn->salt, 16); + size_t salt_length_string_len = + snprintf(buffer, sizeof(buffer), "%u", salt_length); + strncat(temp, buffer, salt_length_string_len); + strncat(temp, txn->salt, salt_length); + output_len += salt_length_string_len + salt_length; + + strncpy(output, temp, output_len); + + return output_len; +} + +// Encode a variable-length integer (similar to utf8Length in JS) +/// Ref: +// https://github.com/StardustCollective/dag4.js/blob/main/packages/dag4-keystore/src/tx-encode.ts#L81 +void encode_var_length(uint32_t value, uint8_t *output, size_t *out_len) { + size_t position = 0; + + if (value < (1 << 6)) { + output[position++] = (value | 0x80); + } else if (value < (1 << 13)) { + output[position++] = ((value & 0x3F) | 0xC0); + output[position++] = (value >> 6); + } else if (value < (1 << 20)) { + output[position++] = ((value & 0x3F) | 0xC0); + output[position++] = ((value >> 6) | 0x80); + output[position++] = (value >> 13); + } else if (value < (1 << 27)) { + output[position++] = ((value & 0x3F) | 0xC0); + output[position++] = ((value >> 6) | 0x80); + output[position++] = ((value >> 13) | 0x80); + output[position++] = (value >> 20); + } else { + output[position++] = ((value & 0x3F) | 0xC0); + output[position++] = ((value >> 6) | 0x80); + output[position++] = ((value >> 13) | 0x80); + output[position++] = ((value >> 20) | 0x80); + output[position++] = (value >> 27); + } + *out_len = position; +} + +/// Ref: +// https://github.com/StardustCollective/dag4.js/blob/main/packages/dag4-keystore/src/tx-encode.ts#L71 +void kryo_serialize(const char *msg, + size_t msg_len, + uint8_t *output, + size_t *out_len) { + uint8_t length_encoded[5] = {0}; + size_t len = 0; + + // Prefix is "03" + utf8Length(msg length + 1) + encode_var_length(msg_len + 1, length_encoded, &len); + + size_t index = 0; + output[index++] = 0x03; // Prefix "03" + + // Append length encoding + memcpy(&output[index], length_encoded, len); + index += len; + + // Append msg as raw bytes + memcpy(&output[index], msg, msg_len); + index += msg_len; + + *out_len = index; +} + +/// Ref: +// https://github.com/StardustCollective/dag4.js/blob/main/packages/dag4-keystore/src/key-store.ts#L337 +void serialize_txn(const constellation_transaction_t *txn, + uint8_t *output, + size_t *output_len) { + char encoded_txn[1024] = ""; + size_t encoded_txn_len = encode_txn(txn, encoded_txn); + + kryo_serialize(encoded_txn, encoded_txn_len, output, output_len); +} diff --git a/apps/constellation_app/constellation_helpers.h b/apps/constellation_app/constellation_helpers.h new file mode 100644 index 000000000..35ed11faa --- /dev/null +++ b/apps/constellation_app/constellation_helpers.h @@ -0,0 +1,69 @@ +/** + * @file constellation_helpers.h + * @author Cypherock X1 Team + * @brief Utilities api definitions for CONSTELLATION chains + * @copyright Copyright (c) 2025 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + */ +#ifndef CONSTELLATION_HELPERS_H +#define CONSTELLATION_HELPERS_H + +/***************************************************************************** + * INCLUDES + *****************************************************************************/ + +#include +#include +#include + +#include "constellation/sign_txn.pb.h" + +/***************************************************************************** + * MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * EXPORTED VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTION PROTOTYPES + *****************************************************************************/ + +/** + * @brief Verifies the derivation path. + * @details The derivation depth is fixed at level 5. So if the depth level != + * 5, then this function return false indicating invalid derivation path. The + * function supports checking derivation paths for HD wallets Types of + * derivations: address: m/44'/144'/0'/0/i + * + * @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 constellation_derivation_path_guard(const uint32_t *path, uint8_t levels); + +/** + * @brief Serializes a constellation_transaction_t. + * @details Encodes the transaction as per dag4.js library and + * serializes the encoded txn with kryo_serialize + * + * @param[in] txn The constellation_transaction_t to serialize + * @param[out] output The buffer to hold the serialized txn + * @param[out] output_len The length of the output serialized txn + * + * @return None + */ +void serialize_txn(const constellation_transaction_t *txn, + uint8_t *output, + size_t *output_len); + +#endif // CONSTELLATION_HELPERS_H \ No newline at end of file diff --git a/apps/constellation_app/constellation_main.c b/apps/constellation_app/constellation_main.c new file mode 100644 index 000000000..3fe308d85 --- /dev/null +++ b/apps/constellation_app/constellation_main.c @@ -0,0 +1,158 @@ +/** + * @file constellation_main.c + * @author Cypherock X1 Team + * @brief A common entry point to various CONSTELLATION 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 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 "constellation_main.h" + +#include "constellation_api.h" +#include "constellation_priv.h" +#include "status_api.h" + +/***************************************************************************** + * EXTERN VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTION PROTOTYPES + *****************************************************************************/ +/** + * @brief Entry point for the CONSTELLATION application of the X1 vault. It is + * invoked by the X1 vault firmware, as soon as there is a USB request raised + * for the Constellation app. + * + * @param usb_evt The USB event which triggered invocation of the constellation + * app + */ +void constellation_main(usb_event_t usb_evt, + const void *constellation_app_config); + +/***************************************************************************** + * STATIC VARIABLES + *****************************************************************************/ + +static const cy_app_desc_t constellation_app_desc = {.id = 23, + .version = + { + .major = 1, + .minor = 0, + .patch = 0, + }, + .app = constellation_main, + .app_config = NULL}; + +/***************************************************************************** + * STATIC FUNCTIONS + *****************************************************************************/ +void constellation_main(usb_event_t usb_evt, + const void *constellation_app_config) { + constellation_query_t query = CONSTELLATION_QUERY_INIT_DEFAULT; + + if (false == + decode_constellation_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 CONSTELLATION_QUERY_GET_PUBLIC_KEYS_TAG: + case CONSTELLATION_QUERY_GET_USER_VERIFIED_PUBLIC_KEY_TAG: { + constellation_get_pub_keys(&query); + break; + } + case CONSTELLATION_QUERY_SIGN_TXN_TAG: { + constellation_sign_transaction(&query); + break; + } + case CONSTELLATION_QUERY_SIGN_MSG_TAG: { + constellation_sign_msg(&query); + break; + } + default: { + /* In case we ever encounter invalid query, convey to the host app */ + constellation_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_QUERY); + break; + } + } +} + +/***************************************************************************** + * GLOBAL FUNCTIONS + *****************************************************************************/ +const cy_app_desc_t *get_constellation_app_desc() { + return &constellation_app_desc; +} \ No newline at end of file diff --git a/apps/constellation_app/constellation_main.h b/apps/constellation_app/constellation_main.h new file mode 100644 index 000000000..9dbaa2461 --- /dev/null +++ b/apps/constellation_app/constellation_main.h @@ -0,0 +1,43 @@ +/** + * @file constellation_main.h + * @author Cypherock X1 Team + * @brief + * @details + + * @copyright Copyright (c) 2025 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + * + */ + +#ifndef CONSTELLATION_MAIN_H +#define CONSTELLATION_MAIN_H + +/***************************************************************************** + * INCLUDES + *****************************************************************************/ + +#include "app_registry.h" +#include "events.h" +/***************************************************************************** + * MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * EXPORTED VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTION PROTOTYPES + *****************************************************************************/ +/** + * @brief Returns the config for CONSTELLATION chain app descriptor + * + * @return A const reference to cy_app_desc_t + */ +const cy_app_desc_t *get_constellation_app_desc(); +#endif /* CONSTELLATION_MAIN_H */ diff --git a/apps/constellation_app/constellation_priv.h b/apps/constellation_app/constellation_priv.h new file mode 100644 index 000000000..bf64d6b32 --- /dev/null +++ b/apps/constellation_app/constellation_priv.h @@ -0,0 +1,83 @@ +/** + * @file constellation_priv.h + * @author Cypherock X1 Team + * @brief Support for constellation app internal operations + * This file is defined to separate CONSTELLATION's internal use + * functions, flows, common APIs + * @copyright Copyright (c) 2024 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + */ +#ifndef CONSTELLATION_PRIV_H +#define CONSTELLATION_PRIV_H +/***************************************************************************** + * INCLUDES + *****************************************************************************/ +#include +#include + +#include "constellation/sign_msg.pb.h" +#include "constellation_context.h" + +/***************************************************************************** + * TYPEDEFS + *****************************************************************************/ +typedef struct { + /** + * The structure holds the wallet information of the transaction. + * @note Populated by constellation_handle_initiate_query() + */ + constellation_sign_txn_initiate_request_t init_info; + + const constellation_transaction_t *txn; + +} constellation_txn_context_t; + +typedef struct { + /// @brief Contains initialization data for constellation sign msg received + /// from host + constellation_sign_msg_initiate_request_t init; + + /// @brief Pointer to msg data in raw format, size from init member is + /// allocated dynamically with a max size cap of @ref MAX_MSG_DATA_SIZE + uint8_t *msg_data; + +} constellation_sign_msg_context_t; + +/***************************************************************************** + * EXPORTED VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTION PROTOTYPES + *****************************************************************************/ + +/** + * @brief Handler for CONSTELLATION public key derivation. + * @details This flow expects CONSTELLATION_GET_PUBLIC_KEY_REQUEST_INITIATE_TAG + * as initial query, otherwise the flow is aborted + * + * @param query object for address public key query + */ +void constellation_get_pub_keys(constellation_query_t *query); + +/** + * @brief Handler for signing a transaction on constellation. + * @details The expected request type is + * CONSTELLATION_SIGN_TXN_REQUEST_INITIATE_TAG. The function controls the + * complete data exchange with host, user prompts and confirmations for signing + * an CONSTELLATION based transaction. + * + * @param query Reference to the decoded query struct from the host app + */ +void constellation_sign_transaction(constellation_query_t *query); + +/** + * @brief This function signs a message with or without(blind sign) + * user verification and sends the signature as a response. + * + * @param query Reference to the decoded query struct from the host app + */ +void constellation_sign_msg(constellation_query_t *query); + +#endif /* CONSTELLATION_PRIV_H */ \ No newline at end of file diff --git a/apps/constellation_app/constellation_pub_key.c b/apps/constellation_app/constellation_pub_key.c new file mode 100644 index 000000000..4435f3d22 --- /dev/null +++ b/apps/constellation_app/constellation_pub_key.c @@ -0,0 +1,531 @@ +/** + * @file constellation_pub_key.c + * @author Cypherock X1 Team + * @brief Generates public key for CONSTELLATION derivations. + * @copyright Copyright (c) 2025 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 +#include + +#include "base58.h" +#include "bip32.h" +#include "coin_utils.h" +#include "composable_app_queue.h" +#include "constellation_api.h" +#include "constellation_context.h" +#include "constellation_helpers.h" +#include "constellation_priv.h" +#include "curves.h" +#include "ecdsa.h" +#include "exchange_main.h" +#include "hasher.h" +#include "reconstruct_wallet_flow.h" +#include "secp256k1.h" +#include "sha2.h" +#include "status_api.h" +#include "ui_core_confirm.h" +#include "ui_screens.h" +#include "wallet_list.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 constellation_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 constellation_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 + * constellation_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 constellation_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 + * constellation_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 constellation_get_public_keys_derivation_path_t *path, + const uint8_t *seed, + uint8_t public_key_list[][CONSTELLATION_PUB_KEY_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 constellation_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( + constellation_query_t *query, + const uint8_t pubkey_list[][CONSTELLATION_PUB_KEY_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 CONSTELLATION. 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; + +/// Ref: +/// https://github.com/StardustCollective/dag4.js/blob/main/packages/dag4-keystore/src/key-store.ts#L39 +static const uint8_t PKCS_PREFIX[PKCS_PREFIX_SIZE] = { + 0x30, 0x56, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, + 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x0a, 0x03, 0x42, 0x00}; + +static bool check_which_request(const constellation_query_t *query, + pb_size_t which_request) { + if (which_request != query->get_public_keys.which_request) { + constellation_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_REQUEST); + return false; + } + + return true; +} + +static bool validate_request( + const constellation_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 + constellation_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_DATA); + status = false; + } + + if (CONSTELLATION_QUERY_GET_USER_VERIFIED_PUBLIC_KEY_TAG == which_request && + 1 < count) { + // `CONSTELLATION_QUERY_GET_USER_VERIFIED_PUBLIC_KEY_TAG` request contains + // more than one derivation path which is not expected + constellation_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_DATA); + status = false; + } + + const constellation_get_public_keys_derivation_path_t *path = NULL; + + for (pb_size_t index = 0; index < count; index++) { + path = &req->derivation_paths[index]; + if (!constellation_derivation_path_guard(path->path, path->path_count)) { + constellation_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_public_key(const uint8_t *seed, + const uint32_t *path, + uint32_t path_length, + uint8_t *public_key) { + HDNode node = {0}; + + if (!derive_hdnode_from_path( + path, path_length, SECP256K1_NAME, seed, &node)) { + // send unknown error; unknown failure reason + constellation_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 1); + memzero(&node, sizeof(HDNode)); + return false; + } + + if (NULL != public_key) { + ecdsa_uncompress_pubkey(&secp256k1, node.public_key, public_key); + } + + memzero(&node, sizeof(HDNode)); + return true; +} + +static bool fill_public_keys( + const constellation_get_public_keys_derivation_path_t *path, + const uint8_t *seed, + uint8_t public_key_list[][CONSTELLATION_PUB_KEY_SIZE], + pb_size_t count) { + for (pb_size_t index = 0; index < count; index++) { + const constellation_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 bool send_public_keys( + constellation_query_t *query, + const uint8_t pubkey_list[][CONSTELLATION_PUB_KEY_SIZE], + const pb_size_t count, + const pb_size_t which_request, + const pb_size_t which_response) { + constellation_result_t response = init_constellation_result(which_response); + constellation_get_public_keys_result_response_t *result = + &response.get_public_keys.result; + size_t batch_limit = sizeof(response.get_public_keys.result.public_keys) / + CONSTELLATION_PUB_KEY_SIZE; + size_t remaining = count; + + response.get_public_keys.which_response = + CONSTELLATION_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 * CONSTELLATION_PUB_KEY_SIZE); + + constellation_send_result(&response); + remaining -= batch_size; + if (0 == remaining) { + break; + } + + if (!constellation_get_query(query, which_request) || + !check_which_request( + query, CONSTELLATION_GET_PUBLIC_KEYS_REQUEST_FETCH_NEXT_TAG)) { + return false; + } + } + return true; +} + +static bool get_user_consent(const pb_size_t which_request, + const char *wallet_name) { + char msg[100] = ""; + + if (CONSTELLATION_QUERY_GET_PUBLIC_KEYS_TAG == which_request) { + snprintf(msg, + sizeof(msg), + UI_TEXT_ADD_ACCOUNT_PROMPT, + CONSTELLATION_NAME, + wallet_name); + } else { + snprintf(msg, + sizeof(msg), + UI_TEXT_RECEIVE_PROMPT, + CONSTELLATION_NAME, + wallet_name); + } + + return core_scroll_page(NULL, msg, constellation_send_error); +} + +/***************************************************************************** + * GLOBAL VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTIONS + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTIONS + *****************************************************************************/ +bool generate_dag_address(char *address, const uint8_t *pubkey) { + // address = 'DAG' + parity + bs58_last36 + // bs58_last36 = last 36 chars of bs58enc(sha256(pkcs_prefixed_pubkey)) + // parity = sum(numeric chars in bs58_last36) % 9 + // pkcs_prefixed_pubkey = pkcs_prefix + uncompressed_pubkey + // see + // https://github.com/StardustCollective/dag4.js/blob/main/packages/dag4-keystore/src/key-store.ts#L230 + + uint8_t pkcs_prefixed_pubkey[PKCS_PREFIXED_PUBKEY_SIZE] = {0}; + uint8_t key_digest[SHA256_DIGEST_SIZE] = {0}; + char bs58_encoded_key[BS58_ENCODED_SIZE] = "\0"; + size_t res_size = BS58_ENCODED_SIZE; + + memcpy(pkcs_prefixed_pubkey, PKCS_PREFIX, PKCS_PREFIX_SIZE); + memcpy(pkcs_prefixed_pubkey + PKCS_PREFIX_SIZE, + pubkey, + CONSTELLATION_PUB_KEY_SIZE); + + sha256_Raw(pkcs_prefixed_pubkey, sizeof(pkcs_prefixed_pubkey), key_digest); + + if (!b58enc(bs58_encoded_key, &res_size, key_digest, SHA256_DIGEST_SIZE) || + res_size < 36) { + constellation_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 2); + return false; + } + + const char *bs58_last36 = bs58_encoded_key + res_size - 36 - 1; + + uint64_t sum = 0; + for (size_t i = 0; i < 36; i++) { + if (bs58_last36[i] >= '0' && bs58_last36[i] <= '9') { + sum += bs58_last36[i] - '0'; + } + } + char parity = (sum % 9) + '0'; + + snprintf(address, + CONSTELLATION_ACCOUNT_ADDRESS_SIZE + 1, + "DAG%c%.*s", + parity, + 36, + bs58_last36); + + return true; +} + +void constellation_get_pub_keys(constellation_query_t *query) { + char wallet_name[NAME_SIZE] = ""; + uint8_t seed[64] = {0}; + + const pb_size_t which_request = query->which_request; + const constellation_get_public_keys_intiate_request_t *init_req = NULL; + pb_size_t which_response; + + if (CONSTELLATION_QUERY_GET_PUBLIC_KEYS_TAG == which_request) { + which_response = CONSTELLATION_RESULT_GET_PUBLIC_KEYS_TAG; + init_req = &query->get_public_keys.initiate; + } else { + which_response = CONSTELLATION_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(constellation_get_public_keys_derivation_path_t)] + [CONSTELLATION_PUB_KEY_SIZE] = {0}; + + if (!check_which_request( + query, CONSTELLATION_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, + constellation_send_error)) { + return; + } + + // Take user consent to export public key for the wallet + if (!get_user_consent(which_request, wallet_name)) { + return; + } + + set_app_flow_status(CONSTELLATION_GET_PUBLIC_KEYS_STATUS_CONFIRM); + + if (!reconstruct_seed( + init_req->wallet_id, &seed[0], constellation_send_error)) { + memzero(seed, sizeof(seed)); + return; + } + + set_app_flow_status(CONSTELLATION_GET_PUBLIC_KEYS_STATUS_SEED_GENERATED); + delay_scr_init(ui_text_processing, DELAY_SHORT); + + bool result = + fill_public_keys(init_req->derivation_paths, seed, pubkey_list, count); + + // Clear seed as soon as it is not needed + memzero(seed, sizeof(seed)); + + if (!result) { + // send unknown error; do not know failure reason + constellation_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 1); + return; + } + + // In case the request is to `CONSTELLATION_QUERY_GET_PUBLIC_KEY_TAG` type, + // then wait for user verification of the address + if (CONSTELLATION_QUERY_GET_USER_VERIFIED_PUBLIC_KEY_TAG == which_request) { + char address[CONSTELLATION_ACCOUNT_ADDRESS_SIZE + 1] = "\0"; + if (!generate_dag_address(address, pubkey_list[0])) { + return; + } + + if (sign_address) { + exchange_sign_address(address, sizeof(address)); + } + + if (!core_scroll_page( + ui_text_receive_on, address, constellation_send_error)) { + return; + } + set_app_flow_status(CONSTELLATION_GET_PUBLIC_KEYS_STATUS_VERIFY); + } + + if (!send_public_keys( + query, pubkey_list, count, which_request, which_response)) { + return; + } + + delay_scr_init(ui_text_check_software_wallet_app, DELAY_TIME); +} diff --git a/apps/constellation_app/constellation_sign_msg.c b/apps/constellation_app/constellation_sign_msg.c new file mode 100644 index 000000000..a01610b0c --- /dev/null +++ b/apps/constellation_app/constellation_sign_msg.c @@ -0,0 +1,577 @@ +/** + * @file constellation_sign_msg.c + * @author Cypherock X1 Team + * @brief Signs messages for CONSTELLATION + *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 +#include +#include + +#include "atca_helpers.h" +#include "atca_status.h" +#include "coin_utils.h" +#include "constant_texts.h" +#include "constellation/sign_msg.pb.h" +#include "constellation_api.h" +#include "constellation_context.h" +#include "constellation_helpers.h" +#include "constellation_priv.h" +#include "pb_decode.h" +#include "reconstruct_wallet_flow.h" +#include "sha2.h" +#include "status_api.h" +#include "ui_core_confirm.h" +#include "ui_screens.h" +#include "wallet_list.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 CONSTELLATION app and return + * false. + * + * @param query Reference to an instance of constellation_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 constellation_query_t *query, + pb_size_t which_request); + +/** + * @brief The function checks if the given `init_req` is a valid request for + * message signing and returns true if it is, otherwise it returns false and + * sends an error message to host. + * + * @param init_req A pointer to a structure of type + * `constellation_sign_msg_initiate_request_t`. + * + * @return a boolean value indicating init_req is valid or not. + */ +static bool validate_initiate_query( + constellation_sign_msg_initiate_request_t *init_req); + +/** + * @brief The function handles the initiation of a query to sign a message for a + * specific wallet. + * @details It performs the following tasks in response to a valid initiate + * request. + * - Get user confirmation on the wallet and coin for which flow is requested. + * - Copy the init request data to sign_msg_ctx. + * + * @param query Reference to the decoded query struct from the host app + * + * @return a boolean value. + */ +static bool handle_initiate_query(constellation_query_t *query); + +/** + * @brief This function is responsible for retrieving and assembling message + * data chunks for signing. + * + * @param query Reference to the decoded query struct from the host app + * + * @return a boolean value indicating if the message data has been reconstructed + * from chunks correctly or not. + */ +static bool get_msg_data(constellation_query_t *query); + +/** + * @brief This function displays the message for verification in case of + * verified sign. + * + * @return a boolean indicating user verification or the rejection. + */ +static bool get_user_verification(); + +/** + * @brief This function generates a signature for a message using a given + * derivation path and private key. + * + * @param sig The parameter `sig` is a pointer to a structure of type + * `constellation_sign_msg_signature_response_t`. + * + * @return a boolean value indicating if signautre for msg data is generated + * correctly or not. + */ +static bool get_msg_data_signature( + constellation_sign_msg_signature_response_t *sig); + +/** + * @brief This function sends a signature response for a sign message query. + * + * @param query A pointer to an constellation_query_t struct, which contains + * information about the query being processed. + * @param sig The parameter `sig` is of type + * `constellation_sign_msg_signature_response_t`, which is a structure + * containing the signature response for a sign message request. + * + * @return a boolean value indicating if the signature is sent to the host + * correctly or not. + */ +static bool send_signature(constellation_query_t *query, + constellation_sign_msg_signature_response_t *sig); + +/** + * @brief This function generates prefixed message/data for signing. + * + * @param ctx A pointer to a constellation_sign_msg_context_t struct, which + * contains information about the message/data to which prefixed needs to be + * added. + * @param prefix The prefix to be added to the message/data. + * @param prefixed_msg_data The buffer to hold the resultant prefixed + * message/data. + * + * @return The length of the resultant prefixed message/data. + */ + +static size_t constellation_get_prefixed_msg_data( + const constellation_sign_msg_context_t *ctx, + const char *prefix, + size_t prefix_len, + char *prefixed_msg_data); + +/** + * @brief This function generates message/data digest for signing by + * hashing the prefixed message/data. + * + * @param ctx A pointer to a constellation_sign_msg_context_t struct, which + * contains information about the message/data which needs to be hashed. + * @param digest The buffer to hold the hash digest. + * + * @return A boolean whether msg data digest was generated successfully or not. + */ + +static bool constellation_get_msg_data_digest( + const constellation_sign_msg_context_t *ctx, + uint8_t *digest); + +/***************************************************************************** + * STATIC VARIABLES + *****************************************************************************/ +static constellation_sign_msg_context_t sign_msg_ctx; + +static const char *sign_message_prefix = "\031Constellation Signed Message:\n"; +static const char *sign_data_prefix = "\031Constellation Signed Data:\n"; + +// for atcab_base64decode_ defined in atca_helpers.c as the original default is +// conditionally defined +uint8_t atcab_b64rules[4] = {'+', '/', '=', 64}; +/***************************************************************************** + * GLOBAL VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTIONS + *****************************************************************************/ + +static bool check_which_request(const constellation_query_t *query, + pb_size_t which_request) { + if (which_request != query->get_public_keys.which_request) { + constellation_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_REQUEST); + return false; + } + + return true; +} + +static bool validate_initiate_query( + constellation_sign_msg_initiate_request_t *init_req) { + bool status = false; + + status = constellation_derivation_path_guard(init_req->derivation_path, + init_req->derivation_path_count); + + uint32_t size_limit = MAX_ALLOWED_SIZE; + if (init_req->message_type == CONSTELLATION_SIGN_MSG_TYPE_SIGN_TYPED_MSG || + init_req->message_type == + CONSTELLATION_SIGN_MSG_TYPE_SIGN_ARBITRARY_DATA) { + size_limit = MAX_MSG_DATA_SIZE; + } + + status = size_limit >= init_req->message_size; + + if (!status) { + constellation_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_DATA); + return false; + } + + return true; +} + +static bool handle_initiate_query(constellation_query_t *query) { + uint8_t wallet_name[NAME_SIZE] = {0}; + constellation_result_t response = + init_constellation_result(CONSTELLATION_RESULT_SIGN_MSG_TAG); + if (!check_which_request(query, + CONSTELLATION_SIGN_MSG_REQUEST_INITIATE_TAG) || + !validate_initiate_query(&query->sign_msg.initiate) || + !get_wallet_name_by_id(query->sign_msg.initiate.wallet_id, + (uint8_t *)wallet_name, + constellation_send_error)) { + return false; + } + + char *ui_text_prompt = UI_TEXT_SIGN_MSG_PROMPT; + if (query->sign_msg.initiate.message_type == + CONSTELLATION_SIGN_MSG_TYPE_SIGN_ARBITRARY_DATA) { + ui_text_prompt = UI_TEXT_SIGN_DATA_PROMPT; + } + + char msg[200] = ""; + snprintf(msg, sizeof(msg), ui_text_prompt, CONSTELLATION_NAME, wallet_name); + + if (!core_confirmation(msg, constellation_send_error)) { + return false; + } + + set_app_flow_status(CONSTELLATION_SIGN_MSG_STATUS_CONFIRM); + + memcpy(&(sign_msg_ctx.init), + &(query->sign_msg.initiate), + sizeof(query->sign_msg.initiate)); + + response.sign_msg.which_response = + CONSTELLATION_SIGN_MSG_RESPONSE_CONFIRMATION_TAG; + response.sign_msg.confirmation.dummy_field = 0; + constellation_send_result(&response); + + // 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 get_msg_data(constellation_query_t *query) { + constellation_result_t response = + init_constellation_result(CONSTELLATION_RESULT_SIGN_MSG_TAG); + uint32_t total_size = sign_msg_ctx.init.message_size; + const constellation_sign_msg_data_t *msg_data = &query->sign_msg.msg_data; + const common_chunk_payload_t *payload = &(msg_data->chunk_payload); + const common_chunk_payload_chunk_t *chunk = &(payload->chunk); + + uint32_t size = 0; + + /** + * Allocate required memory for message size +1. Extra byte is used to add a + * NULL character at the end of the msg data in case it'll be used as a string + */ + sign_msg_ctx.msg_data = malloc(total_size + 1); + ASSERT(NULL != sign_msg_ctx.msg_data); + sign_msg_ctx.msg_data[total_size] = '\0'; + + while (1) { + // Get next data chunk from host + if (!constellation_get_query(query, CONSTELLATION_QUERY_SIGN_MSG_TAG) || + !check_which_request(query, + CONSTELLATION_SIGN_MSG_REQUEST_MSG_DATA_TAG)) { + return false; + } + + if (false == query->sign_msg.msg_data.has_chunk_payload || + payload->chunk_index >= payload->total_chunks || + size + chunk->size > total_size) { + constellation_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_DATA); + return false; + } + + memcpy(sign_msg_ctx.msg_data + size, chunk->bytes, chunk->size); + size += chunk->size; + + // Send chunk ack to host + response.sign_msg.which_response = + CONSTELLATION_SIGN_MSG_RESPONSE_DATA_ACCEPTED_TAG; + response.sign_msg.data_accepted.has_chunk_ack = true; + response.sign_msg.data_accepted.chunk_ack.chunk_index = + payload->chunk_index; + constellation_send_result(&response); + + // If no data remaining to be received from the host, then exit + if (0 == payload->remaining_size || + payload->chunk_index + 1 == payload->total_chunks) { + break; + } + } + + if (total_size != size) { + constellation_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_DATA); + return false; + } + + return true; +} + +static bool get_user_verification() { + bool result = false; + + char *ui_text_title = UI_TEXT_VERIFY_DATA; + switch (sign_msg_ctx.init.message_type) { + case CONSTELLATION_SIGN_MSG_TYPE_SIGN_TYPED_MSG: { + ui_text_title = UI_TEXT_VERIFY_MESSAGE; + } + case CONSTELLATION_SIGN_MSG_TYPE_SIGN_ARBITRARY_DATA: { + uint8_t decoded_json[sign_msg_ctx.init.message_size]; + size_t decoded_json_len = sign_msg_ctx.init.message_size; + ATCA_STATUS decoding_status = + atcab_base64decode_((const char *)sign_msg_ctx.msg_data, + sign_msg_ctx.init.message_size, + decoded_json, + &decoded_json_len, + atcab_b64rules); + + if (decoding_status == ATCA_SUCCESS) { + // TODO: Add a limit on size of data per confirmation based on LVGL + // buffer and split message into multiple confirmations accordingly + result = core_scroll_page( + ui_text_title, (char *)decoded_json, constellation_send_error); + } + } break; + + default: { + result = core_confirmation(UI_TEXT_BLIND_SIGNING_WARNING, + constellation_send_error); + } break; + } + + if (result) { + set_app_flow_status(CONSTELLATION_SIGN_MSG_STATUS_VERIFY); + } + + return result; +} + +static size_t constellation_get_prefixed_msg_data( + const constellation_sign_msg_context_t *ctx, + const char *prefix, + size_t prefix_len, + char *prefixed_msg_data) { + size_t msg_len = ctx->init.message_size; + + char length_string[20] = ""; + size_t length_string_len = sprintf(length_string, "%u\n", msg_len); + + size_t total_len = prefix_len + length_string_len + msg_len; + + snprintf(prefixed_msg_data, + total_len + 1, // +1 for \0 + "%s%s%s", + prefix, + length_string, + (const char *)ctx->msg_data); + + return total_len; +} + +static bool constellation_get_msg_data_digest( + const constellation_sign_msg_context_t *ctx, + uint8_t *digest) { + switch (ctx->init.message_type) { + case CONSTELLATION_SIGN_MSG_TYPE_SIGN_TYPED_MSG: { + char prefixed_message[ctx->init.message_size + 100]; + size_t prefixed_message_len = constellation_get_prefixed_msg_data( + ctx, + sign_message_prefix, + strnlen(sign_message_prefix, CONSTELLATION_SIGN_MSG_PREFIX_LENGTH), + prefixed_message); + + sha512_Raw( + (const uint8_t *)prefixed_message, prefixed_message_len, digest); + } break; + + case CONSTELLATION_SIGN_MSG_TYPE_SIGN_ARBITRARY_DATA: { + char prefixed_data[ctx->init.message_size + 100]; + size_t prefixed_data_len = constellation_get_prefixed_msg_data( + ctx, + sign_data_prefix, + strnlen(sign_data_prefix, CONSTELLATION_SIGN_DATA_PREFIX_LENGTH), + prefixed_data); + + uint8_t sha256_digest[SHA256_DIGEST_LENGTH] = {0}; + sha256_Raw( + (const uint8_t *)prefixed_data, prefixed_data_len, sha256_digest); + + char sha256_hex_str[SHA256_DIGEST_LENGTH * 2 + 1] = ""; + byte_array_to_hex_string(sha256_digest, + sizeof(sha256_digest), + sha256_hex_str, + sizeof(sha256_hex_str)); + + sha512_Raw((uint8_t *)sha256_hex_str, strlen(sha256_hex_str), digest); + } break; + + default: { + sha512_Raw(ctx->msg_data, ctx->init.message_size, digest); + } break; + } + + return true; +} + +static bool get_msg_data_signature( + constellation_sign_msg_signature_response_t *sig) { + bool status = false; + HDNode node = {0}; + uint8_t seed[64] = {0}; + const size_t depth = sign_msg_ctx.init.derivation_path_count; + const uint32_t *hd_path = sign_msg_ctx.init.derivation_path; + const ecdsa_curve *curve = get_curve_by_name(SECP256K1_NAME)->params; + + if (!reconstruct_seed( + sign_msg_ctx.init.wallet_id, seed, constellation_send_error)) { + memzero(seed, sizeof(seed)); + return status; + } + + set_app_flow_status(CONSTELLATION_SIGN_MSG_STATUS_SEED_GENERATED); + delay_scr_init(ui_text_processing, DELAY_SHORT); + + status = derive_hdnode_from_path(hd_path, depth, SECP256K1_NAME, seed, &node); + + // zeroise the seed + memzero(seed, sizeof(seed)); + + if (!status) { + constellation_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_DATA); + } else { + status = true; + uint8_t digest[SHA512_DIGEST_LENGTH] = {0}; + uint8_t signature[64] = {0}; + if (!constellation_get_msg_data_digest(&sign_msg_ctx, digest) || + (0 != ecdsa_sign_digest( + curve, node.private_key, digest, signature, NULL, NULL))) { + constellation_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 1); + status = false; + } + sig->signature.size = ecdsa_sig_to_der(signature, sig->signature.bytes); + } + memzero(&node, sizeof(HDNode)); + return status; +} + +static bool send_signature(constellation_query_t *query, + constellation_sign_msg_signature_response_t *sig) { + constellation_result_t result = + init_constellation_result(CONSTELLATION_RESULT_SIGN_MSG_TAG); + result.sign_msg.which_response = + CONSTELLATION_SIGN_MSG_RESPONSE_SIGNATURE_TAG; + if (!constellation_get_query(query, CONSTELLATION_QUERY_SIGN_MSG_TAG) || + !check_which_request(query, + CONSTELLATION_SIGN_MSG_REQUEST_SIGNATURE_TAG)) { + return false; + } + + memcpy(&result.sign_msg.signature, + sig, + sizeof(constellation_sign_msg_signature_response_t)); + constellation_send_result(&result); + return true; +} + +/***************************************************************************** + * GLOBAL FUNCTIONS + *****************************************************************************/ + +void constellation_sign_msg(constellation_query_t *query) { + constellation_sign_msg_signature_response_t sig_resp = + CONSTELLATION_SIGN_MSG_SIGNATURE_RESPONSE_INIT_DEFAULT; + memzero(&sign_msg_ctx, sizeof(sign_msg_ctx)); + + if (handle_initiate_query(query) && get_msg_data(query) && + get_user_verification() && get_msg_data_signature(&sig_resp) && + send_signature(query, &(sig_resp))) { + delay_scr_init(ui_text_check_software_wallet_app, DELAY_TIME); + } + + if (NULL != sign_msg_ctx.msg_data) { + memzero(sign_msg_ctx.msg_data, sign_msg_ctx.init.message_size); + free(sign_msg_ctx.msg_data); + sign_msg_ctx.msg_data = NULL; + } + + sign_msg_ctx.init.message_size = 0; + + // Clear the dynamic allocation done for UI purposes using cy_malloc + cy_free(); +} diff --git a/apps/constellation_app/constellation_txn.c b/apps/constellation_app/constellation_txn.c new file mode 100644 index 000000000..78788c21b --- /dev/null +++ b/apps/constellation_app/constellation_txn.c @@ -0,0 +1,466 @@ +/** + * @file constellation_txn.c + * @author Cypherock X1 Team + * @brief Source file to handle transaction signing logic for CONSTELLATION + *protocol + * + * @copyright Copyright (c) 2025 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 +#include + +#include "coin_utils.h" +#include "composable_app_queue.h" +#include "constellation/core.pb.h" +#include "constellation/sign_txn.pb.h" +#include "constellation_api.h" +#include "constellation_context.h" +#include "constellation_helpers.h" +#include "constellation_priv.h" +#include "exchange_main.h" +#include "reconstruct_wallet_flow.h" +#include "status_api.h" +#include "ui_core_confirm.h" +#include "ui_screens.h" +#include "utils.h" +#include "wallet_list.h" +/***************************************************************************** + * EXTERN VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE TYPEDEFS + *****************************************************************************/ +typedef constellation_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 bitcoin app and return false. + * + * @param query Reference to an instance of constellation_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 constellation_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 constellation_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 constellation_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 + * CONSTELLATION_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 constellation_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 constellation_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 constellation_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(constellation_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 CONSTELLATION unsigned txn to the host + * @details The function waits for the host to send a request of type + * CONSTELLATION_SIGN_TXN_REQUEST_SIGNATURE_TAG and sends the response + * + * @param query Reference to buffer of type constellation_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(constellation_query_t *query, + const der_sig_t *der_signature); + +/***************************************************************************** + * STATIC VARIABLES + *****************************************************************************/ +static constellation_txn_context_t *constellation_txn_context = NULL; +static bool use_signature_verification = false; + +/***************************************************************************** + * GLOBAL VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTIONS + *****************************************************************************/ +static bool check_which_request(const constellation_query_t *query, + pb_size_t which_request) { + if (which_request != query->sign_txn.which_request) { + constellation_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) { + constellation_result_t result = + init_constellation_result(CONSTELLATION_RESULT_SIGN_TXN_TAG); + result.sign_txn.which_response = which_response; + constellation_send_result(&result); +} + +static bool validate_request_data( + const constellation_sign_txn_request_t *request) { + bool status = true; + + if (!constellation_derivation_path_guard( + request->initiate.derivation_path, + request->initiate.derivation_path_count)) { + constellation_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 constellation_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, + CONSTELLATION_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, + constellation_send_error)) { + return false; + } + + snprintf(msg, + sizeof(msg), + UI_TEXT_SIGN_TXN_PROMPT, + CONSTELLATION_NAME, + wallet_name); + // Take user consent to sign transaction for the wallet + if (!core_confirmation(msg, constellation_send_error)) { + return false; + } + + set_app_flow_status(CONSTELLATION_SIGN_TXN_STATUS_CONFIRM); + memcpy(&constellation_txn_context->init_info, + &query->sign_txn.initiate, + sizeof(constellation_sign_txn_initiate_request_t)); + + send_response(CONSTELLATION_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(constellation_query_t *query) { + if (!constellation_get_query(query, CONSTELLATION_QUERY_SIGN_TXN_TAG) && + !check_which_request(query, + CONSTELLATION_SIGN_TXN_REQUEST_TXN_DATA_TAG)) { + return false; + } + + const constellation_sign_txn_data_t *sign_txn_data = + &query->sign_txn.txn_data; + if (sign_txn_data->has_txn == false) { + return false; + } + + constellation_txn_context->txn = &sign_txn_data->txn; + + send_response(CONSTELLATION_SIGN_TXN_RESPONSE_UNSIGNED_TXN_ACCEPTED_TAG); + + return true; +} + +static bool get_user_verification(void) { + const constellation_transaction_t *txn = constellation_txn_context->txn; + + if (use_signature_verification) { + if (!exchange_validate_stored_signature((char *)txn->destination, + sizeof(txn->destination))) { + return false; + } + } + + // verify recipient address + if (!core_scroll_page( + ui_text_verify_address, txn->destination, constellation_send_error)) { + return false; + } + + // verify recipient amount + char amount_string[30] = {'\0'}; + double decimal_amount = (double)txn->amount; + decimal_amount *= 1e-8; + snprintf(amount_string, sizeof(amount_string), "%.*g", 8, decimal_amount); + + char display[100] = {'\0'}; + snprintf(display, + sizeof(display), + UI_TEXT_VERIFY_AMOUNT, + amount_string, + ""); // We don't know the which transaction(coin or token and/or + // which token) is it, hence we don't know unit + + if (!core_confirmation(display, constellation_send_error)) { + return false; + } + + // verify fee + char fee_string[30] = {'\0'}; + double decimal_fee = (double)txn->fee; + decimal_fee *= 1e-8; + snprintf(fee_string, sizeof(fee_string), "%.*g", 8, decimal_fee); + + char fee_display[100] = {'\0'}; + snprintf( + fee_display, sizeof(fee_display), UI_TEXT_VERIFY_FEE, fee_string, ""); + + if (!core_confirmation(fee_display, constellation_send_error)) { + return false; + } + + set_app_flow_status(CONSTELLATION_SIGN_TXN_STATUS_VERIFY); + + return true; +} + +static bool sign_txn(der_sig_t *der_signature) { + uint8_t seed[64] = {0}; + if (!reconstruct_seed(constellation_txn_context->init_info.wallet_id, + seed, + constellation_send_error)) { + memzero(seed, sizeof(seed)); + // TODO: handle errors of reconstruction flow + return false; + } + + set_app_flow_status(CONSTELLATION_SIGN_TXN_STATUS_SEED_GENERATED); + + uint8_t serialized_txn[1024] = {0}; + size_t serialized_txn_len = 0; + serialize_txn( + constellation_txn_context->txn, serialized_txn, &serialized_txn_len); + + uint8_t sha256_digest[SHA256_DIGEST_LENGTH] = {0}; + sha256_Raw(serialized_txn, serialized_txn_len, sha256_digest); + + char sha256_hex_str[SHA256_DIGEST_LENGTH * 2 + 1] = ""; + byte_array_to_hex_string(sha256_digest, + sizeof(sha256_digest), + sha256_hex_str, + sizeof(sha256_hex_str)); + + uint8_t sha512_digest[SHA512_DIGEST_LENGTH] = {0}; + sha512_Raw((uint8_t *)sha256_hex_str, strlen(sha256_hex_str), sha512_digest); + + HDNode hdnode = {0}; + derive_hdnode_from_path( + constellation_txn_context->init_info.derivation_path, + constellation_txn_context->init_info.derivation_path_count, + SECP256K1_NAME, + seed, + &hdnode); + + uint8_t signature[64] = {0}; + ecdsa_sign_digest( + &secp256k1, hdnode.private_key, sha512_digest, signature, NULL, NULL); + + der_signature->size = ecdsa_sig_to_der(signature, der_signature->bytes); + + memzero(serialized_txn, sizeof(serialized_txn)); + memzero(sha256_digest, sizeof(sha256_digest)); + memzero(sha512_digest, sizeof(sha512_digest)); + memzero(seed, sizeof(seed)); + memzero(&hdnode, sizeof(hdnode)); + memzero(signature, sizeof(signature)); + + return true; +} + +static bool send_signature(constellation_query_t *query, + const der_sig_t *der_signature) { + constellation_result_t result = + init_constellation_result(CONSTELLATION_RESULT_SIGN_TXN_TAG); + result.sign_txn.which_response = + CONSTELLATION_SIGN_TXN_RESPONSE_SIGNATURE_TAG; + + if (!constellation_get_query(query, CONSTELLATION_QUERY_SIGN_TXN_TAG) || + !check_which_request(query, + CONSTELLATION_SIGN_TXN_REQUEST_SIGNATURE_TAG)) { + return false; + } + + memcpy( + &result.sign_txn.signature.signature, der_signature, sizeof(der_sig_t)); + + constellation_send_result(&result); + return true; +} + +/***************************************************************************** + * GLOBAL FUNCTIONS + *****************************************************************************/ + +void constellation_sign_transaction(constellation_query_t *query) { + constellation_txn_context = (constellation_txn_context_t *)malloc( + sizeof(constellation_txn_context_t)); + memzero(constellation_txn_context, sizeof(constellation_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_software_wallet_app, DELAY_TIME); + } + + if (constellation_txn_context) { + free(constellation_txn_context); + constellation_txn_context = NULL; + } +} \ No newline at end of file diff --git a/apps/evm_family/evm_pub_key.c b/apps/evm_family/evm_pub_key.c index 84f8cae33..4cbe59063 100644 --- a/apps/evm_family/evm_pub_key.c +++ b/apps/evm_family/evm_pub_key.c @@ -64,9 +64,12 @@ #include #include "address.h" +#include "composable_app_queue.h" +#include "eth_app.h" #include "evm_api.h" #include "evm_helpers.h" #include "evm_priv.h" +#include "exchange_main.h" #include "reconstruct_wallet_flow.h" #include "status_api.h" #include "ui_core_confirm.h" @@ -224,6 +227,8 @@ static bool get_address(const evm_address_format_t format, * STATIC VARIABLES *****************************************************************************/ +static bool sign_address = false; + /***************************************************************************** * GLOBAL VARIABLES *****************************************************************************/ @@ -275,6 +280,16 @@ static bool validate_request_data(evm_get_public_keys_request_t *request, } } + caq_node_data_t data = {.applet_id = get_eth_app_desc()->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_RECEIVE; + + sign_address = exchange_app_validate_caq(data); + return status; } @@ -483,6 +498,10 @@ void evm_get_pub_keys(evm_query_t *query) { return; } + if (sign_address) { + exchange_sign_address(address, sizeof(address)); + } + if (!core_scroll_page(ui_text_receive_on, address, evm_send_error)) { return; } diff --git a/apps/evm_family/evm_sign_txn.c b/apps/evm_family/evm_sign_txn.c index f49ee37aa..65dad9b83 100644 --- a/apps/evm_family/evm_sign_txn.c +++ b/apps/evm_family/evm_sign_txn.c @@ -61,10 +61,13 @@ *****************************************************************************/ #include "address.h" +#include "composable_app_queue.h" +#include "eth_app.h" #include "evm_api.h" #include "evm_helpers.h" #include "evm_priv.h" #include "evm_user_verification.h" +#include "exchange_main.h" #include "reconstruct_wallet_flow.h" #include "status_api.h" #include "ui_core_confirm.h" @@ -243,6 +246,17 @@ static bool validate_request_data(const evm_sign_txn_request_t *request) { ERROR_DATA_FLOW_INVALID_DATA); status = false; } + + caq_node_data_t data = {.applet_id = get_eth_app_desc()->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; + + txn_context->use_signature_verification = exchange_app_validate_caq(data); + return status; } @@ -444,4 +458,4 @@ void evm_sign_transaction(evm_query_t *query) { free(txn_context); txn_context = NULL; } -} \ No newline at end of file +} diff --git a/apps/evm_family/evm_txn_helpers.h b/apps/evm_family/evm_txn_helpers.h index 4ca5f457c..11f17c827 100644 --- a/apps/evm_family/evm_txn_helpers.h +++ b/apps/evm_family/evm_txn_helpers.h @@ -108,6 +108,9 @@ typedef struct { /// pointer to maintain a list of display nodes ui_display_node *display_node; + + /// for exchange app + bool use_signature_verification; } evm_txn_context_t; /***************************************************************************** diff --git a/apps/evm_family/evm_user_verification.c b/apps/evm_family/evm_user_verification.c index fedbc1cfc..7a0e4b1cf 100644 --- a/apps/evm_family/evm_user_verification.c +++ b/apps/evm_family/evm_user_verification.c @@ -66,6 +66,7 @@ #include "constant_texts.h" #include "evm_api.h" #include "evm_priv.h" +#include "exchange_main.h" #include "ui_core_confirm.h" #include "ui_screens.h" @@ -124,6 +125,13 @@ bool evm_verify_transfer(const evm_txn_context_t *txn_context) { to_address, &address[2], false, g_evm_app->chain_id); snprintf( display, sizeof(display), UI_TEXT_SEND_PROMPT, unit, g_evm_app->name); + + if (txn_context->use_signature_verification) { + if (!exchange_validate_stored_signature(address, sizeof(address))) { + return status; + } + } + if (!core_scroll_page(NULL, display, evm_send_error) || !core_scroll_page(ui_text_verify_address, address, evm_send_error)) { return status; diff --git a/apps/exchange_app/exchange_api.c b/apps/exchange_app/exchange_api.c new file mode 100644 index 000000000..843cd7bdf --- /dev/null +++ b/apps/exchange_app/exchange_api.c @@ -0,0 +1,199 @@ +/** + * @file exchange_api.c + * @author Cypherock X1 Team + * @brief Defines helpers apis for Exchange app. + * @copyright Copyright (c) 2023 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + * + ****************************************************************************** + * @attention + * + * (c) Copyright 2023 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 "exchange_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_exchange_query(const uint8_t *data, + uint16_t data_size, + exchange_query_t *query_out) { + if (NULL == data || NULL == query_out || 0 == data_size) { + exchange_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(exchange_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, EXCHANGE_QUERY_FIELDS, query_out); + + /* Send error to host if status is false*/ + if (false == status) { + exchange_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_DECODING_FAILED); + } + + return status; +} + +bool encode_exchange_result(const exchange_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, EXCHANGE_RESULT_FIELDS, result); + + if (true == status) { + *bytes_written_out = stream.bytes_written; + } + + return status; +} + +bool check_exchange_query(const exchange_query_t *query, + pb_size_t exp_query_tag) { + if ((NULL == query) || (exp_query_tag != query->which_request)) { + exchange_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_QUERY); + return false; + } + return true; +} + +exchange_result_t init_exchange_result(pb_size_t result_tag) { + exchange_result_t result = EXCHANGE_RESULT_INIT_ZERO; + result.which_response = result_tag; + return result; +} + +void exchange_send_error(pb_size_t which_error, uint32_t error_code) { + exchange_result_t result = + init_exchange_result(EXCHANGE_RESULT_COMMON_ERROR_TAG); + result.common_error = init_common_error(which_error, error_code); + exchange_send_result(&result); +} + +void exchange_send_result(const exchange_result_t *result) { + // TODO: Set the options file for all + uint8_t buffer[EXCHANGE_RESULT_SIZE] = {0}; + size_t bytes_encoded = 0; + ASSERT( + encode_exchange_result(result, buffer, sizeof(buffer), &bytes_encoded)); + send_response_to_host(&buffer[0], bytes_encoded); +} + +bool exchange_get_query(exchange_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_exchange_query( + event.usb_event.p_msg, event.usb_event.msg_size, query)) { + return false; + } + + if (!check_exchange_query(query, exp_query_tag)) { + return false; + } + + return true; +} diff --git a/apps/exchange_app/exchange_api.h b/apps/exchange_app/exchange_api.h new file mode 100644 index 000000000..c5bbaf3e4 --- /dev/null +++ b/apps/exchange_app/exchange_api.h @@ -0,0 +1,117 @@ +/** + * @file exchange_api.h + * @author Cypherock X1 Team + * @brief Header file to export some helper functions for the EXCHANGE app + * @copyright Copyright (c) 2023 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + */ +#ifndef EXCHANGE_API_H +#define EXCHANGE_API_H + +/***************************************************************************** + * INCLUDES + *****************************************************************************/ + +#include +#include + +/***************************************************************************** + * MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * EXPORTED VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTION PROTOTYPES + *****************************************************************************/ + +/** + * @brief API to decode query from host with `EXCHANGE_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 exchange_query_t obj to copy the decoded + * result to + * @return bool True if decoding was successful, else false + */ +bool decode_exchange_query(const uint8_t *data, + uint16_t data_size, + exchange_query_t *query_out); + +/** + * @brief Encodes the EXCHANGE result with `EXCHANGE_RESULT_FIELDS` to + * byte-stream + * + * @param[in] result: object of populated @ref exchange_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_exchange_result(const exchange_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 + * `exchange_query_t` matches against the expected tag. + * + * @param query The query of type `exchange_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_exchange_query(const exchange_query_t *query, + pb_size_t exp_query_tag); + +/** + * @brief Returns zero initialized object of type + * exchange_result_t result_tag set in result.which_response field + * + * @param result_tag Result tag to be set in the exchange_result_t result + * @return exchange_result_t Result object of type exchange_result_t + */ +exchange_result_t init_exchange_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 exchange_send_error(pb_size_t which_error, uint32_t error_code); + +/** + * @brief This API encodes exchange_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_exchange_result internally. + * + * @param result The result which needs to be sent to the host. + */ +void exchange_send_result(const exchange_result_t *result); + +/** + * @brief This API receives request of type exchange_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 exchange_get_query(exchange_query_t *query, pb_size_t exp_query_tag); + +#endif diff --git a/apps/exchange_app/exchange_close_flow.c b/apps/exchange_app/exchange_close_flow.c new file mode 100644 index 000000000..adb90250c --- /dev/null +++ b/apps/exchange_app/exchange_close_flow.c @@ -0,0 +1,147 @@ +/** + * @file close_flow.c + * @author Cypherock X1 Team + * @brief Closes the exhchange flow + * @copyright Copyright (c) 2023 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + * + ****************************************************************************** + * @attention + * + * (c) Copyright 2023 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 "composable_app_queue.h" +#include "core_shared_context.h" +#include "exchange/close_flow.pb.h" +#include "exchange_api.h" +#include "ui_delay.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 exchange app and return false. + * + * @param query Reference to an instance of exchange_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 exchange_query_t *query, + pb_size_t which_request); + +/***************************************************************************** + * STATIC VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTIONS + *****************************************************************************/ + +static bool check_which_request(const exchange_query_t *query, + pb_size_t which_request) { + if (which_request != query->close_flow.which_request) { + exchange_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_REQUEST); + return false; + } + + return true; +} + +/***************************************************************************** + * GLOBAL FUNCTIONS + *****************************************************************************/ + +void exchange_close_flow(exchange_query_t *query) { + exchange_result_t result = + init_exchange_result(EXCHANGE_RESULT_CLOSE_FLOW_TAG); + if (!check_which_request(query, EXCHANGE_CLOSE_FLOW_REQUEST_CLOSE_TAG)) { + return; + } + + // Clear existing composable app queue + caq_clear(); + + // Clear shared context + core_clear_shared_context(); + + delay_scr_init("Swap flow closed", DELAY_TIME); + + result.close_flow.which_response = EXCHANGE_CLOSE_FLOW_RESPONSE_RESULT_TAG; + exchange_send_result(&result); +} diff --git a/apps/exchange_app/exchange_context.h b/apps/exchange_app/exchange_context.h new file mode 100644 index 000000000..f0a8f1915 --- /dev/null +++ b/apps/exchange_app/exchange_context.h @@ -0,0 +1,33 @@ +/** + * @file exchange_context.h + * @author Cypherock X1 Team + * @brief Header file defining typedefs and MACROS for the EXCHANGE app + * @copyright Copyright (c) 2023 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + */ +#ifndef EXCHANGE_CONTEXT_H +#define EXCHANGE_CONTEXT_H + +/***************************************************************************** + * INCLUDES + *****************************************************************************/ +#include + +/***************************************************************************** + * MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * EXPORTED VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTION PROTOTYPES + *****************************************************************************/ + +#endif /* EXCHANGE_CONTEXT_H */ diff --git a/apps/exchange_app/exchange_get_signature.c b/apps/exchange_app/exchange_get_signature.c new file mode 100644 index 000000000..06b77d279 --- /dev/null +++ b/apps/exchange_app/exchange_get_signature.c @@ -0,0 +1,153 @@ +/** + * @file get_signature.c + * @author Cypherock X1 Team + * @brief Dumps stored signature from shared_context to the host + * @copyright Copyright (c) 2023 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + * + ****************************************************************************** + * @attention + * + * (c) Copyright 2023 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 "core_session.h" +#include "core_shared_context.h" +#include "exchange/get_signature.pb.h" +#include "exchange_api.h" +#include "exchange_main.h" +#include "status_api.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 exchange app and return false. + * + * @param query Reference to an instance of exchange_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 exchange_query_t *query, + pb_size_t which_request); + +/***************************************************************************** + * STATIC VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTIONS + *****************************************************************************/ + +static bool check_which_request(const exchange_query_t *query, + pb_size_t which_request) { + if (which_request != query->get_signature.which_request) { + exchange_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_REQUEST); + return false; + } + + caq_node_data_t data = {.applet_id = get_applet_id()}; + + memzero(data.params, sizeof(data.params)); + data.params[0] = EXCHANGE_FLOW_TAG_FETCH_SIGNATURE; + + exchange_app_validate_caq(data); + + return true; +} + +/***************************************************************************** + * GLOBAL FUNCTIONS + *****************************************************************************/ + +void exchange_get_signature(exchange_query_t *query) { + exchange_result_t result = + init_exchange_result(EXCHANGE_RESULT_GET_SIGNATURE_TAG); + if (!check_which_request(query, + EXCHANGE_GET_SIGNATURE_REQUEST_INITIATE_TAG)) { + return; + } + + result.get_signature.which_response = + EXCHANGE_GET_SIGNATURE_RESPONSE_RESULT_TAG; + + memcpy(result.get_signature.result.signature, + shared_context, + sizeof(result.get_signature.result.signature)); + result.get_signature.result.index = SESSION_KEY_INDEX; + + exchange_send_result(&result); +} diff --git a/apps/exchange_app/exchange_initiate_flow.c b/apps/exchange_app/exchange_initiate_flow.c new file mode 100644 index 000000000..c1d81fe7c --- /dev/null +++ b/apps/exchange_app/exchange_initiate_flow.c @@ -0,0 +1,196 @@ +/** + * @file initiate_flow.c + * @author Cypherock X1 Team + * @brief Initiates the exhchange flow + * @copyright Copyright (c) 2023 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + * + ****************************************************************************** + * @attention + * + * (c) Copyright 2023 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 "composable_app_queue.h" +#include "exchange/initiate_flow.pb.h" +#include "exchange_api.h" +#include "exchange_context.h" +#include "exchange_main.h" +#include "memzero.h" +#include "ui_delay.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 exchange app and return false. + * + * @param query Reference to an instance of exchange_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 exchange_query_t *query, + pb_size_t which_request); + +/***************************************************************************** + * STATIC VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTIONS + *****************************************************************************/ + +static bool check_which_request(const exchange_query_t *query, + pb_size_t which_request) { + if (which_request != query->initiate_flow.which_request) { + exchange_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_REQUEST); + return false; + } + + return true; +} + +/***************************************************************************** + * GLOBAL FUNCTIONS + *****************************************************************************/ + +void exchange_initiate_flow(exchange_query_t *query) { + exchange_result_t result = + init_exchange_result(EXCHANGE_RESULT_INITIATE_FLOW_TAG); + if (!check_which_request(query, + EXCHANGE_INITIATE_FLOW_REQUEST_INITIATE_TAG)) { + return; + } + + // Clear existing composable app queue + caq_clear(); + + delay_scr_init("Swap flow initiated", DELAY_TIME); + + // Receive flow + { + caq_node_data_t data = {.applet_id = + query->initiate_flow.initiate.to.applet_id}; + + memzero(data.params, sizeof(data.params)); + memcpy(data.params, + query->initiate_flow.initiate.to.wallet_id, + sizeof(query->initiate_flow.initiate.to.wallet_id)); + data.params[32] = EXCHANGE_FLOW_TAG_RECEIVE; + + caq_push(data); + } + + // Fetch signature for receive address + { + caq_node_data_t data = {.applet_id = get_exchange_app_desc()->id}; + + memzero(data.params, sizeof(data.params)); + data.params[0] = EXCHANGE_FLOW_TAG_FETCH_SIGNATURE; + + caq_push(data); + } + + // Store signature for receiver address in send flow + { + caq_node_data_t data = {.applet_id = get_exchange_app_desc()->id}; + + memzero(data.params, sizeof(data.params)); + data.params[0] = EXCHANGE_FLOW_TAG_STORE_SIGNATURE; + + caq_push(data); + } + + // Send flow + { + caq_node_data_t data = {.applet_id = + query->initiate_flow.initiate.from.applet_id}; + + memzero(data.params, sizeof(data.params)); + memcpy(data.params, + query->initiate_flow.initiate.from.wallet_id, + sizeof(query->initiate_flow.initiate.from.wallet_id)); + data.params[32] = EXCHANGE_FLOW_TAG_SEND; + + caq_push(data); + } + + result.initiate_flow.which_response = + EXCHANGE_INITIATE_FLOW_RESPONSE_RESULT_TAG; + exchange_send_result(&result); +} diff --git a/apps/exchange_app/exchange_main.c b/apps/exchange_app/exchange_main.c new file mode 100644 index 000000000..59c29e5c6 --- /dev/null +++ b/apps/exchange_app/exchange_main.c @@ -0,0 +1,281 @@ +/** + * @file exchange_main.c + * @author Cypherock X1 Team + * @brief A common entry point to various Exchange actions supported. + * @copyright Copyright (c) 2023 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + * + ****************************************************************************** + * @attention + * + * (c) Copyright 2023 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 "exchange_main.h" + +#include +#include +#include + +#include "atecc_utils.h" +#include "base58.h" +#include "bip32.h" +#include "board.h" +#include "composable_app_queue.h" +#include "core_session.h" +#include "core_shared_context.h" +#include "curves.h" +#include "exchange/core.pb.h" +#include "exchange_api.h" +#include "exchange_priv.h" +#include "nist256p1.h" +#include "sha2.h" +#include "status_api.h" +#include "ui_core_confirm.h" +#include "ui_delay.h" +#include "utils.h" + +/***************************************************************************** + * EXTERN VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTION PROTOTYPES + *****************************************************************************/ +/** + * @brief Entry point for the EXCHANGE application of the X1 vault. It is + * invoked by the X1 vault firmware, as soon as there is a USB request raised + * for the Exchange app. + * + * @param usb_evt The USB event which triggered invocation of the exchange + * app + */ +void exchange_main(usb_event_t usb_evt, const void *app_config); + +/***************************************************************************** + * STATIC VARIABLES + *****************************************************************************/ +static const cy_app_desc_t exchange_app_desc = {.id = 24, + .version = + { + .major = 1, + .minor = 0, + .patch = 0, + }, + .app = exchange_main, + .app_config = NULL}; + +/***************************************************************************** + * STATIC FUNCTIONS + *****************************************************************************/ +static void derive_server_public_key(uint8_t *server_verification_pub_key) { + HDNode node; + char xpub[XPUB_SIZE] = {'\0'}; + + base58_encode_check(get_card_root_xpub(), + FS_KEYSTORE_XPUB_LEN, + nist256p1_info.hasher_base58, + xpub, + XPUB_SIZE); + + hdnode_deserialize_public( + (char *)xpub, 0x0488b21e, NIST256P1_NAME, &node, NULL); + hdnode_public_ckd(&node, SESSION_KEY_INDEX); + + memcpy(server_verification_pub_key, node.public_key, SESSION_PUB_KEY_SIZE); +} + +void exchange_main(usb_event_t usb_evt, const void *app_config) { + exchange_query_t query = EXCHANGE_QUERY_INIT_DEFAULT; + + if (false == decode_exchange_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); + + LOG_SWV("%s (%d) - Query:%d\n", __func__, __LINE__, query.which_request); + switch ((uint8_t)query.which_request) { + case EXCHANGE_QUERY_INITIATE_FLOW_TAG: { + exchange_initiate_flow(&query); + break; + } + case EXCHANGE_QUERY_GET_SIGNATURE_TAG: { + exchange_get_signature(&query); + break; + } + case EXCHANGE_QUERY_STORE_SIGNATURE_TAG: { + exchange_store_signature(&query); + break; + } + case EXCHANGE_QUERY_CLOSE_FLOW_TAG: { + exchange_close_flow(&query); + break; + } + default: { + /* In case we ever encounter invalid query, convey to the host app */ + exchange_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_QUERY); + } break; + } +} + +/***************************************************************************** + * GLOBAL FUNCTIONS + *****************************************************************************/ +const cy_app_desc_t *get_exchange_app_desc() { + return &exchange_app_desc; +} + +bool exchange_app_validate_caq(caq_node_data_t data) { + bool status = false; + caq_node_data_t caq_data = caq_peek(&status); + if (!status) { + return false; + } + + if (caq_data.applet_id == data.applet_id && + memcmp(caq_data.params, data.params, sizeof(data.params)) == 0) { + { + char hex_arr[500] = {0}; + char title[100] = {0}; + snprintf(title, 100, "%ld", data.applet_id); + byte_array_to_hex_string(data.params, 40, hex_arr, 100); + LOG_INFO("Match %s [%s]", title, hex_arr); + } + + caq_pop(); + return true; + } + + { + char hex_arr[500] = {0}; + char title[100] = {0}; + snprintf(title, 100, "%ld", data.applet_id); + byte_array_to_hex_string(data.params, 40, hex_arr, 100); + LOG_ERROR("CAQ Invalid data received: %s [%s]", title, hex_arr); + } + + { + char hex_arr[500] = {0}; + char title[100] = {0}; + snprintf(title, 100, "%ld", caq_data.applet_id); + byte_array_to_hex_string(caq_data.params, 40, hex_arr, 100); + LOG_ERROR("CAQ data exptected: %s [%s]", title, hex_arr); + } + + delay_scr_init("Invalid operation during Swap\n rebooting...", DELAY_TIME); + BSP_reset(); + + return false; +} + +bool exchange_validate_stored_signature(char *receiver, + size_t receiver_max_size) { + uint8_t sig[64] = {0}; + memcpy(sig, shared_context, sizeof(sig)); + + core_clear_shared_context(); + + uint8_t server_verification_pub_key[SESSION_PUB_KEY_SIZE]; + derive_server_public_key(server_verification_pub_key); + + size_t len = strnlen(receiver, receiver_max_size); + char recv_addr[len]; + strncpy(recv_addr, receiver, len); + + uint8_t hash[SHA256_DIGEST_LENGTH] = {0}; + + sha256_Raw((uint8_t *)recv_addr, len, hash); + + if (ecdsa_verify_digest(&nist256p1, server_verification_pub_key, sig, hash) != + 0) { + delay_scr_init("Failed to validate signature\n Do not proceed with Swap", + DELAY_TIME); + + return false; + } + + return true; +} + +void exchange_sign_address(char *address, size_t address_max_size) { + uint8_t hash[SHA256_DIGEST_LENGTH] = {0}; + size_t len = strnlen(address, address_max_size); + sha256_Raw((uint8_t *)address, len, hash); + + auth_data_t data = atecc_sign(hash); + core_clear_shared_context(); + size_t offset = 0; + memcpy(shared_context, data.signature, sizeof(data.signature)); + offset += sizeof(data.signature); + + memcpy(shared_context + offset, data.postfix1, sizeof(data.postfix1)); + offset += sizeof(data.postfix1); + + memcpy(shared_context + offset, data.postfix2, sizeof(data.postfix2)); + offset += sizeof(data.postfix2); +} diff --git a/apps/exchange_app/exchange_main.h b/apps/exchange_app/exchange_main.h new file mode 100644 index 000000000..5d3b278e8 --- /dev/null +++ b/apps/exchange_app/exchange_main.h @@ -0,0 +1,92 @@ +/** + * @file exchange_main.h + * @author Cypherock X1 Team + * @brief Header file for a common entry point to various Exchange actions + supported. + * @details + + * @copyright Copyright (c) 2023 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + * + */ + +#ifndef EXCHANGE_MAIN_H +#define EXCHANGE_MAIN_H + +/***************************************************************************** + * INCLUDES + *****************************************************************************/ + +#include "app_registry.h" +#include "composable_app_queue.h" +/***************************************************************************** + * MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * TYPEDEFS + *****************************************************************************/ + +typedef enum EXCHANGE_FLOW_TAGS { + EXCHANGE_FLOW_TAG_RECEIVE = 0x1, + EXCHANGE_FLOW_TAG_FETCH_SIGNATURE, + EXCHANGE_FLOW_TAG_STORE_SIGNATURE, + EXCHANGE_FLOW_TAG_SEND, +} exchange_flow_tag_e; + +/***************************************************************************** + * EXPORTED VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTION PROTOTYPES + *****************************************************************************/ +/** + * @brief Returns the config for Exchange chain app descriptors + * + * @return A const reference to cy_app_desc_t + */ +const cy_app_desc_t *get_exchange_app_desc(); + +/** + * @brief Validates the data against the data at the front of the composable app + * queue. + * + * It compares the provided data with the data at the front of the queue. If + * they match, it removes the data from the queue. If they do not match, it + * triggers a reset. Does nothing if no data is present. + * + * @param data The data to validate against the queue. + * @return true If the validation is successful and data is popped. + * @return false If the validation fails. + */ +bool exchange_app_validate_caq(caq_node_data_t data); + +/** + * @brief Validates a stored signature against a server public key. + * + * Derives the server's public key and verifies the signature against the + * provided receiver address. + * + * @param receiver Pointer to the receiver address string. + * @param receiver_max_size Maximum size of the receiver buffer. + * @return true If the signature is valid. + * @return false If the signature is invalid. + */ +bool exchange_validate_stored_signature(char *receiver, + size_t receiver_max_size); + +/** + * @brief Signs an address using the ATECC608A and stores the signature in the + * shared context. + * + * This function calculates the SHA256 hash of the address, signs it using the + * ATECC608A, and stores the resulting signature and postfix data in the shared + * context. + * + * @param address Pointer to the address string to be signed. + * @param address_max_size Maximum size of the address buffer. + */ +void exchange_sign_address(char *address, size_t address_max_size); +#endif /* EXCHANGE_MAIN_H */ diff --git a/apps/exchange_app/exchange_priv.h b/apps/exchange_app/exchange_priv.h new file mode 100644 index 000000000..eebe83551 --- /dev/null +++ b/apps/exchange_app/exchange_priv.h @@ -0,0 +1,85 @@ +/** + * @file exchange_priv.h + * @author Cypherock X1 Team + * @brief Support for exchange app internal operations + * This file is defined to separate EXCHANGE's internal use + * functions, flows, common APIs + * @copyright Copyright (c) 2023 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + */ +#ifndef EXCHANGE_PRIV_H +#define EXCHANGE_PRIV_H +/***************************************************************************** + * INCLUDES + *****************************************************************************/ + +#include "exchange/core.pb.h" + +/***************************************************************************** + * PRIVATE MACROS AND DEFINES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE TYPEDEFS + *****************************************************************************/ +#pragma pack(push, 1) +#pragma pack(pop) + +/***************************************************************************** + * TYPEDEFS + *****************************************************************************/ + +/***************************************************************************** + * EXPORTED VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL FUNCTION PROTOTYPES + *****************************************************************************/ + +/** + * @brief Initiates the exchange flow by pushing data onto the composable app + * queue. + * + * This function clears the existing queue, then pushes data for the receive + * flow, fetching the signature, storing the signature, and finally the send + * flow. + * + * @param query Pointer to the exchange query structure. + */ +void exchange_initiate_flow(exchange_query_t *query); + +/** + * @brief Retrieves the signature from the shared context and sends it as a + * result. + * + * This function retrieves the signature stored in the shared context, copies it + * into the result structure, and sends the result. + * + * @param query Pointer to the exchange query structure. + */ +void exchange_get_signature(exchange_query_t *query); + +/** + * @brief Stores the provided signature in the shared context. + * + * This function clears the existing shared context and copies the provided + * signature into it. + * + * @param query Pointer to the exchange query structure containing the signature + * to store. + */ +void exchange_store_signature(exchange_query_t *query); + +/** + * @brief Closes the exchange flow by clearing the composable app + * queue and shared context. + * + * This function clears the existing queue, and the shared context. + * + * @param query Pointer to the exchange query structure. + */ +void exchange_close_flow(exchange_query_t *query); + +#endif diff --git a/apps/exchange_app/exchange_store_signature.c b/apps/exchange_app/exchange_store_signature.c new file mode 100644 index 000000000..517f40598 --- /dev/null +++ b/apps/exchange_app/exchange_store_signature.c @@ -0,0 +1,156 @@ +/** + * @file store_signature.c + * @author Cypherock X1 Team + * @brief Stores the signature in the shared_context + * @copyright Copyright (c) 2023 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + * + ****************************************************************************** + * @attention + * + * (c) Copyright 2023 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 "composable_app_queue.h" +#include "core_shared_context.h" +#include "exchange/store_signature.pb.h" +#include "exchange_api.h" +#include "exchange_main.h" +#include "status_api.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 exchange app and return false. + * + * @param query Reference to an instance of exchange_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 exchange_query_t *query, + pb_size_t which_request); + +/***************************************************************************** + * STATIC VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * GLOBAL VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * STATIC FUNCTIONS + *****************************************************************************/ + +static bool check_which_request(const exchange_query_t *query, + pb_size_t which_request) { + if (which_request != query->store_signature.which_request) { + exchange_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, + ERROR_DATA_FLOW_INVALID_REQUEST); + return false; + } + + caq_node_data_t data = {.applet_id = get_applet_id()}; + + memzero(data.params, sizeof(data.params)); + data.params[0] = EXCHANGE_FLOW_TAG_STORE_SIGNATURE; + + exchange_app_validate_caq(data); + + return true; +} + +/***************************************************************************** + * GLOBAL FUNCTIONS + *****************************************************************************/ + +void exchange_store_signature(exchange_query_t *query) { + exchange_result_t result = + init_exchange_result(EXCHANGE_RESULT_STORE_SIGNATURE_TAG); + if (!check_which_request(query, + EXCHANGE_STORE_SIGNATURE_REQUEST_INITIATE_TAG)) { + return; + } + + core_clear_shared_context(); + + memcpy(shared_context, + query->store_signature.initiate.signature, + sizeof(query->store_signature.initiate.signature)); + + result.store_signature.which_response = + EXCHANGE_STORE_SIGNATURE_RESPONSE_RESULT_TAG; + + exchange_send_result(&result); +} diff --git a/apps/icp_app/icp_pub_key.c b/apps/icp_app/icp_pub_key.c index 8e0434ee2..d8c91643a 100644 --- a/apps/icp_app/icp_pub_key.c +++ b/apps/icp_app/icp_pub_key.c @@ -65,9 +65,11 @@ #include #include "bip32.h" +#include "composable_app_queue.h" #include "constant_texts.h" #include "curves.h" #include "ecdsa.h" +#include "exchange_main.h" #include "icp/get_public_key.pb.h" #include "icp_api.h" #include "icp_context.h" @@ -217,6 +219,7 @@ static bool get_user_consent(const pb_size_t which_request, /***************************************************************************** * STATIC VARIABLES *****************************************************************************/ +static bool sign_address = false; static bool check_which_request(const icp_query_t *query, pb_size_t which_request) { @@ -262,6 +265,14 @@ static bool validate_request(const icp_get_public_keys_intiate_request_t *req, } } + 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; } @@ -540,6 +551,10 @@ void icp_get_pub_keys(icp_query_t *query) { char account_id[200] = {0}; get_account_id_to_display(principal, account_id, sizeof(account_id)); + // NOTE: not sure if this is enough or we need to sign principal id instead + if (sign_address) { + exchange_sign_address(account_id, sizeof(account_id)); + } if (!core_scroll_page(ui_text_account_id, account_id, icp_send_error)) { return; } diff --git a/apps/icp_app/icp_txn.c b/apps/icp_app/icp_txn.c index 9a5fdfa4d..2ed54807e 100644 --- a/apps/icp_app/icp_txn.c +++ b/apps/icp_app/icp_txn.c @@ -67,7 +67,9 @@ #include #include "base58.h" +#include "composable_app_queue.h" #include "constant_texts.h" +#include "exchange_main.h" #include "icp/sign_txn.pb.h" #include "icp_api.h" #include "icp_context.h" @@ -233,6 +235,7 @@ static bool send_signature(icp_query_t *query, const sig_t *signature); * STATIC VARIABLES *****************************************************************************/ static icp_txn_context_t *icp_txn_context = NULL; +static bool use_signature_verification = false; /***************************************************************************** * GLOBAL VARIABLES @@ -268,6 +271,17 @@ static bool validate_request_data(const icp_sign_txn_request_t *request) { 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; } @@ -429,6 +443,13 @@ static bool get_user_verification_for_token_txn(void) { get_principal_id_to_display( decoded_utxn->to.owner, ICP_PRINCIPAL_LENGTH, principal_id); + if (use_signature_verification) { + if (!exchange_validate_stored_signature(principal_id, + sizeof(principal_id))) { + return false; + } + } + // Now take user verification if (!core_scroll_page( ui_text_verify_principal_id, principal_id, icp_send_error)) { @@ -526,6 +547,13 @@ static bool get_user_verification_for_coin_txn(void) { to_account_id, ICP_ACCOUNT_ID_LENGTH * 2 + 1); + if (use_signature_verification) { + if (!exchange_validate_stored_signature(to_account_id, + sizeof(to_account_id))) { + return false; + } + } + if (!core_scroll_page( ui_text_verify_account_id, to_account_id, icp_send_error)) { return false; diff --git a/apps/near_app/near_pub_key.c b/apps/near_app/near_pub_key.c index e6636ddde..01e9449a8 100644 --- a/apps/near_app/near_pub_key.c +++ b/apps/near_app/near_pub_key.c @@ -60,6 +60,8 @@ * INCLUDES *****************************************************************************/ +#include "composable_app_queue.h" +#include "exchange_main.h" #include "near_api.h" #include "near_context.h" #include "near_helpers.h" @@ -207,6 +209,7 @@ static bool get_user_consent(const pb_size_t which_request, /***************************************************************************** * STATIC VARIABLES *****************************************************************************/ +static bool sign_address = false; static bool check_which_request(const near_query_t *query, pb_size_t which_request) { @@ -252,6 +255,14 @@ static bool validate_request(const near_get_public_keys_intiate_request_t *req, } } + 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; } @@ -422,6 +433,11 @@ void near_get_pub_keys(near_query_t *query) { char address[100] = ""; byte_array_to_hex_string( pubkey_list[0], sizeof(pubkey_list[0]), address, sizeof(address)); + + if (sign_address) { + exchange_sign_address(address, sizeof(address)); + } + if (!core_scroll_page(ui_text_receive_on, address, near_send_error)) { return; } diff --git a/apps/near_app/near_txn.c b/apps/near_app/near_txn.c index 43f226888..d11f3b825 100644 --- a/apps/near_app/near_txn.c +++ b/apps/near_app/near_txn.c @@ -61,6 +61,8 @@ * INCLUDES *****************************************************************************/ +#include "composable_app_queue.h" +#include "exchange_main.h" #include "near_api.h" #include "near_context.h" #include "near_helpers.h" @@ -231,6 +233,17 @@ static bool validate_request_data(const near_sign_txn_request_t *request) { 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; + + exchange_app_validate_caq(data); + return status; } @@ -397,4 +410,4 @@ void near_sign_transaction(near_query_t *query) { } return; -} \ No newline at end of file +} diff --git a/apps/solana_app/solana_pub_key.c b/apps/solana_app/solana_pub_key.c index b4bbfdec6..0c0eb28ee 100644 --- a/apps/solana_app/solana_pub_key.c +++ b/apps/solana_app/solana_pub_key.c @@ -63,6 +63,8 @@ #include #include +#include "composable_app_queue.h" +#include "exchange_main.h" #include "reconstruct_wallet_flow.h" #include "solana_api.h" #include "solana_helpers.h" @@ -204,6 +206,7 @@ static bool get_user_consent(const pb_size_t which_request, /***************************************************************************** * STATIC VARIABLES *****************************************************************************/ +static bool sign_address = false; /***************************************************************************** * GLOBAL VARIABLES @@ -256,6 +259,16 @@ STATIC bool validate_request_data(solana_get_public_keys_request_t *request, } } + 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_RECEIVE; + + sign_address = exchange_app_validate_caq(data); + return status; } @@ -435,6 +448,10 @@ void solana_get_pub_keys(solana_query_t *query) { return; }; + if (sign_address) { + exchange_sign_address(address, sizeof(address)); + } + if (!core_scroll_page(ui_text_receive_on, address, solana_send_error)) { return; } @@ -451,4 +468,4 @@ void solana_get_pub_keys(solana_query_t *query) { } delay_scr_init(ui_text_check_cysync_app, DELAY_TIME); -} \ No newline at end of file +} diff --git a/apps/solana_app/solana_sign_txn.c b/apps/solana_app/solana_sign_txn.c index dbda91058..b72e3be09 100644 --- a/apps/solana_app/solana_sign_txn.c +++ b/apps/solana_app/solana_sign_txn.c @@ -62,6 +62,8 @@ #include +#include "composable_app_queue.h" +#include "exchange_main.h" #include "int-util.h" #include "reconstruct_wallet_flow.h" #include "solana_api.h" @@ -216,6 +218,7 @@ static bool send_signature(solana_query_t *query, *****************************************************************************/ STATIC solana_txn_context_t *solana_txn_context = NULL; +static bool use_signature_verification = false; /***************************************************************************** * GLOBAL VARIABLES @@ -246,6 +249,16 @@ static bool validate_request_data(const solana_sign_txn_request_t *request) { 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; } @@ -424,6 +437,12 @@ static bool verify_solana_transfer_sol_transaction() { return false; } + if (use_signature_verification) { + if (!exchange_validate_stored_signature(address, sizeof(address))) { + return false; + } + } + if (!core_scroll_page(ui_text_verify_address, address, solana_send_error)) { return false; } @@ -672,6 +691,12 @@ static bool verify_solana_transfer_token_transaction() { return false; } + if (use_signature_verification) { + if (!exchange_validate_stored_signature(address, sizeof(address))) { + return false; + } + } + // Now take user verification if (!core_scroll_page(ui_text_verify_address, address, solana_send_error)) { return false; @@ -812,4 +837,4 @@ void solana_sign_transaction(solana_query_t *query) { free(solana_txn_context); solana_txn_context = NULL; } -} \ No newline at end of file +} diff --git a/apps/starknet_app/starknet_pub_key.c b/apps/starknet_app/starknet_pub_key.c index 3fce53277..c9a0c47c0 100644 --- a/apps/starknet_app/starknet_pub_key.c +++ b/apps/starknet_app/starknet_pub_key.c @@ -64,6 +64,8 @@ #include #include "assert_conf.h" +#include "composable_app_queue.h" +#include "exchange_main.h" #include "mini-gmp-helpers.h" #include "reconstruct_wallet_flow.h" #include "starkcurve.h" @@ -227,6 +229,7 @@ static void starknet_derive_argent_address(const uint8_t *pub_key, char *addr); /***************************************************************************** * static VARIABLES *****************************************************************************/ +static bool sign_address = false; /***************************************************************************** * GLOBAL VARIABLES @@ -279,6 +282,16 @@ static bool validate_request_data(starknet_get_public_keys_request_t *request, } } + 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_RECEIVE; + + sign_address = exchange_app_validate_caq(data); + return status; } @@ -533,6 +546,10 @@ void starknet_get_pub_keys(starknet_query_t *query) { snprintf(address + index, sizeof(address) - index, raw_address); } + if (sign_address) { + exchange_sign_address(address, sizeof(address)); + } + if (!core_scroll_page(ui_text_receive_on, address, starknet_send_error)) { return; } diff --git a/apps/starknet_app/starknet_sign_txn.c b/apps/starknet_app/starknet_sign_txn.c index d1559339a..28b0a16a2 100644 --- a/apps/starknet_app/starknet_sign_txn.c +++ b/apps/starknet_app/starknet_sign_txn.c @@ -63,7 +63,9 @@ *****************************************************************************/ #include "bignum.h" +#include "composable_app_queue.h" #include "ecdsa.h" +#include "exchange_main.h" #include "mini-gmp-helpers.h" #include "mini-gmp.h" #include "mpz_ecdsa.h" @@ -220,6 +222,8 @@ static void stark_amount_get_decimal_str(const uint8_t *byte_array, *****************************************************************************/ static starknet_txn_context_t *starknet_txn_context = NULL; +static bool use_signature_verification = false; + /***************************************************************************** * GLOBAL VARIABLES *****************************************************************************/ @@ -254,6 +258,17 @@ static bool validate_request_data(const starknet_sign_txn_request_t *request) { 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; } @@ -408,6 +423,12 @@ static bool get_invoke_txn_user_verification() { &address[2], sizeof(address)); + if (use_signature_verification) { + if (!exchange_validate_stored_signature(address, sizeof(address))) { + return false; + } + } + if (!core_scroll_page(ui_text_verify_address, address, starknet_send_error)) { return false; } @@ -626,4 +647,4 @@ void starknet_sign_transaction(starknet_query_t *query) { } return; -} \ No newline at end of file +} diff --git a/apps/tron_app/tron_context.h b/apps/tron_app/tron_context.h index 539bd75e2..75347eb4d 100644 --- a/apps/tron_app/tron_context.h +++ b/apps/tron_app/tron_context.h @@ -41,6 +41,7 @@ typedef struct { * @param[in] raw_txn: tron raw transaction * @return bool True if decoding was successful, else false */ -bool extract_contract_info(tron_transaction_raw_t *raw_txn); +bool extract_contract_info(tron_transaction_raw_t *raw_txn, + bool use_signature_verification); #endif /* TRON_CONTEXT_H */ diff --git a/apps/tron_app/tron_parse_contracts.c b/apps/tron_app/tron_parse_contracts.c index bea49e59b..4efe25668 100644 --- a/apps/tron_app/tron_parse_contracts.c +++ b/apps/tron_app/tron_parse_contracts.c @@ -72,6 +72,7 @@ #include "coin_utils.h" #include "curves.h" #include "ecdsa.h" +#include "exchange_main.h" #include "hasher.h" #include "secp256k1.h" #include "sha2.h" @@ -283,7 +284,8 @@ static bool parse_unverified(uint8_t *data, uint8_t *contract_address) { return true; } -static bool transfer_contract_txn(tron_transaction_contract_t *contract) { +static bool transfer_contract_txn(tron_transaction_contract_t *contract, + bool use_signature_verification) { uint8_t to_address[TRON_INITIAL_ADDRESS_LENGTH] = {0}; // uint8_t owner_address[TRON_INITIAL_ADDRESS_LENGTH] = {0}; int64_t amount = 0; @@ -314,6 +316,12 @@ static bool transfer_contract_txn(tron_transaction_contract_t *contract) { return false; } + if (use_signature_verification) { + if (!exchange_validate_stored_signature(address, sizeof(address))) { + return false; + } + } + if (!core_scroll_page(ui_text_verify_address, address, tron_send_error)) { return false; } @@ -430,7 +438,8 @@ static bool transfer_asset_contract_txn(tron_transaction_contract_t *contract) { * GLOBAL FUNCTIONS *****************************************************************************/ -bool extract_contract_info(tron_transaction_raw_t *raw_txn) { +bool extract_contract_info(tron_transaction_raw_t *raw_txn, + bool use_signature_verification) { if (!(raw_txn->contract_count > 0)) { tron_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, ERROR_DATA_FLOW_INVALID_DATA); @@ -442,7 +451,7 @@ bool extract_contract_info(tron_transaction_raw_t *raw_txn) { switch (contract.type) { case TRON_TRANSACTION_CONTRACT_TRANSFER_CONTRACT: { // Transfer TRX type - if (!transfer_contract_txn(&contract)) { + if (!transfer_contract_txn(&contract, use_signature_verification)) { return false; } break; diff --git a/apps/tron_app/tron_pub_key.c b/apps/tron_app/tron_pub_key.c index 45624b045..dee9e30c9 100644 --- a/apps/tron_app/tron_pub_key.c +++ b/apps/tron_app/tron_pub_key.c @@ -66,7 +66,9 @@ #include "base58.h" #include "bip32.h" #include "coin_utils.h" +#include "composable_app_queue.h" #include "curves.h" +#include "exchange_main.h" #include "reconstruct_wallet_flow.h" #include "sha3.h" #include "status_api.h" @@ -211,7 +213,7 @@ static bool get_user_consent(const pb_size_t which_request, /***************************************************************************** * STATIC VARIABLES *****************************************************************************/ - +static bool sign_address = false; /***************************************************************************** * GLOBAL VARIABLES *****************************************************************************/ @@ -264,6 +266,16 @@ static bool validate_request_data(tron_get_public_keys_request_t *request, } } + 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_RECEIVE; + + sign_address = exchange_app_validate_caq(data); + return status; } @@ -458,6 +470,10 @@ void tron_get_pub_keys(tron_query_t *query) { return; } + if (sign_address) { + exchange_sign_address(main_address, sizeof(main_address)); + } + if (!core_scroll_page(ui_text_receive_on, main_address, tron_send_error)) { return; } @@ -474,4 +490,4 @@ void tron_get_pub_keys(tron_query_t *query) { } delay_scr_init(ui_text_check_cysync_app, DELAY_TIME); -} \ No newline at end of file +} diff --git a/apps/tron_app/tron_sign_txn.c b/apps/tron_app/tron_sign_txn.c index 4e49a8b50..12ea5a18f 100644 --- a/apps/tron_app/tron_sign_txn.c +++ b/apps/tron_app/tron_sign_txn.c @@ -69,8 +69,10 @@ #include "base58.h" #include "coin_utils.h" +#include "composable_app_queue.h" #include "curves.h" #include "ecdsa.h" +#include "exchange_main.h" #include "hasher.h" #include "reconstruct_wallet_flow.h" #include "secp256k1.h" @@ -83,7 +85,7 @@ #include "ui_core_confirm.h" #include "ui_screens.h" #include "wallet_list.h" -//#include "tron_contracts.c" +// #include "tron_contracts.c" /***************************************************************************** * EXTERN VARIABLES @@ -221,6 +223,8 @@ static bool send_signature(tron_query_t *query, STATIC tron_txn_context_t *tron_txn_context = NULL; +static bool use_signature_verification = false; + /***************************************************************************** * GLOBAL VARIABLES *****************************************************************************/ @@ -250,6 +254,16 @@ static bool validate_request_data(const tron_sign_txn_request_t *request) { 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; } @@ -348,7 +362,8 @@ STATIC bool tron_fetch_valid_transaction(tron_query_t *query) { STATIC bool tron_get_user_verification() { // extract raw - if (!extract_contract_info(tron_txn_context->raw_txn)) { + if (!extract_contract_info(tron_txn_context->raw_txn, + use_signature_verification)) { tron_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, ERROR_DATA_FLOW_DECODING_FAILED); return false; @@ -437,4 +452,4 @@ void tron_sign_transaction(tron_query_t *query) { free(tron_txn_context); tron_txn_context = NULL; } -} \ No newline at end of file +} diff --git a/apps/xrp_app/xrp_pub_key.c b/apps/xrp_app/xrp_pub_key.c index 4b0c04b6c..d44d54e8d 100644 --- a/apps/xrp_app/xrp_pub_key.c +++ b/apps/xrp_app/xrp_pub_key.c @@ -61,7 +61,9 @@ *****************************************************************************/ #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" @@ -209,6 +211,7 @@ static bool get_user_consent(const pb_size_t which_request, /***************************************************************************** * STATIC VARIABLES *****************************************************************************/ +static bool sign_address = false; static bool check_which_request(const xrp_query_t *query, pb_size_t which_request) { @@ -254,6 +257,14 @@ static bool validate_request(const xrp_get_public_keys_intiate_request_t *req, } } + 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; } @@ -446,6 +457,10 @@ void xrp_get_pub_keys(xrp_query_t *query) { return; } + if (sign_address) { + exchange_sign_address(address, sizeof(address)); + } + if (!core_scroll_page(ui_text_receive_on, address, xrp_send_error)) { return; } diff --git a/apps/xrp_app/xrp_txn.c b/apps/xrp_app/xrp_txn.c index 7c09c845b..26e963539 100644 --- a/apps/xrp_app/xrp_txn.c +++ b/apps/xrp_app/xrp_txn.c @@ -63,6 +63,8 @@ #include +#include "composable_app_queue.h" +#include "exchange_main.h" #include "reconstruct_wallet_flow.h" #include "status_api.h" #include "ui_core_confirm.h" @@ -199,6 +201,7 @@ static bool send_signature(xrp_query_t *query, const der_sig_t *der_signature); * STATIC VARIABLES *****************************************************************************/ static xrp_txn_context_t *xrp_txn_context = NULL; +static bool use_signature_verification = false; /***************************************************************************** * GLOBAL VARIABLES @@ -233,6 +236,16 @@ static bool validate_request_data(const xrp_sign_txn_request_t *request) { 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; } @@ -347,6 +360,12 @@ static bool get_user_verification(void) { return false; } + if (use_signature_verification) { + if (!exchange_validate_stored_signature(to_address, sizeof(to_address))) { + return false; + } + } + if (!core_scroll_page(ui_text_verify_address, to_address, xrp_send_error)) { return false; } @@ -464,4 +483,4 @@ void xrp_sign_transaction(xrp_query_t *query) { } return; -} \ No newline at end of file +} diff --git a/common/core/app_registry.h b/common/core/app_registry.h index fee05409a..f0fa5a938 100644 --- a/common/core/app_registry.h +++ b/common/core/app_registry.h @@ -23,7 +23,7 @@ * MACROS AND DEFINES *****************************************************************************/ -#define REGISTRY_MAX_APPS 23 +#define REGISTRY_MAX_APPS 25 /***************************************************************************** * TYPEDEFS diff --git a/common/core/composable_app_queue.c b/common/core/composable_app_queue.c new file mode 100644 index 000000000..17c96520c --- /dev/null +++ b/common/core/composable_app_queue.c @@ -0,0 +1,91 @@ +/** + * @author Cypherock X1 Team + * + * @copyright Copyright (c) 2022 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + */ + +#include "composable_app_queue.h" + +#include +static composable_app_queue_t *composable_app_queue = NULL; + +static caq_node_t *caq_new_node(caq_node_data_t data) { + caq_node_t *temp = (caq_node_t *)malloc(sizeof(caq_node_t)); + temp->data = data; + temp->next = NULL; + return temp; +} + +static composable_app_queue_t *caq_create_queue() { + composable_app_queue_t *queue = + (composable_app_queue_t *)malloc(sizeof(composable_app_queue_t)); + queue->front = queue->rear = NULL; + queue->count = 0; + return queue; +} + +bool caq_is_empty() { + return (composable_app_queue == NULL || composable_app_queue->count == 0); +} + +void caq_push(caq_node_data_t data) { + caq_node_t *temp = caq_new_node(data); + composable_app_queue->count++; + + if (composable_app_queue->rear == NULL) { + composable_app_queue->front = composable_app_queue->rear = temp; + return; + } + + composable_app_queue->rear->next = temp; + composable_app_queue->rear = temp; +} + +bool caq_pop() { + if (caq_is_empty()) { + return false; + } + + caq_node_t *temp = composable_app_queue->front; + + composable_app_queue->front = composable_app_queue->front->next; + if (composable_app_queue->front == NULL) { + composable_app_queue->rear = NULL; + } + composable_app_queue->count--; + + free(temp); + return true; +} + +caq_node_data_t caq_peek(bool *status) { + *status = false; + caq_node_data_t output = {0}; + + if (caq_is_empty()) { + return output; + } + + caq_node_t *temp = composable_app_queue->front; + + output = temp->data; + *status = true; + + return output; +} + +bool caq_clear() { + while (!caq_is_empty()) { + bool status = caq_pop(); + if (!status) { + return false; + } + } + return true; +} + +void caq_init() { + composable_app_queue = caq_create_queue(); +} diff --git a/common/core/composable_app_queue.h b/common/core/composable_app_queue.h new file mode 100644 index 000000000..0f30e4520 --- /dev/null +++ b/common/core/composable_app_queue.h @@ -0,0 +1,91 @@ +/** + * @author Cypherock X1 Team + * + * @copyright Copyright (c) 2022 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + */ + +#ifndef COMPOSABLE_APP_QUEUE +#define COMPOSABLE_APP_QUEUE + +#include +#pragma once + +#include + +#define APP_QUEUE_PARAMS_SIZE 128 +#define APP_QUEUE_CALLER_NAME_SIZE 32 + +typedef struct caq_node_data { + uint32_t applet_id; + uint8_t params[APP_QUEUE_PARAMS_SIZE]; + // Add a caller id here if multiple apps are using this in future +} caq_node_data_t; + +typedef struct caq_node { + caq_node_data_t data; + struct caq_node *next; +} caq_node_t; + +typedef struct composable_app_queue { + caq_node_t *front; + caq_node_t *rear; + int count; +} composable_app_queue_t; + +/** + * @brief Initializes the composable app queue. + * + * This function creates and initializes the composable app queue. It must be + * called before any other queue operations. + */ +void caq_init(void); + +/** + * @brief Checks if the composable app queue is empty. + * + * @return true If the queue is empty. + * @return false If the queue is not empty. + */ +bool caq_is_empty(void); + +/** + * @brief Pushes data onto the composable app queue. + * + * @param data The data to be pushed onto the queue. + */ +void caq_push(caq_node_data_t data); + +/** + * @brief Pops data from the composable app queue. + * + * @return true If data was successfully popped from the queue. + * @return false If the queue was empty. + */ +bool caq_pop(void); + +/** + * @brief Peeks at the data at the front of the composable app queue. + * + * @param status Pointer to a boolean variable to indicate if the peek was + * successful. + * Will be set to true if successful, false otherwise. + * + * @return caq_node_data_t The data at the front of the queue. If the queue is + * empty, + * it returns a zeroed out data. Check the status + * parameter + * to see if the queue was empty. + */ +caq_node_data_t caq_peek(bool *status); + +/** + * @brief Clears all elements from the composable app queue. + * + * @return true If the queue was successfully cleared. + * @return false If there was an error during clearing. + */ +bool caq_clear(void); + +#endif // COMPOSABLE_APP_QUEUE diff --git a/common/core/core_flow_init.c b/common/core/core_flow_init.c index 4f8f97f98..ff6466188 100644 --- a/common/core/core_flow_init.c +++ b/common/core/core_flow_init.c @@ -68,10 +68,12 @@ #include "bsc_app.h" #include "btc_app.h" #include "btc_main.h" +#include "constellation_main.h" #include "dash_app.h" #include "doge_app.h" #include "eth_app.h" #include "evm_main.h" +#include "exchange_main.h" #include "fantom_app.h" #include "icp_main.h" #include "inheritance_main.h" @@ -186,5 +188,7 @@ void core_init_app_registry() { registry_add_app(get_inheritance_app_desc()); registry_add_app(get_xrp_app_desc()); registry_add_app(get_starknet_app_desc()); + registry_add_app(get_constellation_app_desc()); registry_add_app(get_icp_app_desc()); + registry_add_app(get_exchange_app_desc()); } diff --git a/common/core/core_shared_context.c b/common/core/core_shared_context.c new file mode 100644 index 000000000..7ec499998 --- /dev/null +++ b/common/core/core_shared_context.c @@ -0,0 +1,97 @@ +/** + * @author Cypherock X1 Team + * + * @copyright Copyright (c) 2022 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + * + ****************************************************************************** + * @attention + * + * (c) Copyright 2022 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 "core_shared_context.h" + +#include + +#include "memzero.h" +/***************************************************************************** + * EXTERN VARIABLES + *****************************************************************************/ + +/***************************************************************************** + * PRIVATE TYPEDEFS + *****************************************************************************/ +/***************************************************************************** + * STATIC VARIABLES + *****************************************************************************/ +/***************************************************************************** + * GLOBAL VARIABLES + *****************************************************************************/ + +uint8_t shared_context[SHARED_CONTEXT_SIZE]; + +/***************************************************************************** + * PRIVATE MACROS AND DEFINES + *****************************************************************************/ +/***************************************************************************** + * STATIC FUNCTION PROTOTYPES + *****************************************************************************/ +/***************************************************************************** + * STATIC FUNCTIONS + *****************************************************************************/ +/***************************************************************************** + * GLOBAL FUNCTIONS + *****************************************************************************/ + +void core_clear_shared_context() { + memzero(shared_context, SHARED_CONTEXT_SIZE); +} diff --git a/common/core/core_shared_context.h b/common/core/core_shared_context.h new file mode 100644 index 000000000..01fc99139 --- /dev/null +++ b/common/core/core_shared_context.h @@ -0,0 +1,29 @@ +/** + * @author Cypherock X1 Team + * @brief Header file containing the session functions + * This file declares the functions used to create and manage the + * shared context. + * + * @copyright Copyright (c) 2022 HODL TECH PTE LTD + *
You may obtain a copy of license at https://mitcc.org/ + */ + +#ifndef CORE_SHARED_CONTEXT +#define CORE_SHARED_CONTEXT + +#pragma once + +#include +#include + +#include "core.pb.h" +#include "device_authentication_api.h" + +#define SHARED_CONTEXT_SIZE 256 + +extern uint8_t shared_context[SHARED_CONTEXT_SIZE]; + +void core_clear_shared_context(); + +#endif // CORE_SHARED_CONTEXT diff --git a/common/cypherock-common b/common/cypherock-common index 55f0d2739..67a0d07b7 160000 --- a/common/cypherock-common +++ b/common/cypherock-common @@ -1 +1 @@ -Subproject commit 55f0d2739ba9d98e82a5684c0c8b509e32671046 +Subproject commit 67a0d07b71c8041558ca7c0d19e7dff9d4153677 diff --git a/common/interfaces/card_interface/nfc.c b/common/interfaces/card_interface/nfc.c index 3ff3090b0..a9c4146a9 100644 --- a/common/interfaces/card_interface/nfc.c +++ b/common/interfaces/card_interface/nfc.c @@ -669,6 +669,9 @@ ret_code_t nfc_exchange_apdu(uint8_t *send_apdu, ASSERT(recv_len != NULL); ASSERT(send_len != 0); + uint8_t expected_recv_len = *recv_len; + *recv_len = 0; + ret_code_t err_code = adafruit_diagnose_card_presence(); if (err_code != 0) return NFC_CARD_ABSENT; @@ -693,8 +696,10 @@ ret_code_t nfc_exchange_apdu(uint8_t *send_apdu, total_packets = ceil(send_len / (1.0 * SEND_PACKET_MAX_LEN)); for (int packet = 1; packet <= total_packets;) { - recv_pkt_len = RECV_PACKET_MAX_ENC_LEN; /* On every request set acceptable - packet length */ + recv_pkt_len = RECV_PACKET_MAX_ENC_LEN <= expected_recv_len + ? RECV_PACKET_MAX_ENC_LEN + : expected_recv_len; /* On every request set acceptable + packet length */ /** * Sets appropriate CLA byte for each packet. CLA byte (first byte of @@ -757,11 +762,14 @@ ret_code_t nfc_exchange_apdu(uint8_t *send_apdu, /** Prepare to request next packet from the card */ *recv_len = recv_pkt_len; - recv_pkt_len = RECV_PACKET_MAX_ENC_LEN; + uint8_t remaining_recv_len = expected_recv_len - *recv_len + 2; + recv_pkt_len = RECV_PACKET_MAX_ENC_LEN <= remaining_recv_len + ? RECV_PACKET_MAX_ENC_LEN + : remaining_recv_len; request_chain_pkt[2] = ceil(*recv_len * 1.0 / RECV_PACKET_MAX_LEN); /** Request all the remaining packets of multi-packet response */ - while (recv_apdu[*recv_len - 2] == 0x61) { + while (recv_apdu[*recv_len - 2] == 0x61 && recv_pkt_len > 0) { *recv_len -= 2; err_code = adafruit_pn532_in_data_exchange(request_chain_pkt, sizeof(request_chain_pkt), @@ -787,7 +795,10 @@ ret_code_t nfc_exchange_apdu(uint8_t *send_apdu, /** Prepare to request next packet from the card */ *recv_len += recv_pkt_len; - recv_pkt_len = RECV_PACKET_MAX_ENC_LEN; + remaining_recv_len = expected_recv_len - *recv_len + 2; + recv_pkt_len = RECV_PACKET_MAX_ENC_LEN <= remaining_recv_len + ? RECV_PACKET_MAX_ENC_LEN + : remaining_recv_len; request_chain_pkt[2] = *recv_len / RECV_PACKET_MAX_LEN + 1; } diff --git a/common/interfaces/user_interface/ui_scroll_page.c b/common/interfaces/user_interface/ui_scroll_page.c index baa7d914f..b0d67a157 100644 --- a/common/interfaces/user_interface/ui_scroll_page.c +++ b/common/interfaces/user_interface/ui_scroll_page.c @@ -261,8 +261,10 @@ static void page_update_header(void) { gp_scrollabe_page_lvgl->p_ui_page_lvgl, 128, scroll_page_height); lv_obj_align( gp_scrollabe_page_lvgl->p_ui_page_lvgl, NULL, scroll_page_aligment, 0, 0); - lv_obj_set_hidden(gp_scrollabe_page_lvgl->p_ui_header_lvgl, - is_heading_hidden); + if (gp_scrollabe_page_lvgl->p_ui_header_lvgl != NULL) { + lv_obj_set_hidden(gp_scrollabe_page_lvgl->p_ui_header_lvgl, + is_heading_hidden); + } } static void page_update_footnote(void) { @@ -678,7 +680,8 @@ static void ui_scrollable_page_create(void) { /** * Recalculate the total pages if heading is only visible on the first page */ - if (gp_scrollabe_page_data->bool_only_first_page_header_visible) { + if (NULL != gp_scrollabe_page_data->p_ui_heading && + gp_scrollabe_page_data->bool_only_first_page_header_visible) { gp_scrollabe_page_data->total_page_num = 1; int16_t first_page_height = currPageHeight; diff --git a/common/proto-options/constellation/core.options b/common/proto-options/constellation/core.options new file mode 100644 index 000000000..65a4c4612 --- /dev/null +++ b/common/proto-options/constellation/core.options @@ -0,0 +1 @@ +# Options for file common/cypherock-common/proto/constellation/core.proto diff --git a/common/proto-options/constellation/error.options b/common/proto-options/constellation/error.options new file mode 100644 index 000000000..d8b0f145a --- /dev/null +++ b/common/proto-options/constellation/error.options @@ -0,0 +1 @@ +# Options for file common/cypherock-common/proto/constellation/error.proto diff --git a/common/proto-options/constellation/get_public_key.options b/common/proto-options/constellation/get_public_key.options new file mode 100644 index 000000000..579a84b1b --- /dev/null +++ b/common/proto-options/constellation/get_public_key.options @@ -0,0 +1,5 @@ +# Options for file common/cypherock-common/proto/constellation/get_public_key.proto +constellation.GetPublicKeysDerivationPath.path type:FT_STATIC max_count:5 fixed_length:true +constellation.GetPublicKeysIntiateRequest.wallet_id type:FT_STATIC max_size:32 fixed_length:true +constellation.GetPublicKeysIntiateRequest.derivation_paths type:FT_STATIC max_count:100 fixed_length:true +constellation.GetPublicKeysResultResponse.public_keys type:FT_STATIC max_size:65 max_count:10 fixed_length:true diff --git a/common/proto-options/constellation/sign_msg.options b/common/proto-options/constellation/sign_msg.options new file mode 100644 index 000000000..a9586d289 --- /dev/null +++ b/common/proto-options/constellation/sign_msg.options @@ -0,0 +1,4 @@ +# Options for file common/cypherock-common/proto/constellation/sign_msg.proto +constellation.SignMsgInitiateRequest.wallet_id type:FT_STATIC max_size:32 fixed_length:true +constellation.SignMsgInitiateRequest.derivation_path type:FT_STATIC max_count:5 fixed_length:true +constellation.SignMsgSignatureResponse.signature type:FT_STATIC max_size:72 fixed_length:false diff --git a/common/proto-options/constellation/sign_txn.options b/common/proto-options/constellation/sign_txn.options new file mode 100644 index 000000000..f8df5071c --- /dev/null +++ b/common/proto-options/constellation/sign_txn.options @@ -0,0 +1,10 @@ +# Options for file common/cypherock-common/proto/constellation/sign_txn.proto +constellation.SignTxnInitiateRequest.wallet_id type:FT_STATIC max_size:32 fixed_length:true +constellation.SignTxnInitiateRequest.derivation_path type:FT_STATIC max_count:5 fixed_length:true +constellation.SignTxnSignatureResponse.signature type:FT_STATIC max_size:72 fixed_length:false + +constellation.Transaction.source type:FT_STATIC max_size:41 fixed_length:true +constellation.Transaction.destination type:FT_STATIC max_size:41 fixed_length:true +constellation.Parent.hash type:FT_STATIC max_size:65 fixed_length:true +constellation.Transaction.salt type:FT_STATIC max_size:16 fixed_length:false + diff --git a/common/proto-options/exchange/get_signature.options b/common/proto-options/exchange/get_signature.options new file mode 100644 index 000000000..68c5fa92d --- /dev/null +++ b/common/proto-options/exchange/get_signature.options @@ -0,0 +1 @@ +exchange.GetSignatureResultResponse.signature type:FT_STATIC max_size:94 fixed_length:true diff --git a/common/proto-options/exchange/initiate_flow.options b/common/proto-options/exchange/initiate_flow.options new file mode 100644 index 000000000..047ddfe85 --- /dev/null +++ b/common/proto-options/exchange/initiate_flow.options @@ -0,0 +1 @@ +exchange.CoinDetails.wallet_id type:FT_STATIC max_size:32 fixed_length:true diff --git a/common/proto-options/exchange/store_signature.options b/common/proto-options/exchange/store_signature.options new file mode 100644 index 000000000..2b3f5449d --- /dev/null +++ b/common/proto-options/exchange/store_signature.options @@ -0,0 +1 @@ +exchange.StoreSignatureIntiateRequest.signature type:FT_STATIC max_size:64 fixed_length:true diff --git a/common/proto-options/version.options b/common/proto-options/version.options index ecd5a1261..c90ddf0d8 100644 --- a/common/proto-options/version.options +++ b/common/proto-options/version.options @@ -1,2 +1,2 @@ # Options for file common/cypherock-common/proto/version.proto -core.AppVersionResultResponse.app_versions type:FT_STATIC max_count:20 +core.AppVersionResultResponse.app_versions type:FT_STATIC max_count:25 diff --git a/src/constant_texts.c b/src/constant_texts.c index 92e8b2ac2..b08946c58 100644 --- a/src/constant_texts.c +++ b/src/constant_texts.c @@ -529,6 +529,7 @@ const char *ui_text_unreliable_cards = "Warning, your cards are unreliable! Contact support"; const char *ui_critical_card_health_migrate_data = "Card health is critical! Migrate to new set of cards"; +const char *ui_text_check_software_wallet_app = "Check the Software Wallet App"; // App specific diff --git a/src/constant_texts.h b/src/constant_texts.h index 8687b6b40..263ba034e 100644 --- a/src/constant_texts.h +++ b/src/constant_texts.h @@ -56,8 +56,11 @@ #define UI_TEXT_PIN "PIN\n %s" #define UI_TEXT_VERIFY_DESTINATION_TAG "Verify Destination Tag\n%lu" #define UI_TEXT_VERIFY_PRIORITY_FEE "Verify Priority Fee\n%s\n%s" -#define UI_TEXT_VERIFY_MEMO "Verify Memo\n%s" #define UI_TEXT_VERIFY_FEE "Verify Fee\n%s\n%s" +#define UI_TEXT_VERIFY_DATA "Verify Data" +#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" // product hash extern const char *product_hash; @@ -376,6 +379,7 @@ extern const char *ui_text_card_detected; extern const char *ui_text_nfc_hardware_fault_detected; extern const char *ui_text_unreliable_cards; extern const char *ui_critical_card_health_migrate_data; +extern const char *ui_text_check_software_wallet_app; // App specific diff --git a/src/main.c b/src/main.c index 959bab00f..6d7657007 100644 --- a/src/main.c +++ b/src/main.c @@ -76,6 +76,7 @@ */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ +#include "composable_app_queue.h" #define _DEFAULT_SOURCE /* needed for usleep() */ #include #include @@ -137,6 +138,7 @@ int main(void) { ekp_queue_init(); #endif application_init(); + caq_init(); #ifdef RUN_ENGINE #if USE_SIMULATOR == 0 diff --git a/utilities/cmake/firmware/firmware.cmake b/utilities/cmake/firmware/firmware.cmake index e573bea43..c9a405509 100644 --- a/utilities/cmake/firmware/firmware.cmake +++ b/utilities/cmake/firmware/firmware.cmake @@ -60,9 +60,10 @@ target_include_directories(${EXECUTABLE} PRIVATE apps/tron_app apps/inheritance_app apps/starknet_app - apps/xrp_app + apps/constellation_app apps/icp_app + apps/exchange_app src/ src/menu @@ -189,6 +190,7 @@ target_include_directories(${EXECUTABLE} PRIVATE $<$:${PROJECT_SOURCE_DIR}/tests/apps/solana_app> $<$:${PROJECT_SOURCE_DIR}/tests/apps/inheritance_app> $<$:${PROJECT_SOURCE_DIR}/tests/apps/xrp_app> + $<$:${PROJECT_SOURCE_DIR}/tests/apps/constellation_app> $<$:${PROJECT_SOURCE_DIR}/tests/apps/icp_app> ) diff --git a/utilities/cmake/simulator/simulator.cmake b/utilities/cmake/simulator/simulator.cmake index 6058abc68..ece63f670 100644 --- a/utilities/cmake/simulator/simulator.cmake +++ b/utilities/cmake/simulator/simulator.cmake @@ -58,7 +58,9 @@ target_include_directories(${PROJECT_NAME} PRIVATE apps/inheritance_app apps/xrp_app apps/starknet_app + apps/constellation_app apps/icp_app + apps/exchange_app src/ src/menu @@ -164,6 +166,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE $<$:${PROJECT_SOURCE_DIR}/tests/apps/solana_app> $<$:${PROJECT_SOURCE_DIR}/tests/apps/inheritance_app> $<$:${PROJECT_SOURCE_DIR}/tests/apps/xrp_app> + $<$:${PROJECT_SOURCE_DIR}/tests/apps/constellation_app> $<$:${PROJECT_SOURCE_DIR}/tests/apps/icp_app> ) diff --git a/version.txt b/version.txt index e2e08fe80..86109041a 100755 --- a/version.txt +++ b/version.txt @@ -1,3 +1,3 @@ -firmware version=000:006:008:002 +firmware version=000:006:010:004 hardware version=000:001:000:000 magic number=45227A01