diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index 6be628488..a709e6d13 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -77,7 +77,7 @@ jobs:
bash ./utilities/ci/static-analyzer.sh ${SHA_BASE}
fi
- name: Upload result
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: clang-tidy-result
path: anaylysis.results
diff --git a/apps/icp_app/icp_api.c b/apps/icp_app/icp_api.c
new file mode 100644
index 000000000..33f50768c
--- /dev/null
+++ b/apps/icp_app/icp_api.c
@@ -0,0 +1,196 @@
+/**
+ * @file icp_api.c
+ * @author Cypherock X1 Team
+ * @brief Defines helpers apis for ICP app.
+ * @copyright Copyright (c) 2024 HODL TECH PTE LTD
+ *
You may obtain a copy of license at https://mitcc.org/
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * (c) Copyright 2024 by HODL TECH PTE LTD
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject
+ * to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ *
+ * "Commons Clause" License Condition v1.0
+ *
+ * The Software is provided to you by the Licensor under the License,
+ * as defined below, subject to the following condition.
+ *
+ * Without limiting other conditions in the License, the grant of
+ * rights under the License will not include, and the License does not
+ * grant to you, the right to Sell the Software.
+ *
+ * For purposes of the foregoing, "Sell" means practicing any or all
+ * of the rights granted to you under the License to provide to third
+ * parties, for a fee or other consideration (including without
+ * limitation fees for hosting or consulting/ support services related
+ * to the Software), a product or service whose value derives, entirely
+ * or substantially, from the functionality of the Software. Any license
+ * notice or attribution required by the License must also include
+ * this Commons Clause License Condition notice.
+ *
+ * Software: All X1Wallet associated files.
+ * License: MIT
+ * Licensor: HODL TECH PTE LTD
+ *
+ ******************************************************************************
+ */
+
+/*****************************************************************************
+ * INCLUDES
+ *****************************************************************************/
+
+#include "icp_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_icp_query(const uint8_t *data,
+ uint16_t data_size,
+ icp_query_t *query_out) {
+ if (NULL == data || NULL == query_out || 0 == data_size) {
+ icp_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(icp_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, ICP_QUERY_FIELDS, query_out);
+
+ /* Send error to host if status is false*/
+ if (false == status) {
+ icp_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG,
+ ERROR_DATA_FLOW_DECODING_FAILED);
+ }
+
+ return status;
+}
+
+bool encode_icp_result(const icp_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, ICP_RESULT_FIELDS, result);
+
+ if (true == status) {
+ *bytes_written_out = stream.bytes_written;
+ }
+
+ return status;
+}
+
+bool check_icp_query(const icp_query_t *query, pb_size_t exp_query_tag) {
+ if ((NULL == query) || (exp_query_tag != query->which_request)) {
+ icp_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG,
+ ERROR_DATA_FLOW_INVALID_QUERY);
+ return false;
+ }
+ return true;
+}
+
+icp_result_t init_icp_result(pb_size_t result_tag) {
+ icp_result_t result = ICP_RESULT_INIT_ZERO;
+ result.which_response = result_tag;
+ return result;
+}
+
+void icp_send_error(pb_size_t which_error, uint32_t error_code) {
+ icp_result_t result = init_icp_result(ICP_RESULT_COMMON_ERROR_TAG);
+ result.common_error = init_common_error(which_error, error_code);
+ icp_send_result(&result);
+}
+
+void icp_send_result(const icp_result_t *result) {
+ // TODO: Set all option files
+ uint8_t buffer[1700] = {0};
+ size_t bytes_encoded = 0;
+ ASSERT(encode_icp_result(result, buffer, sizeof(buffer), &bytes_encoded));
+ send_response_to_host(&buffer[0], bytes_encoded);
+}
+
+bool icp_get_query(icp_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_icp_query(
+ event.usb_event.p_msg, event.usb_event.msg_size, query)) {
+ return false;
+ }
+
+ if (!check_icp_query(query, exp_query_tag)) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/apps/icp_app/icp_api.h b/apps/icp_app/icp_api.h
new file mode 100644
index 000000000..7079aa347
--- /dev/null
+++ b/apps/icp_app/icp_api.h
@@ -0,0 +1,113 @@
+/**
+ * @file icp_api.h
+ * @author Cypherock X1 Team
+ * @brief Header file to export some helper functions for the ICP app
+ * @copyright Copyright (c) 2024 HODL TECH PTE LTD
+ *
You may obtain a copy of license at https://mitcc.org/
+ */
+#ifndef ICP_API_H
+#define ICP_API_H
+
+/*****************************************************************************
+ * INCLUDES
+ *****************************************************************************/
+
+#include
+#include
+
+/*****************************************************************************
+ * MACROS AND DEFINES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * TYPEDEFS
+ *****************************************************************************/
+
+/*****************************************************************************
+ * EXPORTED VARIABLES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * GLOBAL FUNCTION PROTOTYPES
+ *****************************************************************************/
+
+/**
+ * @brief API to decode query from host with `ICP_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 icp_query_t obj to copy the decoded result to
+ * @return bool True if decoding was successful, else false
+ */
+bool decode_icp_query(const uint8_t *data,
+ uint16_t data_size,
+ icp_query_t *query_out);
+
+/**
+ * @brief Encodes the ICP result with `ICP_RESULT_FIELDS` to byte-stream
+ *
+ * @param[in] result: object of populated @ref icp_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_icp_result(const icp_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
+ * `icp_query_t` matches against the expected tag.
+ *
+ * @param query The query of type `icp_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_icp_query(const icp_query_t *query, pb_size_t exp_query_tag);
+
+/**
+ * @brief Returns zero initialized object of type
+ * icp_result_t result_tag set in result.which_response field
+ *
+ * @param result_tag Result tag to be set in the icp_result_t result
+ * @return icp_result_t Result object of type icp_result_t
+ */
+icp_result_t init_icp_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 icp_send_error(pb_size_t which_error, uint32_t error_code);
+
+/**
+ * @brief This API encodes icp_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_icp_result internally.
+ *
+ * @param result The result which needs to be sent to the host.
+ */
+void icp_send_result(const icp_result_t *result);
+
+/**
+ * @brief This API receives request of type icp_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 icp_get_query(icp_query_t *query, pb_size_t exp_query_tag);
+
+#endif
diff --git a/apps/icp_app/icp_context.h b/apps/icp_app/icp_context.h
new file mode 100644
index 000000000..43b795e7a
--- /dev/null
+++ b/apps/icp_app/icp_context.h
@@ -0,0 +1,156 @@
+/**
+ * @file icp_context.h
+ * @author Cypherock X1 Team
+ * @brief Header file defining typedefs and MACROS for the ICP app
+ * @copyright Copyright (c) 2024 HODL TECH PTE LTD
+ *
You may obtain a copy of license at https://mitcc.org/
+ */
+#ifndef ICP_CONTEXT_H
+#define ICP_CONTEXT_H
+
+/*****************************************************************************
+ * INCLUDES
+ *****************************************************************************/
+#include
+#include
+#include
+#include
+#include
+
+#include "pb.h"
+/*****************************************************************************
+ * MACROS AND DEFINES
+ *****************************************************************************/
+#define ICP_NAME "ICP"
+#define ICP_LUNIT "ICP"
+#define ICP_PUB_KEY_SIZE 33
+#define ICP_PRINCIPAL_LENGTH 29
+#define ICP_ACCOUNT_ID_LENGTH 32
+
+#define ICP_SELF_AUTH_ID_TAG 0x02
+#define SECP256K1_UNCOMPRESSED_PK_LEN 65
+
+#define ICP_SUBACCOUNT_ID_LEN 32
+
+// macros for read_state_request
+#define MAX_PATHS 1 // Max number of paths (only 1 needed)
+#define MAX_SEGMENTS \
+ 2 // Maximum segments per path (only 2 needed: "request_status" and
+ // requestId)
+#define MAX_SEGMENT_SIZE 32 // Maximum byte length of a segment
+
+#define MAX_INGRESS_EXPIRY_SIZE 10
+
+#define SHA256_DIGEST_LENGTH 32
+
+/*****************************************************************************
+ * TYPEDEFS
+ *****************************************************************************/
+
+// TODO: Populate structure for ICP
+typedef struct {
+} icp_config_t;
+
+/// Reference:
+// https://github.com/dfinity/agent-js/blob/main/packages/candid/src/idl.ts#L27
+// https://github.com/dfinity/agent-js/blob/main/packages/candid/src/idl.ts#L1703
+typedef enum {
+ Null = -1,
+ Bool = -2,
+ Nat = -3,
+ Int = -4,
+
+ Nat8 = -5,
+ Nat16 = -6,
+ Nat32 = -7,
+ Nat64 = -8,
+
+ Int8 = -9,
+ Int16 = -10,
+ Int32 = -11,
+ Int64 = -12,
+
+ Float32 = -13,
+ Float64 = -14,
+ Text = -15,
+ Reserved = -16,
+ Empty = -17,
+ Opt = -18,
+ Vector = -19,
+ Record = -20,
+ Variant = -21,
+ Func = -22,
+ Service = -23,
+ Principal = -24,
+} IDLTypes_e;
+
+// Calculated hashes of transfer method fields for easy comparison and avoiding
+// re-computing again and again
+/// Reference:
+// https://github.com/Zondax/ledger-icp/blob/main/app/src/candid/candid_types.h#L104
+typedef enum {
+ transfer_hash_to = 25979,
+ transfer_hash_fee = 5094982,
+ transfer_hash_memo = 1213809850,
+ transfer_hash_from_subaccount = 1835347746, // optional
+ transfer_hash_created_at_time = 3258775938, // optional
+ transfer_hash_amount = 3573748184,
+} transfer_hash_fields;
+
+/// Reference:
+// https://github.com/dfinity/ic-js/blob/main/packages/ledger-icp/candid/ledger.certified.idl.js#L13
+typedef struct {
+ uint64_t e8s;
+} token_t;
+
+/// Reference:
+// https://github.com/dfinity/ic-js/blob/main/packages/ledger-icp/candid/ledger.certified.idl.js#L198
+typedef struct {
+ uint64_t timestamp_nanos;
+} timestamp_t;
+
+/// Reference:
+// https://github.com/dfinity/ic-js/blob/main/packages/ledger-icp/candid/ledger.certified.idl.js#L291
+typedef struct {
+ uint8_t to[ICP_ACCOUNT_ID_LENGTH];
+ token_t amount;
+ token_t fee;
+ uint64_t memo;
+
+ // optional fields
+ bool has_from_subaccount;
+ uint8_t from_subaccount[ICP_SUBACCOUNT_ID_LEN];
+ bool has_created_at_time;
+ timestamp_t created_at_time;
+} icp_transfer_t;
+
+typedef PB_BYTES_ARRAY_T(MAX_SEGMENT_SIZE) icp_path_segment_t;
+typedef PB_BYTES_ARRAY_T(MAX_INGRESS_EXPIRY_SIZE)
+ icp_read_state_request_ingress_expiry_t;
+
+// Represents a path, which is an array of icp_path_segment_t
+typedef struct {
+ icp_path_segment_t segments[MAX_SEGMENTS]; // Path consists of segments
+ size_t segment_count; // Number of segments in this path
+} icp_read_state_request_path_t;
+
+/// Reference:
+// https://github.com/dfinity/agent-js/blob/main/packages/agent/src/agent/http/types.ts#L93
+typedef struct {
+ char *request_type;
+ icp_read_state_request_path_t paths[MAX_PATHS];
+ size_t path_count; // Number of paths
+ icp_read_state_request_ingress_expiry_t ingress_expiry;
+ uint8_t sender[ICP_PRINCIPAL_LENGTH];
+} icp_read_state_request_t;
+
+/*****************************************************************************
+ * EXPORTED VARIABLES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * GLOBAL FUNCTION PROTOTYPES
+ *****************************************************************************/
+
+#endif /* ICP_CONTEXT_H */
diff --git a/apps/icp_app/icp_helpers.c b/apps/icp_app/icp_helpers.c
new file mode 100644
index 000000000..7c5d35159
--- /dev/null
+++ b/apps/icp_app/icp_helpers.c
@@ -0,0 +1,188 @@
+/**
+ * @file icp_helpers.c
+ * @author Cypherock X1 Team
+ * @brief Utilities specific to Icp chains
+ * @copyright Copyright (c) 2024 HODL TECH PTE LTD
+ *
You may obtain a copy of license at https://mitcc.org/
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * (c) Copyright 2024 by HODL TECH PTE LTD
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject
+ * to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ *
+ * "Commons Clause" License Condition v1.0
+ *
+ * The Software is provided to you by the Licensor under the License,
+ * as defined below, subject to the following condition.
+ *
+ * Without limiting other conditions in the License, the grant of
+ * rights under the License will not include, and the License does not
+ * grant to you, the right to Sell the Software.
+ *
+ * For purposes of the foregoing, "Sell" means practicing any or all
+ * of the rights granted to you under the License to provide to third
+ * parties, for a fee or other consideration (including without
+ * limitation fees for hosting or consulting/ support services related
+ * to the Software), a product or service whose value derives, entirely
+ * or substantially, from the functionality of the Software. Any license
+ * notice or attribution required by the License must also include
+ * this Commons Clause License Condition notice.
+ *
+ * Software: All X1Wallet associated files.
+ * License: MIT
+ * Licensor: HODL TECH PTE LTD
+ *
+ ******************************************************************************
+ */
+
+/*****************************************************************************
+ * INCLUDES
+ *****************************************************************************/
+#include "icp_helpers.h"
+
+#include
+#include
+
+#include "sha2.h"
+
+/*****************************************************************************
+ * EXTERN VARIABLES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * PRIVATE MACROS AND DEFINES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * PRIVATE TYPEDEFS
+ *****************************************************************************/
+
+/*****************************************************************************
+ * STATIC FUNCTION PROTOTYPES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * STATIC VARIABLES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * GLOBAL VARIABLES
+ *****************************************************************************/
+#define CRC32_POLYNOMIAL \
+ 0xEDB88320 // Reversed polynomial used in standard CRC32
+#define CRC32_INIT 0xFFFFFFFF // Initial value
+#define CRC32_FINAL_XOR 0xFFFFFFFF // Final XOR value
+
+// Secp256k1 OID (Object Identifier) Prefix
+uint8_t const SECP256K1_DER_PREFIX[] = {
+ 0x30, 0x56, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02,
+ 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x0a, 0x03, 0x42, 0x00};
+
+// Precomputed CRC-32 table
+uint32_t crc32_table[256];
+
+// SHA-224 Initial Hash Values (IV) from FIPS 180-4
+static const uint32_t sha224_initial_hash[8] = {0xc1059ed8,
+ 0x367cd507,
+ 0x3070dd17,
+ 0xf70e5939,
+ 0xffc00b31,
+ 0x68581511,
+ 0x64f98fa7,
+ 0xbefa4fa4};
+
+/*****************************************************************************
+ * STATIC FUNCTIONS
+ *****************************************************************************/
+
+/*****************************************************************************
+ * GLOBAL FUNCTIONS
+ *****************************************************************************/
+
+bool icp_derivation_path_guard(const uint32_t *path, uint8_t levels) {
+ bool status = false;
+ if (levels != ICP_IMPLICIT_ACCOUNT_DEPTH) {
+ return status;
+ }
+
+ uint32_t purpose = path[0], coin = path[1], account = path[2],
+ change = path[3], address = path[4];
+
+ // m/44'/223'/0'/0/i
+ status = (ICP_PURPOSE_INDEX == purpose && ICP_COIN_INDEX == coin &&
+ ICP_ACCOUNT_INDEX == account && ICP_CHANGE_INDEX == change &&
+ is_non_hardened(address));
+
+ return status;
+}
+
+void get_secp256k1_der_encoded_pub_key(const uint8_t *public_key,
+ uint8_t *result) {
+ memzero(result, SECP256K1_DER_PK_LEN);
+ memcpy(result, SECP256K1_DER_PREFIX, SECP256K1_DER_PREFIX_LEN);
+ memcpy(result + SECP256K1_DER_PREFIX_LEN,
+ public_key,
+ SECP256K1_UNCOMPRESSED_PK_LEN);
+}
+
+// Function to initialize the CRC-32 lookup table
+void crc32_init() {
+ for (uint32_t i = 0; i < 256; i++) {
+ uint32_t crc = i;
+ for (uint32_t j = 0; j < 8; j++) {
+ if (crc & 1)
+ crc = (crc >> 1) ^ CRC32_POLYNOMIAL;
+ else
+ crc >>= 1;
+ }
+ crc32_table[i] = crc;
+ }
+}
+
+uint32_t compute_crc32(const uint8_t *data, size_t length) {
+ // Initialize CRC32 table
+ crc32_init();
+
+ uint32_t crc = CRC32_INIT;
+ for (size_t i = 0; i < length; i++) {
+ uint8_t index = (crc ^ data[i]) & 0xFF;
+ crc = (crc >> 8) ^ crc32_table[index];
+ }
+ return crc ^ CRC32_FINAL_XOR;
+}
+
+// Custom SHA-224 function using SHA-256 core
+void sha224_Raw(const uint8_t *data, size_t len, uint8_t *digest) {
+ SHA256_CTX ctx;
+ sha256_Init(&ctx);
+
+ // Override initial hash values with SHA-224 IV
+ memcpy(ctx.state, sha224_initial_hash, sizeof(sha224_initial_hash));
+
+ sha256_Update(&ctx, data, len);
+ sha256_Final(&ctx, digest);
+
+ // Truncate to 224 bits (first 28 bytes)
+ memzero(&digest[SHA224_DIGEST_LENGTH], 32 - SHA224_DIGEST_LENGTH);
+}
\ No newline at end of file
diff --git a/apps/icp_app/icp_helpers.h b/apps/icp_app/icp_helpers.h
new file mode 100644
index 000000000..2b28e9d90
--- /dev/null
+++ b/apps/icp_app/icp_helpers.h
@@ -0,0 +1,118 @@
+/**
+ * @file icp_helpers.h
+ * @author Cypherock X1 Team
+ * @brief Utilities api definitions for ICP chains
+ * @copyright Copyright (c) 2024 HODL TECH PTE LTD
+ *
You may obtain a copy of license at https://mitcc.org/
+ */
+#ifndef ICP_HELPERS_H
+#define ICP_HELPERS_H
+
+/*****************************************************************************
+ * INCLUDES
+ *****************************************************************************/
+
+#include
+#include
+#include
+
+#include "coin_utils.h"
+#include "icp_context.h"
+
+/*****************************************************************************
+ * MACROS AND DEFINES
+ *****************************************************************************/
+
+#define ICP_IMPLICIT_ACCOUNT_DEPTH 5
+
+#define ICP_PURPOSE_INDEX 0x8000002C // 44'
+#define ICP_COIN_INDEX (0x80000000 | 223) // 223'
+#define ICP_ACCOUNT_INDEX 0x80000000 // 0'
+#define ICP_CHANGE_INDEX 0x00000000 // 0
+
+#define ICP_PREFIXED_ACCOUNT_ID_LENGTH 21
+#define ICP_ACCOUNT_ADDRESS_LENGTH 34
+#define SECP256K1_DER_PREFIX_LEN \
+ 23 // Secp256k1 OID (Object Identifier) prefix len
+#define SECP256K1_DER_PK_LEN \
+ SECP256K1_DER_PREFIX_LEN + SECP256K1_UNCOMPRESSED_PK_LEN
+
+#define SHA224_DIGEST_LENGTH 28
+
+/*****************************************************************************
+ * 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
+ *
+ * @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 icp_derivation_path_guard(const uint32_t *path, uint8_t levels);
+
+/**
+ * @brief Derives the DER-encoded public key from the given uncompressed public
+ * key for Secp256k1.
+ *
+ * This function encodes the provided uncompressed public key in DER format and
+ * stores the result in the `result` buffer.
+ *
+ * @param[in] public_key The uncompressed public key to encode in DER format.
+ * @param[out] result The buffer to store the DER-encoded public key.
+ *
+ * @note The caller must ensure that `result` has sufficient space to store the
+ * DER-encoded public key.
+ *
+ * @return None
+ */
+void get_secp256k1_der_encoded_pub_key(const uint8_t *public_key,
+ uint8_t *result);
+
+/**
+ * @brief Computes the CRC32 checksum for a given input buffer.
+ *
+ * This function computes the CRC32 checksum of the provided input data using a
+ * lookup table. The checksum is a 32-bit value commonly used for data integrity
+ * checks.
+ *
+ * @param[in] data The input data for which the CRC32 checksum is computed.
+ * @param[in] length The length of the input data in bytes.
+ *
+ * @return The CRC32 checksum of the input data.
+ */
+uint32_t compute_crc32(const uint8_t *data, size_t length);
+
+/**
+ * @brief Custom SHA-224 function using SHA-256 core.
+ *
+ * This function hashes the data using SHA-224 algorithm.
+ *
+ * @param[in] data The input data to hash.
+ * @param[in] len The length of the input data.
+ * @param[out] digest The buffer to store the hash digest.
+ *
+ * @note The caller must ensure that `digest` has sufficient space to store the
+ * hash digest
+ *
+ * @return None.
+ */
+void sha224_Raw(const uint8_t *data, size_t len, uint8_t *digest);
+
+#endif // ICP_HELPERS_H
diff --git a/apps/icp_app/icp_main.c b/apps/icp_app/icp_main.c
new file mode 100644
index 000000000..0dd5f4525
--- /dev/null
+++ b/apps/icp_app/icp_main.c
@@ -0,0 +1,151 @@
+/**
+ * @file icp_main.c
+ * @author Cypherock X1 Team
+ * @brief A common entry point to various ICP coin actions supported.
+ * @copyright Copyright (c) 2024 HODL TECH PTE LTD
+ *
You may obtain a copy of license at https://mitcc.org/
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * (c) Copyright 2024 by HODL TECH PTE LTD
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject
+ * to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ *
+ * "Commons Clause" License Condition v1.0
+ *
+ * The Software is provided to you by the Licensor under the License,
+ * as defined below, subject to the following condition.
+ *
+ * Without limiting other conditions in the License, the grant of
+ * rights under the License will not include, and the License does not
+ * grant to you, the right to Sell the Software.
+ *
+ * For purposes of the foregoing, "Sell" means practicing any or all
+ * of the rights granted to you under the License to provide to third
+ * parties, for a fee or other consideration (including without
+ * limitation fees for hosting or consulting/ support services related
+ * to the Software), a product or service whose value derives, entirely
+ * or substantially, from the functionality of the Software. Any license
+ * notice or attribution required by the License must also include
+ * this Commons Clause License Condition notice.
+ *
+ * Software: All X1Wallet associated files.
+ * License: MIT
+ * Licensor: HODL TECH PTE LTD
+ *
+ ******************************************************************************
+ */
+
+/*****************************************************************************
+ * INCLUDES
+ *****************************************************************************/
+
+#include "icp_main.h"
+
+#include "icp_api.h"
+#include "icp_priv.h"
+#include "status_api.h"
+
+/*****************************************************************************
+ * EXTERN VARIABLES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * PRIVATE MACROS AND DEFINES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * PRIVATE TYPEDEFS
+ *****************************************************************************/
+
+/*****************************************************************************
+ * GLOBAL VARIABLES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * STATIC FUNCTION PROTOTYPES
+ *****************************************************************************/
+/**
+ * @brief Entry point for the ICP application of the X1 vault. It is invoked
+ * by the X1 vault firmware, as soon as there is a USB request raised for the
+ * Icp app.
+ *
+ * @param usb_evt The USB event which triggered invocation of the icp app
+ */
+void icp_main(usb_event_t usb_evt, const void *icp_app_config);
+
+/*****************************************************************************
+ * STATIC VARIABLES
+ *****************************************************************************/
+
+static const cy_app_desc_t icp_app_desc = {.id = 22,
+ .version =
+ {
+ .major = 1,
+ .minor = 0,
+ .patch = 0,
+ },
+ .app = icp_main,
+ .app_config = NULL};
+
+/*****************************************************************************
+ * STATIC FUNCTIONS
+ *****************************************************************************/
+void icp_main(usb_event_t usb_evt, const void *icp_app_config) {
+ icp_query_t query = ICP_QUERY_INIT_DEFAULT;
+
+ if (false == decode_icp_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 ICP_QUERY_GET_PUBLIC_KEYS_TAG:
+ case ICP_QUERY_GET_USER_VERIFIED_PUBLIC_KEY_TAG: {
+ icp_get_pub_keys(&query);
+ break;
+ }
+ case ICP_QUERY_SIGN_TXN_TAG: {
+ icp_sign_transaction(&query);
+ break;
+ }
+ default: {
+ /* In case we ever encounter invalid query, convey to the host app */
+ icp_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG,
+ ERROR_DATA_FLOW_INVALID_QUERY);
+ break;
+ }
+ }
+
+ return;
+}
+
+/*****************************************************************************
+ * GLOBAL FUNCTIONS
+ *****************************************************************************/
+const cy_app_desc_t *get_icp_app_desc() {
+ return &icp_app_desc;
+}
diff --git a/apps/icp_app/icp_main.h b/apps/icp_app/icp_main.h
new file mode 100644
index 000000000..4ebaf934a
--- /dev/null
+++ b/apps/icp_app/icp_main.h
@@ -0,0 +1,43 @@
+/**
+ * @file icp_main.h
+ * @author Cypherock X1 Team
+ * @brief
+ * @details
+
+ * @copyright Copyright (c) 2024 HODL TECH PTE LTD
+ *
You may obtain a copy of license at https://mitcc.org/
+ *
+ */
+
+#ifndef ICP_MAIN_H
+#define ICP_MAIN_H
+
+/*****************************************************************************
+ * INCLUDES
+ *****************************************************************************/
+
+#include "app_registry.h"
+#include "events.h"
+/*****************************************************************************
+ * MACROS AND DEFINES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * TYPEDEFS
+ *****************************************************************************/
+
+/*****************************************************************************
+ * EXPORTED VARIABLES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * GLOBAL FUNCTION PROTOTYPES
+ *****************************************************************************/
+/**
+ * @brief Returns the config for ICP chain app descriptor
+ *
+ * @return A const reference to cy_app_desc_t
+ */
+const cy_app_desc_t *get_icp_app_desc();
+#endif /* ICP_MAIN_H */
diff --git a/apps/icp_app/icp_priv.h b/apps/icp_app/icp_priv.h
new file mode 100644
index 000000000..3983d3561
--- /dev/null
+++ b/apps/icp_app/icp_priv.h
@@ -0,0 +1,65 @@
+/**
+ * @file icp_priv.h
+ * @author Cypherock X1 Team
+ * @brief Support for icp app internal operations
+ * This file is defined to separate ICP'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 ICP_PRIV_H
+#define ICP_PRIV_H
+/*****************************************************************************
+ * INCLUDES
+ *****************************************************************************/
+#include
+#include
+
+#include "icp_context.h"
+
+/*****************************************************************************
+ * TYPEDEFS
+ *****************************************************************************/
+typedef struct {
+ /**
+ * The structure holds the wallet information of the transaction.
+ * @note Populated by icp_handle_initiate_query()
+ */
+ icp_sign_txn_initiate_request_t init_info;
+
+ const icp_transfer_request_t *icp_transfer_req;
+
+ // decoded raw txn
+ icp_transfer_t *raw_icp_transfer_txn;
+
+} icp_txn_context_t;
+
+/*****************************************************************************
+ * EXPORTED VARIABLES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * GLOBAL FUNCTION PROTOTYPES
+ *****************************************************************************/
+
+/**
+ * @brief Handler for ICP public key derivation.
+ * @details This flow expects ICP_GET_PUBLIC_KEY_REQUEST_INITIATE_TAG as initial
+ * query, otherwise the flow is aborted
+ *
+ * @param query object for address public key query
+ */
+void icp_get_pub_keys(icp_query_t *query);
+
+/**
+ * @brief Handler for signing a transaction on icp.
+ * @details The expected request type is ICP_SIGN_TXN_REQUEST_INITIATE_TAG. The
+ * function controls the complete data exchange with host, user prompts and
+ * confirmations for signing an ICP based transaction.
+ *
+ * @param query Reference to the decoded query struct from the host app
+ */
+void icp_sign_transaction(icp_query_t *query);
+
+#endif /* ICP_PRIV_H */
diff --git a/apps/icp_app/icp_pub_key.c b/apps/icp_app/icp_pub_key.c
new file mode 100644
index 000000000..48f602bc5
--- /dev/null
+++ b/apps/icp_app/icp_pub_key.c
@@ -0,0 +1,628 @@
+/**
+ * @file icp_pub_key.c
+ * @author Cypherock X1 Team
+ * @brief Generates public key for ICP derivations.
+ * @copyright Copyright (c) 2024 HODL TECH PTE LTD
+ *
You may obtain a copy of license at https://mitcc.org/
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * (c) Copyright 2024 by HODL TECH PTE LTD
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject
+ * to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ *
+ * "Commons Clause" License Condition v1.0
+ *
+ * The Software is provided to you by the Licensor under the License,
+ * as defined below, subject to the following condition.
+ *
+ * Without limiting other conditions in the License, the grant of
+ * rights under the License will not include, and the License does not
+ * grant to you, the right to Sell the Software.
+ *
+ * For purposes of the foregoing, "Sell" means practicing any or all
+ * of the rights granted to you under the License to provide to third
+ * parties, for a fee or other consideration (including without
+ * limitation fees for hosting or consulting/ support services related
+ * to the Software), a product or service whose value derives, entirely
+ * or substantially, from the functionality of the Software. Any license
+ * notice or attribution required by the License must also include
+ * this Commons Clause License Condition notice.
+ *
+ * Software: All X1Wallet associated files.
+ * License: MIT
+ * Licensor: HODL TECH PTE LTD
+ *
+ ******************************************************************************
+ */
+
+/*****************************************************************************
+ * INCLUDES
+ *****************************************************************************/
+
+#include
+#include
+#include
+
+#include "base32.h"
+#include "bip32.h"
+#include "constant_texts.h"
+#include "curves.h"
+#include "ecdsa.h"
+#include "icp_api.h"
+#include "icp_context.h"
+#include "icp_helpers.h"
+#include "icp_priv.h"
+#include "reconstruct_wallet_flow.h"
+#include "secp256k1.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
+ *****************************************************************************/
+
+/*****************************************************************************
+ * 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 icp_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 icp_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 icp_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 icp_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 icp_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 icp_get_public_keys_derivation_path_t *path,
+ const uint8_t *seed,
+ uint8_t public_key_list[][ICP_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 icp_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(icp_query_t *query,
+ const uint8_t pubkey_list[][ICP_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 ICP. 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 check_which_request(const icp_query_t *query,
+ pb_size_t which_request) {
+ if (which_request != query->get_public_keys.which_request) {
+ icp_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG,
+ ERROR_DATA_FLOW_INVALID_REQUEST);
+ return false;
+ }
+
+ return true;
+}
+
+static bool validate_request(const icp_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
+ icp_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG,
+ ERROR_DATA_FLOW_INVALID_DATA);
+ status = false;
+ }
+
+ if (ICP_QUERY_GET_USER_VERIFIED_PUBLIC_KEY_TAG == which_request &&
+ 1 < count) {
+ // `ICP_QUERY_GET_USER_VERIFIED_PUBLIC_KEY_TAG` request contains more than
+ // one derivation path which is not expected
+ icp_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG,
+ ERROR_DATA_FLOW_INVALID_DATA);
+ status = false;
+ }
+
+ const icp_get_public_keys_derivation_path_t *path = NULL;
+
+ for (pb_size_t index = 0; index < count; index++) {
+ path = &req->derivation_paths[index];
+ if (!icp_derivation_path_guard(path->path, path->path_count)) {
+ icp_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG,
+ ERROR_DATA_FLOW_INVALID_DATA);
+ status = false;
+ break;
+ }
+ }
+
+ 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
+ icp_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 1);
+ memzero(&node, sizeof(HDNode));
+ return false;
+ }
+
+ if (NULL != public_key) {
+ memcpy(public_key, node.public_key, ICP_PUB_KEY_SIZE);
+ }
+
+ memzero(&node, sizeof(HDNode));
+ return true;
+}
+
+static bool fill_public_keys(const icp_get_public_keys_derivation_path_t *path,
+ const uint8_t *seed,
+ uint8_t public_key_list[][ICP_PUB_KEY_SIZE],
+ pb_size_t count) {
+ for (pb_size_t index = 0; index < count; index++) {
+ const icp_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(icp_query_t *query,
+ const uint8_t pubkey_list[][ICP_PUB_KEY_SIZE],
+ const pb_size_t count,
+ const pb_size_t which_request,
+ const pb_size_t which_response) {
+ icp_result_t response = init_icp_result(which_response);
+ icp_get_public_keys_result_response_t *result =
+ &response.get_public_keys.result;
+ size_t batch_limit =
+ sizeof(response.get_public_keys.result.public_keys) / ICP_PUB_KEY_SIZE;
+ size_t remaining = count;
+
+ response.get_public_keys.which_response =
+ ICP_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 * ICP_PUB_KEY_SIZE);
+
+ icp_send_result(&response);
+ remaining -= batch_size;
+ if (0 == remaining) {
+ break;
+ }
+
+ if (!icp_get_query(query, which_request) ||
+ !check_which_request(query,
+ ICP_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 (ICP_QUERY_GET_PUBLIC_KEYS_TAG == which_request) {
+ snprintf(
+ msg, sizeof(msg), UI_TEXT_ADD_ACCOUNT_PROMPT, ICP_NAME, wallet_name);
+ } else {
+ snprintf(msg, sizeof(msg), UI_TEXT_RECEIVE_PROMPT, ICP_NAME, wallet_name);
+ }
+
+ return core_scroll_page(NULL, msg, icp_send_error);
+}
+
+/*****************************************************************************
+ * GLOBAL VARIABLES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * STATIC FUNCTIONS
+ *****************************************************************************/
+
+/**
+ * @brief Extracts the principal ID from a compressed public key.
+ *
+ * This function decompresses the provided compressed public key, encodes the
+ * public key in DER format, and then computes a SHA3-224 hash of the
+ * DER-encoded public key to generate the principal ID. The principal ID is
+ * stored in the provided buffer.
+ *
+ * @param[out] principal The buffer to store the resulting principal ID.
+ * @param[in] compressed_public_key The compressed public key from which the
+ * principal is derived.
+ *
+ * @note The caller must ensure that `principal` has sufficient space to store
+ * the principal ID.
+ *
+ * @return None
+ */
+void icp_get_principal_from_pub_key(uint8_t *principal,
+ const uint8_t *compressed_public_key) {
+ if (principal == NULL || compressed_public_key == NULL) {
+ // TODO: Error handling
+ return;
+ }
+
+ uint8_t secp256k1_uncompressed_public_key[SECP256K1_UNCOMPRESSED_PK_LEN] = {
+ 0};
+ ecdsa_uncompress_pubkey(
+ &secp256k1, compressed_public_key, secp256k1_uncompressed_public_key);
+
+ uint8_t secp256k1_der_encoded_pub_key[SECP256K1_DER_PK_LEN] = {0};
+ get_secp256k1_der_encoded_pub_key(secp256k1_uncompressed_public_key,
+ secp256k1_der_encoded_pub_key);
+
+ sha224_Raw(secp256k1_der_encoded_pub_key, SECP256K1_DER_PK_LEN, principal);
+
+ principal[ICP_PRINCIPAL_LENGTH - 1] = ICP_SELF_AUTH_ID_TAG;
+}
+
+/**
+ * @brief Converts a principal ID into a human-readable account ID format.
+ *
+ * This function takes a principal ID, computes a SHA3-224 hash of the principal
+ * ID along with a predefined ICP account domain separator, and then generates a
+ * checksum. The result is a formatted account ID, which is a combination of the
+ * checksum and the hash of the principal ID.
+ *
+ * @param[in] principal The principal ID to convert.
+ * @param[out] account_id The buffer to store the resulting account ID in string
+ * format.
+ * @param[in] account_id_max_size The maximum size of the `account_id` buffer.
+ *
+ * @note The caller must ensure that `account_id` is large enough to hold the
+ * formatted account ID.
+ *
+ * @return None
+ */
+void get_account_id_to_display(const uint8_t *principal,
+ char *account_id,
+ size_t account_id_max_size) {
+ if (principal == NULL || account_id == NULL ||
+ account_id_max_size < (ICP_ACCOUNT_ID_LENGTH * 2 + 1)) {
+ // TODO: Error handling
+ return;
+ }
+
+ // b"\x0Aaccount-id"
+ const uint8_t ICP_ACCOUNT_DOMAIN_SEPARATOR[] = {
+ 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2d, 0x69, 0x64};
+
+ // TODO: handle subaccounts in future?
+ // For now only default subaccount
+ uint8_t zero_subaccount[ICP_SUBACCOUNT_ID_LEN] = {0};
+
+ size_t buffer_len = sizeof(ICP_ACCOUNT_DOMAIN_SEPARATOR) +
+ ICP_PRINCIPAL_LENGTH + ICP_SUBACCOUNT_ID_LEN;
+ uint8_t buffer[buffer_len];
+ memzero(buffer, buffer_len);
+ memcpy(buffer,
+ ICP_ACCOUNT_DOMAIN_SEPARATOR,
+ sizeof(ICP_ACCOUNT_DOMAIN_SEPARATOR));
+ memcpy(buffer + sizeof(ICP_ACCOUNT_DOMAIN_SEPARATOR),
+ principal,
+ ICP_PRINCIPAL_LENGTH);
+ memcpy(buffer + sizeof(ICP_ACCOUNT_DOMAIN_SEPARATOR) + ICP_PRINCIPAL_LENGTH,
+ zero_subaccount,
+ ICP_SUBACCOUNT_ID_LEN);
+
+ uint8_t hash[SHA224_DIGEST_LENGTH] = {0};
+ sha224_Raw(buffer, buffer_len, hash);
+
+ uint32_t checksum = compute_crc32(hash, sizeof(hash));
+
+ uint8_t account_id_bytes[ICP_ACCOUNT_ID_LENGTH] = {0};
+
+ // Store checksum in Big-Endian order
+ account_id_bytes[0] = (checksum >> 24) & 0xFF;
+ account_id_bytes[1] = (checksum >> 16) & 0xFF;
+ account_id_bytes[2] = (checksum >> 8) & 0xFF;
+ account_id_bytes[3] = (checksum) & 0xFF;
+
+ memcpy(account_id_bytes + 4, hash, sizeof(hash));
+
+ byte_array_to_hex_string(
+ account_id_bytes, ICP_ACCOUNT_ID_LENGTH, account_id, account_id_max_size);
+}
+
+/**
+ * @brief Converts a principal ID into a human-readable principal ID format.
+ *
+ * This function computes a checksum for the given principal ID and generates a
+ * base32-encoded representation of the principal ID. The result is formatted
+ * with hyphens at regular intervals to generate a more readable version of the
+ * principal ID.
+ *
+ * @param[in] principal The principal ID to convert.
+ * @param[out] principal_id The buffer to store the resulting principal ID in
+ * string format.
+ * @param[in] principal_id_max_size The maximum size of the `principal_id`
+ * buffer.
+ *
+ * @note The caller must ensure that `principal_id` is large enough to hold the
+ * formatted principal ID.
+ *
+ * @return None
+ */
+void get_principal_id_to_display(const uint8_t *principal,
+ char *principal_id,
+ size_t principal_id_max_size) {
+ if (principal == NULL || principal_id == NULL ||
+ principal_id_max_size < (ICP_ACCOUNT_ID_LENGTH * 2 + 1)) {
+ // TODO: Error handling
+ return;
+ }
+
+ uint32_t checksum = compute_crc32(principal, ICP_PRINCIPAL_LENGTH);
+
+ uint8_t principal_id_bytes[ICP_PRINCIPAL_LENGTH + 4] = {0};
+
+ // Store checksum in Big-Endian order
+ principal_id_bytes[0] = (checksum >> 24) & 0xFF;
+ principal_id_bytes[1] = (checksum >> 16) & 0xFF;
+ principal_id_bytes[2] = (checksum >> 8) & 0xFF;
+ principal_id_bytes[3] = (checksum) & 0xFF;
+
+ memcpy(principal_id_bytes + 4, principal, ICP_PRINCIPAL_LENGTH);
+
+ char principal_id_without_dashes[200] = {0};
+ size_t length = base32_encoded_length(sizeof(principal_id_bytes));
+ base32_encode(principal_id_bytes,
+ sizeof(principal_id_bytes),
+ principal_id_without_dashes,
+ sizeof(principal_id_without_dashes),
+ BASE32_ALPHABET_RFC4648_SMALLCASE);
+
+ // hyphenate output
+ size_t offset = 0;
+ size_t hyphens = 0;
+ const size_t limit = 5;
+ while (offset < length) {
+ const size_t curr_limit =
+ limit <= (length - offset) ? limit : length - offset;
+ memcpy(principal_id + offset + hyphens,
+ principal_id_without_dashes + offset,
+ curr_limit);
+ offset += curr_limit;
+
+ if (offset < length) {
+ memcpy(principal_id + offset + hyphens, "-", 1);
+ hyphens += 1;
+ }
+ }
+
+ // Ensure Null character
+ principal_id[offset + hyphens] = 0x00;
+}
+/*****************************************************************************
+ * GLOBAL FUNCTIONS
+ *****************************************************************************/
+void icp_get_pub_keys(icp_query_t *query) {
+ char wallet_name[NAME_SIZE] = "";
+ uint8_t seed[64] = {0};
+
+ const pb_size_t which_request = query->which_request;
+ const icp_get_public_keys_intiate_request_t *init_req = NULL;
+ pb_size_t which_response;
+
+ if (ICP_QUERY_GET_PUBLIC_KEYS_TAG == which_request) {
+ which_response = ICP_RESULT_GET_PUBLIC_KEYS_TAG;
+ init_req = &query->get_public_keys.initiate;
+ } else {
+ which_response = ICP_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(icp_get_public_keys_derivation_path_t)]
+ [ICP_PUB_KEY_SIZE] = {0};
+
+ if (!check_which_request(query, ICP_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, icp_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(ICP_GET_PUBLIC_KEYS_STATUS_CONFIRM);
+
+ if (!reconstruct_seed(init_req->wallet_id, &seed[0], icp_send_error)) {
+ memzero(seed, sizeof(seed));
+ return;
+ }
+
+ set_app_flow_status(ICP_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
+ icp_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 1);
+ return;
+ }
+
+ if (ICP_QUERY_GET_USER_VERIFIED_PUBLIC_KEY_TAG == which_request) {
+ uint8_t principal[ICP_PRINCIPAL_LENGTH] = {0};
+
+ icp_get_principal_from_pub_key(principal, pubkey_list[0]);
+
+ char account_id[200] = {0};
+ get_account_id_to_display(principal, account_id, sizeof(account_id));
+
+ if (!core_scroll_page(ui_text_account_id, account_id, icp_send_error)) {
+ return;
+ }
+
+ char principal_id[200] = {0};
+ get_principal_id_to_display(principal, principal_id, sizeof(principal_id));
+
+ if (!core_scroll_page(ui_text_principal_id, principal_id, icp_send_error)) {
+ return;
+ }
+
+ set_app_flow_status(ICP_GET_PUBLIC_KEYS_STATUS_VERIFY);
+ }
+
+ if (!send_public_keys(
+ query, pubkey_list, count, which_request, which_response)) {
+ return;
+ }
+
+ delay_scr_init(ui_text_check_cysync_app, DELAY_TIME);
+ return;
+}
diff --git a/apps/icp_app/icp_txn.c b/apps/icp_app/icp_txn.c
new file mode 100644
index 000000000..eb61197d1
--- /dev/null
+++ b/apps/icp_app/icp_txn.c
@@ -0,0 +1,495 @@
+/**
+ * @file icp_txn.c
+ * @author Cypherock X1 Team
+ * @brief Source file to handle transaction signing logic for ICP protocol
+ *
+ * @copyright Copyright (c) 2024 HODL TECH PTE LTD
+ *
You may obtain a copy of license at https://mitcc.org/
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * (c) Copyright 2024 by HODL TECH PTE LTD
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject
+ * to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ *
+ * "Commons Clause" License Condition v1.0
+ *
+ * The Software is provided to you by the Licensor under the License,
+ * as defined below, subject to the following condition.
+ *
+ * Without limiting other conditions in the License, the grant of
+ * rights under the License will not include, and the License does not
+ * grant to you, the right to Sell the Software.
+ *
+ * For purposes of the foregoing, "Sell" means practicing any or all
+ * of the rights granted to you under the License to provide to third
+ * parties, for a fee or other consideration (including without
+ * limitation fees for hosting or consulting/ support services related
+ * to the Software), a product or service whose value derives, entirely
+ * or substantially, from the functionality of the Software. Any license
+ * notice or attribution required by the License must also include
+ * this Commons Clause License Condition notice.
+ *
+ * Software: All X1Wallet associated files.
+ * License: MIT
+ * Licensor: HODL TECH PTE LTD
+ *
+ ******************************************************************************
+ */
+
+/*****************************************************************************
+ * INCLUDES
+ *****************************************************************************/
+
+#include
+#include
+#include
+#include
+
+#include "base58.h"
+#include "icp/sign_txn.pb.h"
+#include "icp_api.h"
+#include "icp_context.h"
+#include "icp_helpers.h"
+#include "icp_priv.h"
+#include "icp_txn_helpers.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 AND GLOBAL VARIABLES
+ *****************************************************************************/
+#define ICP_DOMAIN_SEPARATOR_LEN 11
+// "\x0Aic-request"
+uint8_t domain_separator[ICP_DOMAIN_SEPARATOR_LEN] =
+ {10, 105, 99, 45, 114, 101, 113, 117, 101, 115, 116};
+/*****************************************************************************
+ * PRIVATE MACROS AND DEFINES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * PRIVATE TYPEDEFS
+ *****************************************************************************/
+typedef icp_sign_txn_signature_response_t 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 icp_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 icp_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 icp_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 icp_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
+ * ICP_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 icp_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 icp_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 icp_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(icp_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(sig_t *signature);
+
+/**
+ * @brief Sends signature of the ICP unsigned txn to the host
+ * @details The function waits for the host to send a request of type
+ * ICP_SIGN_TXN_REQUEST_SIGNATURE_TAG and sends the response
+ *
+ * @param query Reference to buffer of type icp_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(icp_query_t *query, const sig_t *signature);
+
+/*****************************************************************************
+ * STATIC VARIABLES
+ *****************************************************************************/
+static icp_txn_context_t *icp_txn_context = NULL;
+
+/*****************************************************************************
+ * GLOBAL VARIABLES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * STATIC FUNCTIONS
+ *****************************************************************************/
+static bool check_which_request(const icp_query_t *query,
+ pb_size_t which_request) {
+ if (which_request != query->sign_txn.which_request) {
+ icp_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) {
+ icp_result_t result = init_icp_result(ICP_RESULT_SIGN_TXN_TAG);
+ result.sign_txn.which_response = which_response;
+ icp_send_result(&result);
+}
+
+static bool validate_request_data(const icp_sign_txn_request_t *request) {
+ bool status = true;
+
+ if (!icp_derivation_path_guard(request->initiate.derivation_path,
+ request->initiate.derivation_path_count)) {
+ // TODO: add proper specific errors
+ icp_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG,
+ ERROR_DATA_FLOW_INVALID_DATA);
+ status = false;
+ }
+ return status;
+}
+
+static bool handle_initiate_query(const icp_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, ICP_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,
+ icp_send_error)) {
+ return false;
+ }
+
+ snprintf(msg, sizeof(msg), UI_TEXT_SIGN_TXN_PROMPT, ICP_NAME, wallet_name);
+ // Take user consent to sign transaction for the wallet
+ if (!core_confirmation(msg, icp_send_error)) {
+ return false;
+ }
+
+ set_app_flow_status(ICP_SIGN_TXN_STATUS_CONFIRM);
+ memcpy(&icp_txn_context->init_info,
+ &query->sign_txn.initiate,
+ sizeof(icp_sign_txn_initiate_request_t));
+
+ send_response(ICP_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(icp_query_t *query) {
+ if (!icp_get_query(query, ICP_QUERY_SIGN_TXN_TAG) &&
+ !check_which_request(query, ICP_SIGN_TXN_REQUEST_TXN_DATA_TAG)) {
+ return false;
+ }
+
+ const icp_sign_txn_data_t *txn_data = &query->sign_txn.txn_data;
+ if (txn_data->has_icp_transfer_req == false) {
+ return false;
+ }
+
+ icp_txn_context->icp_transfer_req = &txn_data->icp_transfer_req;
+
+ // Verify request_type = "call"
+ if (memcmp(icp_txn_context->icp_transfer_req->request_type, "call", 4) != 0) {
+ return false;
+ }
+
+ // Verify method_name = "transfer"
+ if (memcmp(icp_txn_context->icp_transfer_req->method_name, "transfer", 8) !=
+ 0) {
+ return false;
+ }
+
+ send_response(ICP_SIGN_TXN_RESPONSE_DATA_ACCEPTED_TAG);
+
+ icp_txn_context->raw_icp_transfer_txn =
+ (icp_transfer_t *)malloc(sizeof(icp_transfer_t));
+
+ if (!icp_parse_transfer_txn(icp_txn_context->icp_transfer_req->arg.bytes,
+ icp_txn_context->icp_transfer_req->arg.size,
+ icp_txn_context->raw_icp_transfer_txn)) {
+ icp_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG,
+ ERROR_DATA_FLOW_INVALID_DATA);
+ return false;
+ }
+
+ return true;
+}
+
+static bool get_user_verification(void) {
+ const icp_transfer_t *decoded_utxn = icp_txn_context->raw_icp_transfer_txn;
+
+ char to_address[ICP_ACCOUNT_ID_LENGTH * 2 + 1] = "";
+
+ byte_array_to_hex_string(decoded_utxn->to,
+ ICP_ACCOUNT_ID_LENGTH,
+ to_address,
+ ICP_ACCOUNT_ID_LENGTH * 2 + 1);
+
+ if (!core_scroll_page(ui_text_verify_address, to_address, icp_send_error)) {
+ return false;
+ }
+
+ // verify recipient amount
+ uint64_t amount = 0;
+ memcpy(&amount, &decoded_utxn->amount.e8s, sizeof(uint64_t));
+ char amount_string[30] = {'\0'};
+ double decimal_amount = (double)amount;
+ decimal_amount *= 1e-8;
+ snprintf(amount_string, sizeof(amount_string), "%.8f", decimal_amount);
+
+ char display[100] = {'\0'};
+ snprintf(display,
+ sizeof(display),
+ UI_TEXT_VERIFY_AMOUNT,
+ amount_string,
+ ICP_LUNIT);
+
+ if (!core_confirmation(display, icp_send_error)) {
+ return false;
+ }
+
+ // verify transaction fee
+ uint64_t fee = 0;
+ memcpy(&fee, &decoded_utxn->fee.e8s, sizeof(uint64_t));
+ char fee_string[30] = {'\0'};
+ double decimal_fee = (double)fee;
+ decimal_fee *= 1e-8;
+ snprintf(fee_string, sizeof(fee_string), "%.8f", decimal_fee);
+
+ snprintf(
+ display, sizeof(display), UI_TEXT_SEND_TXN_FEE, fee_string, ICP_LUNIT);
+ if (!core_scroll_page(UI_TEXT_TXN_FEE, display, icp_send_error)) {
+ return false;
+ }
+
+ // verify memo
+ char display_memo[50] = {'\0'};
+ snprintf(display_memo,
+ sizeof(display_memo),
+ UI_TEXT_VERIFY_MEMO,
+ decoded_utxn->memo);
+
+ if (!core_confirmation(display_memo, icp_send_error)) {
+ return false;
+ }
+
+ set_app_flow_status(ICP_SIGN_TXN_STATUS_VERIFY);
+
+ return true;
+}
+
+static bool sign_txn(sig_t *signature) {
+ uint8_t seed[64] = {0};
+ if (!reconstruct_seed(
+ icp_txn_context->init_info.wallet_id, seed, icp_send_error)) {
+ memzero(seed, sizeof(seed));
+ // TODO: handle errors of reconstruction flow
+ return false;
+ }
+
+ set_app_flow_status(ICP_SIGN_TXN_STATUS_SEED_GENERATED);
+
+ uint8_t request_id[SHA256_DIGEST_LENGTH] = {0};
+ hash_icp_transfer_request(icp_txn_context->icp_transfer_req, request_id);
+
+ uint8_t result[SHA256_DIGEST_LENGTH + ICP_DOMAIN_SEPARATOR_LEN] = {0};
+ memcpy(result, domain_separator, ICP_DOMAIN_SEPARATOR_LEN);
+ memcpy(result + ICP_DOMAIN_SEPARATOR_LEN, request_id, SHA256_DIGEST_LENGTH);
+
+ uint8_t digest[SHA256_DIGEST_LENGTH] = {0};
+ sha256_Raw(result, SHA256_DIGEST_LENGTH + ICP_DOMAIN_SEPARATOR_LEN, digest);
+
+ HDNode hdnode = {0};
+ derive_hdnode_from_path(icp_txn_context->init_info.derivation_path,
+ icp_txn_context->init_info.derivation_path_count,
+ SECP256K1_NAME,
+ seed,
+ &hdnode);
+
+ ecdsa_sign_digest(&secp256k1,
+ hdnode.private_key,
+ digest,
+ signature->transfer_req_signature,
+ NULL,
+ NULL);
+
+ uint8_t read_state_request_id[SHA256_DIGEST_LENGTH] = {0};
+ get_icp_read_state_request_id(read_state_request_id,
+ request_id,
+ sizeof(request_id),
+ icp_txn_context->icp_transfer_req);
+
+ uint8_t read_state_request_result[SHA256_DIGEST_LENGTH +
+ ICP_DOMAIN_SEPARATOR_LEN] = {0};
+ memcpy(read_state_request_result, domain_separator, ICP_DOMAIN_SEPARATOR_LEN);
+ memcpy(read_state_request_result + ICP_DOMAIN_SEPARATOR_LEN,
+ read_state_request_id,
+ SHA256_DIGEST_LENGTH);
+
+ uint8_t read_state_request_digest[SHA256_DIGEST_LENGTH] = {0};
+ sha256_Raw(read_state_request_result,
+ SHA256_DIGEST_LENGTH + ICP_DOMAIN_SEPARATOR_LEN,
+ read_state_request_digest);
+
+ ecdsa_sign_digest(&secp256k1,
+ hdnode.private_key,
+ read_state_request_digest,
+ signature->read_state_req_signature,
+ NULL,
+ NULL);
+
+ memzero(request_id, sizeof(request_id));
+ memzero(read_state_request_id, sizeof(read_state_request_id));
+ memzero(result, sizeof(result));
+ memzero(read_state_request_result, sizeof(read_state_request_result));
+ memzero(digest, sizeof(digest));
+ memzero(read_state_request_digest, sizeof(read_state_request_digest));
+ memzero(seed, sizeof(seed));
+ memzero(&hdnode, sizeof(hdnode));
+
+ return true;
+}
+
+static bool send_signature(icp_query_t *query, const sig_t *signature) {
+ icp_result_t result = init_icp_result(ICP_RESULT_SIGN_TXN_TAG);
+ result.sign_txn.which_response = ICP_SIGN_TXN_RESPONSE_SIGNATURE_TAG;
+
+ if (!icp_get_query(query, ICP_QUERY_SIGN_TXN_TAG) ||
+ !check_which_request(query, ICP_SIGN_TXN_REQUEST_SIGNATURE_TAG)) {
+ return false;
+ }
+
+ memcpy(&result.sign_txn.signature, signature, sizeof(sig_t));
+
+ icp_send_result(&result);
+ return true;
+}
+
+/*****************************************************************************
+ * GLOBAL FUNCTIONS
+ *****************************************************************************/
+
+void icp_sign_transaction(icp_query_t *query) {
+ icp_txn_context = (icp_txn_context_t *)malloc(sizeof(icp_txn_context_t));
+ memzero(icp_txn_context, sizeof(icp_txn_context_t));
+
+ sig_t signature = {0};
+
+ if (handle_initiate_query(query) && fetch_valid_input(query) &&
+ get_user_verification() && sign_txn(&signature) &&
+ send_signature(query, &signature)) {
+ delay_scr_init(ui_text_check_cysync, DELAY_TIME);
+ }
+
+ if (icp_txn_context) {
+ if (icp_txn_context->raw_icp_transfer_txn) {
+ free(icp_txn_context->raw_icp_transfer_txn);
+ icp_txn_context->raw_icp_transfer_txn = NULL;
+ }
+ free(icp_txn_context);
+ icp_txn_context = NULL;
+ }
+}
diff --git a/apps/icp_app/icp_txn_helpers.c b/apps/icp_app/icp_txn_helpers.c
new file mode 100644
index 000000000..f95626439
--- /dev/null
+++ b/apps/icp_app/icp_txn_helpers.c
@@ -0,0 +1,540 @@
+/**
+ * @file icp_txn_helpers.c
+ * @author Cypherock X1 Team
+ * @brief Helper functions for the ICP app for txn signing flow
+ * @copyright Copyright (c) 2024 HODL TECH PTE LTD
+ *
You may obtain a copy of license at https://mitcc.org/
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * (c) Copyright 2024 by HODL TECH PTE LTD
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject
+ * to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ *
+ * "Commons Clause" License Condition v1.0
+ *
+ * The Software is provided to you by the Licensor under the License,
+ * as defined below, subject to the following condition.
+ *
+ * Without limiting other conditions in the License, the grant of
+ * rights under the License will not include, and the License does not
+ * grant to you, the right to Sell the Software.
+ *
+ * For purposes of the foregoing, "Sell" means practicing any or all
+ * of the rights granted to you under the License to provide to third
+ * parties, for a fee or other consideration (including without
+ * limitation fees for hosting or consulting/ support services related
+ * to the Software), a product or service whose value derives, entirely
+ * or substantially, from the functionality of the Software. Any license
+ * notice or attribution required by the License must also include
+ * this Commons Clause License Condition notice.
+ *
+ * Software: All X1Wallet associated files.
+ * License: MIT
+ * Licensor: HODL TECH PTE LTD
+ *
+ ******************************************************************************
+ */
+
+/*****************************************************************************
+ * INCLUDES
+ *****************************************************************************/
+
+#include "icp_txn_helpers.h"
+
+#include
+#include
+#include
+
+#include "icp_context.h"
+#include "utils.h"
+
+/*****************************************************************************
+ * EXTERN VARIABLES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * PRIVATE MACROS AND DEFINES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * PRIVATE TYPEDEFS
+ *****************************************************************************/
+
+/*****************************************************************************
+ * STATIC VARIABLES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * GLOBAL VARIABLES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * STATIC FUNCTION PROTOTYPES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * STATIC FUNCTIONS
+ *****************************************************************************/
+
+/*****************************************************************************
+ * GLOBAL FUNCTIONS
+ *****************************************************************************/
+
+/**
+ * Hash a string using SHA-256.
+ */
+void hash_string(const char *value, uint8_t *hash) {
+ sha256_Raw((const uint8_t *)value, strlen(value), hash);
+}
+
+/**
+ * Hash a given LEB128-encoded integer.
+ */
+void hash_leb128(uint64_t value, uint8_t *hash) {
+ uint8_t buffer[10] = {0};
+ size_t offset = 0;
+
+ do {
+ buffer[offset] = (value & 0x7F) | (value >= 0x80 ? 0x80 : 0);
+ value >>= 7;
+ offset++;
+ } while (value > 0);
+
+ sha256_Raw(buffer, offset, hash);
+}
+
+/**
+ * Comparison function for sorting hash pairs.
+ */
+int compare_hashes(const void *a, const void *b) {
+ return memcmp(((hash_pair_t *)a)->key_hash,
+ ((hash_pair_t *)b)->key_hash,
+ SHA256_DIGEST_LENGTH);
+}
+
+bool decode_vec(int64_t type,
+ const uint8_t *data,
+ size_t *offset,
+ uint8_t *res) {
+ switch (type) {
+ case Nat8: {
+ uint64_t len = leb_decode(data, offset);
+ memcpy(res, data + *offset, len);
+ (*offset) += len;
+ break;
+ }
+ default:
+ return false;
+ }
+ return true;
+}
+
+bool read_recipient_account_id(const uint8_t *data,
+ size_t *offset,
+ icp_transfer_t *txn) {
+ return decode_vec(Nat8, data, offset, txn->to);
+}
+
+bool read_amount_value(const uint8_t *data,
+ size_t *offset,
+ icp_transfer_t *txn) {
+ txn->amount.e8s = U64_READ_LE_ARRAY(data + *offset);
+ (*offset) += 8;
+ return true;
+}
+
+bool read_fee_value(const uint8_t *data, size_t *offset, icp_transfer_t *txn) {
+ txn->fee.e8s = U64_READ_LE_ARRAY(data + *offset);
+ (*offset) += 8;
+ return true;
+}
+
+bool read_memo_value(const uint8_t *data, size_t *offset, icp_transfer_t *txn) {
+ txn->memo = U64_READ_LE_ARRAY(data + *offset);
+ (*offset) += 8;
+ return true;
+}
+
+bool read_from_subaccount_value(const uint8_t *data,
+ size_t *offset,
+ icp_transfer_t *txn) {
+ txn->has_from_subaccount = *(data + *offset);
+ (*offset)++;
+
+ if (txn->has_from_subaccount) {
+ return decode_vec(Nat8, data, offset, txn->from_subaccount);
+ }
+ return true;
+}
+
+bool read_created_at_time_value(const uint8_t *data,
+ size_t *offset,
+ icp_transfer_t *txn) {
+ txn->has_created_at_time = *(data + *offset);
+ (*offset)++;
+
+ if (txn->has_created_at_time) {
+ txn->created_at_time.timestamp_nanos = U64_READ_LE_ARRAY(data + *offset);
+ (*offset) += 8;
+ }
+
+ return true;
+}
+
+/// Reference:
+// https://github.com/dfinity/agent-js/blob/main/packages/candid/src/utils/leb128.ts#L74
+uint64_t leb_decode(const uint8_t *buffer, size_t *offset) {
+ uint64_t result = 0;
+ int shift = 0;
+ uint8_t byte;
+ do {
+ byte = buffer[*offset];
+ (*offset)++;
+ result |= ((uint64_t)(byte & 0x7F) << shift);
+ shift += 7;
+ } while ((byte & 0x80) != 0);
+ return result;
+}
+
+/// Reference:
+// https://github.com/dfinity/agent-js/blob/main/packages/candid/src/utils/leb128.ts#L135
+int64_t sleb_decode(const uint8_t *buffer, size_t *offset) {
+ int64_t result = 0;
+ int shift = 0;
+ uint8_t byte;
+
+ while (1) {
+ byte = buffer[*offset];
+ (*offset)++;
+ result |= (int64_t)(byte & 0x7F) << shift;
+ shift += 7;
+
+ // If MSB is not set, break (last byte)
+ if ((byte & 0x80) == 0) {
+ // Check if the sign bit (0x40) is set for negative numbers
+ if (shift < 64 && (byte & 0x40)) {
+ result |= -((int64_t)1 << shift);
+ }
+ break;
+ }
+ }
+ return result;
+}
+
+/// Reference:
+// https://github.com/dfinity/agent-js/blob/main/packages/candid/src/idl.ts#L1588
+// @TODO: add unit tests for parser
+bool icp_parse_transfer_txn(const uint8_t *byte_array,
+ uint16_t byte_array_size,
+ icp_transfer_t *txn) {
+ size_t offset = 0;
+
+ // Verify the "DIDL" Magic Number
+ if (memcmp(byte_array, MAGIC_NUMBER, 4) != 0) {
+ return false;
+ }
+ offset += 4;
+
+ // Decode Type Table
+ size_t num_types = leb_decode(byte_array, &offset);
+ IDL_complex_type_t type_table[num_types];
+
+ for (size_t i = 0; i < num_types; i++) {
+ int64_t type = sleb_decode(byte_array, &offset);
+
+ switch (type) {
+ case Opt:
+ case Vector: {
+ int64_t child_type = sleb_decode(byte_array, &offset);
+ IDL_complex_type_t c_ty;
+ c_ty.type_id = type;
+ c_ty.child_type = child_type;
+ type_table[i] = c_ty;
+ break;
+ }
+ case Record: {
+ IDL_complex_type_t c_ty;
+ c_ty.type_id = type;
+ c_ty.num_fields = leb_decode(byte_array, &offset);
+ c_ty.fields =
+ (record_field_t *)malloc(sizeof(record_field_t) * c_ty.num_fields);
+ for (int j = 0; j < c_ty.num_fields; j++) {
+ uint64_t hash = leb_decode(byte_array, &offset);
+ int64_t field_type = sleb_decode(byte_array, &offset);
+ record_field_t field;
+ field.key_hash = hash;
+ field.type = field_type;
+ c_ty.fields[j] = field;
+ }
+
+ type_table[i] = c_ty;
+ break;
+ }
+ default:
+ return false;
+ }
+ }
+
+ uint64_t arg_count = leb_decode(byte_array, &offset);
+ // only 1 argument supported
+ if (arg_count != 1) {
+ return false;
+ }
+
+ uint64_t arg_type_index = leb_decode(byte_array, &offset);
+
+ if (arg_type_index < 0 && arg_type_index >= num_types) {
+ return false;
+ }
+
+ IDL_complex_type_t arg_type = type_table[arg_type_index];
+
+ // we expect a single record of 4-6 fields (to, amount, fee, memo,
+ // from_subaccount, created_at_time)
+ if (arg_type.type_id != Record || arg_type.num_fields < 4 ||
+ arg_type.num_fields > 6) {
+ return false;
+ }
+
+ // Decode Transfer Args
+ for (int i = 0; i < arg_type.num_fields; i++) {
+ record_field_t field = arg_type.fields[i];
+ switch (field.key_hash) {
+ case transfer_hash_to:
+ // we can also verify the type
+ // not doing right now
+ if (!read_recipient_account_id(byte_array, &offset, txn)) {
+ return false;
+ }
+ break;
+ case transfer_hash_amount:
+ if (!read_amount_value(byte_array, &offset, txn)) {
+ return false;
+ }
+ break;
+ case transfer_hash_fee:
+ if (!read_fee_value(byte_array, &offset, txn)) {
+ return false;
+ }
+ break;
+ case transfer_hash_memo:
+ if (!read_memo_value(byte_array, &offset, txn)) {
+ return false;
+ }
+ break;
+ case transfer_hash_from_subaccount:
+ if (!read_from_subaccount_value(byte_array, &offset, txn)) {
+ return false;
+ }
+ break;
+ case transfer_hash_created_at_time:
+ if (!read_created_at_time_value(byte_array, &offset, txn)) {
+ return false;
+ }
+ break;
+ default:
+ return false;
+ }
+ }
+
+ if (offset != byte_array_size) {
+ return false;
+ }
+
+ // free memory
+ for (size_t i = 0; i < num_types; i++) {
+ if (type_table[i].fields) {
+ free(type_table[i].fields);
+ type_table[i].fields = NULL;
+ }
+ }
+
+ return true;
+}
+
+/// Reference:
+// https://github.com/dfinity/agent-js/blob/main/packages/agent/src/request_id.ts#L87
+void hash_icp_transfer_request(const icp_transfer_request_t *request,
+ uint8_t *hash) {
+ hash_pair_t pairs[NUM_FIELDS_IN_ICP_TRANSFER_REQUEST] = {0};
+ size_t pair_count = 0;
+
+ hash_string("request_type", pairs[pair_count].key_hash);
+ sha256_Raw(request->request_type,
+ sizeof(request->request_type),
+ pairs[pair_count].value_hash);
+ pair_count++;
+
+ hash_string("canister_id", pairs[pair_count].key_hash);
+ sha256_Raw(request->canister_id.bytes,
+ request->canister_id.size,
+ pairs[pair_count].value_hash);
+ pair_count++;
+
+ hash_string("method_name", pairs[pair_count].key_hash);
+ sha256_Raw(request->method_name,
+ sizeof(request->method_name),
+ pairs[pair_count].value_hash);
+ pair_count++;
+
+ hash_string("sender", pairs[pair_count].key_hash);
+ sha256_Raw(
+ request->sender, sizeof(request->sender), pairs[pair_count].value_hash);
+ pair_count++;
+
+ hash_string("ingress_expiry", pairs[pair_count].key_hash);
+ sha256_Raw(request->ingress_expiry.bytes,
+ request->ingress_expiry.size,
+ pairs[pair_count].value_hash);
+ pair_count++;
+
+ hash_string("nonce", pairs[pair_count].key_hash);
+ sha256_Raw(
+ request->nonce, sizeof(request->nonce), pairs[pair_count].value_hash);
+ pair_count++;
+
+ hash_string("arg", pairs[pair_count].key_hash);
+ sha256_Raw(
+ request->arg.bytes, request->arg.size, pairs[pair_count].value_hash);
+ pair_count++;
+
+ // Sort key-value pairs by key hash
+ qsort(pairs, pair_count, sizeof(hash_pair_t), compare_hashes);
+
+ uint8_t concatenated[MAX_CONCATENATED_ICP_REQUEST_HASHES_SIZE] = {0};
+ size_t offset = 0;
+ for (size_t i = 0; i < pair_count; i++) {
+ memcpy(&concatenated[offset], pairs[i].key_hash, SHA256_DIGEST_LENGTH);
+ offset += SHA256_DIGEST_LENGTH;
+ memcpy(&concatenated[offset], pairs[i].value_hash, SHA256_DIGEST_LENGTH);
+ offset += SHA256_DIGEST_LENGTH;
+ }
+
+ sha256_Raw(concatenated, offset, hash);
+}
+
+/// Reference:
+// https://github.com/dfinity/agent-js/blob/main/packages/agent/src/request_id.ts#L87
+void hash_icp_read_state_request(const icp_read_state_request_t *request,
+ uint8_t *hash) {
+ hash_pair_t pairs[NUM_FIELDS_IN_ICP_READ_STATE_REQUEST] = {0};
+ size_t pair_count = 0;
+
+ hash_string("request_type", pairs[pair_count].key_hash);
+ sha256_Raw((const uint8_t *)request->request_type,
+ strlen(request->request_type),
+ pairs[pair_count].value_hash);
+ pair_count++;
+
+ hash_string("sender", pairs[pair_count].key_hash);
+ sha256_Raw(
+ request->sender, sizeof(request->sender), pairs[pair_count].value_hash);
+ pair_count++;
+
+ hash_string("ingress_expiry", pairs[pair_count].key_hash);
+ sha256_Raw(request->ingress_expiry.bytes,
+ request->ingress_expiry.size,
+ pairs[pair_count].value_hash);
+ pair_count++;
+
+ hash_string("paths", pairs[pair_count].key_hash);
+ uint8_t path_hashes[SHA256_DIGEST_LENGTH * request->path_count];
+
+ for (size_t path_index = 0; path_index < request->path_count; path_index++) {
+ size_t segment_count = request->paths[path_index].segment_count;
+ uint8_t segment_hashes[SHA256_DIGEST_LENGTH * segment_count];
+
+ for (size_t segment_index = 0; segment_index < segment_count;
+ segment_index++) {
+ sha256_Raw(request->paths[path_index].segments[segment_index].bytes,
+ request->paths[path_index].segments[segment_index].size,
+ segment_hashes + SHA256_DIGEST_LENGTH * segment_index);
+ }
+
+ sha256_Raw(segment_hashes,
+ sizeof(segment_hashes),
+ path_hashes + SHA256_DIGEST_LENGTH * path_index);
+ }
+
+ sha256_Raw(path_hashes, sizeof(path_hashes), pairs[pair_count].value_hash);
+ pair_count++;
+
+ // Sort key-value pairs by key hash
+ qsort(pairs, pair_count, sizeof(hash_pair_t), compare_hashes);
+
+ uint8_t concatenated[MAX_CONCATENATED_ICP_REQUEST_HASHES_SIZE] = {0};
+ size_t offset = 0;
+ for (size_t i = 0; i < pair_count; i++) {
+ memcpy(&concatenated[offset], pairs[i].key_hash, SHA256_DIGEST_LENGTH);
+ offset += SHA256_DIGEST_LENGTH;
+ memcpy(&concatenated[offset], pairs[i].value_hash, SHA256_DIGEST_LENGTH);
+ offset += SHA256_DIGEST_LENGTH;
+ }
+
+ sha256_Raw(concatenated, offset, hash);
+}
+
+/// Reference:
+// https://github.com/dfinity/agent-js/blob/main/packages/agent/src/agent/http/index.ts#L1030
+void get_icp_read_state_request_id(
+ uint8_t *read_state_request_id,
+ const uint8_t *transfer_request_id,
+ size_t transfer_request_id_len,
+ const icp_transfer_request_t *transfer_request) {
+ icp_read_state_request_t read_state_req;
+
+ // request_type
+ read_state_req.request_type = "read_state";
+
+ // path = ["request_status", requestId]
+ // paths = [path]
+ // see
+ // https://github.com/dfinity/agent-js/blob/main/packages/agent/src/polling/index.ts#L86
+ read_state_req.path_count = 1;
+
+ read_state_req.paths[0].segment_count = 2;
+
+ const char *status_str = "request_status";
+ size_t status_len = strlen(status_str);
+ memcpy(read_state_req.paths[0].segments[0].bytes, status_str, status_len);
+ read_state_req.paths[0].segments[0].size = status_len;
+
+ memcpy(read_state_req.paths[0].segments[1].bytes,
+ transfer_request_id,
+ transfer_request_id_len);
+ read_state_req.paths[0].segments[1].size = transfer_request_id_len;
+
+ // sender
+ memcpy(read_state_req.sender,
+ transfer_request->sender,
+ sizeof(transfer_request->sender));
+
+ // ingress_expiry
+ memcpy(read_state_req.ingress_expiry.bytes,
+ transfer_request->ingress_expiry.bytes,
+ transfer_request->ingress_expiry.size);
+ read_state_req.ingress_expiry.size = transfer_request->ingress_expiry.size;
+
+ hash_icp_read_state_request(&read_state_req, read_state_request_id);
+}
\ No newline at end of file
diff --git a/apps/icp_app/icp_txn_helpers.h b/apps/icp_app/icp_txn_helpers.h
new file mode 100644
index 000000000..bfca44350
--- /dev/null
+++ b/apps/icp_app/icp_txn_helpers.h
@@ -0,0 +1,149 @@
+/**
+ * @file icp_txn_helpers.h
+ * @author Cypherock X1 Team
+ * @brief Helper functions for the ICP app for txn signing flow
+ * @copyright Copyright (c) 2024 HODL TECH PTE LTD
+ *
You may obtain a copy of license at https://mitcc.org/
+ */
+#ifndef ICP_TXN_HELPERS_H
+#define ICP_TXN_HELPERS_H
+
+/*****************************************************************************
+ * INCLUDES
+ *****************************************************************************/
+#include
+#include
+#include
+
+#include "icp_context.h"
+#include "icp_priv.h"
+
+/*****************************************************************************
+ * MACROS AND DEFINES
+ *****************************************************************************/
+#define MAGIC_NUMBER "DIDL"
+#define MAX_CONCATENATED_ICP_REQUEST_HASHES_SIZE 512
+#define NUM_FIELDS_IN_ICP_TRANSFER_REQUEST 7
+#define NUM_FIELDS_IN_ICP_READ_STATE_REQUEST 4
+/*****************************************************************************
+ * TYPEDEFS
+ *****************************************************************************/
+
+// To hold the hash of a record field key and its type
+typedef struct {
+ uint64_t key_hash; // Hash of the record field key
+ int64_t type; // Type ID of the field
+} record_field_t;
+
+// To hold a complex IDL type
+typedef struct {
+ int64_t type_id; // Type identifier (e.g., Nat8, Vector, Record etc.)
+ union {
+ int64_t child_type; // Used if it's a compound type (e.g., Vector)
+ struct {
+ uint64_t num_fields; // Number of fields if it's a record
+ record_field_t *fields; // Pointer to field array
+ };
+ };
+} IDL_complex_type_t;
+
+// To hold the hashes of a struct field, its key hash and the corresponding
+// value hash
+typedef struct {
+ uint8_t key_hash[SHA256_DIGEST_LENGTH];
+ uint8_t value_hash[SHA256_DIGEST_LENGTH];
+} hash_pair_t;
+
+/*****************************************************************************
+ * EXPORTED VARIABLES
+ *****************************************************************************/
+
+/*****************************************************************************
+ * GLOBAL FUNCTION PROTOTYPES
+ *****************************************************************************/
+
+/**
+ * Function to decode LEB128 encoded positive integers.
+ * @param buffer Pointer to the LEB128 encoded bytes.
+ * @param offset Offset from the start of the buffer.
+ * @return Decoded integer value.
+ */
+uint64_t leb_decode(const uint8_t *buffer, size_t *offset);
+
+/**
+ * Decode a Signed LEB128 encoded buffer into an integer.
+ * Supports decoding of negative values using two's complement.
+ * @param buffer Pointer to the LEB128 encoded bytes.
+ * @param offset Offset from the start of the buffer.
+ * @return Decoded integer value.
+ */
+int64_t sleb_decode(const uint8_t *buffer, size_t *offset);
+
+/**
+ * @brief Parse byte array of unsigned txn and store decoded information to be
+ * used for user confirmation.
+ * @details Only icp payment transaction is supported as of now.
+ *
+ * @param byte_array Constant reference to buffer containing the raw unsigned
+ * txn
+ * @param byte_array_size Size in bytes of the txn
+ * @param utxn Reference to buffer where decoded information will be populated.
+ * It can be used at a later stage for user verification.
+ * @return true If the parsing was successful
+ * @return false If the parsing failed - it could be due to an unsupported
+ * transaction or data type or missing information
+ */
+bool icp_parse_transfer_txn(const uint8_t *byte_array,
+ uint16_t byte_array_size,
+ icp_transfer_t *utxn);
+
+/**
+ * @brief Hash icp_transfer_request_t structure using SHA-256.
+ *
+ * @param[in] request icp_transfer_request_t to hash
+ * @param [out] hash The buffer to store the result hash
+ *
+ * @note The caller must ensure that `hash` has sufficient space to store the
+ * hash digest
+ *
+ * @return None.
+ */
+void hash_icp_transfer_request(const icp_transfer_request_t *request,
+ uint8_t *hash);
+
+/**
+ * @brief Hash icp_read_state_request_t structure using SHA-256.
+ *
+ * @param[in] request icp_read_state_request_t to hash
+ * @param [out] hash The buffer to store the result hash
+ *
+ * @note The caller must ensure that `hash` has sufficient space to store the
+ * hash digest
+ *
+ * @return None.
+ */
+void hash_icp_read_state_request(const icp_read_state_request_t *request,
+ uint8_t *hash);
+
+/**
+ * @brief Generate icp_read_state_request_id by constructing
+ * and hashing icp_read_state_request_t structure using SHA-256.
+ *
+ * @param[out] read_state_request_id The buffer to store the result
+ * icp_read_state_request_id
+ * @param [in] transfer_request_id Used in constructing icp_read_state_request_t
+ * @param [in] transfer_request Used in constructing icp_read_state_request_t
+ *
+ * @note The caller must ensure that `read_state_request_id` has sufficient
+ * space to store the request_id
+ *
+ * @return None.
+ */
+void get_icp_read_state_request_id(
+ uint8_t *read_state_request_id,
+ const uint8_t *transfer_request_id,
+ size_t transfer_request_id_len,
+ const icp_transfer_request_t *transfer_request);
+
+#endif /* ICP_TXN_HELPERS_H */
diff --git a/common/core/app_registry.h b/common/core/app_registry.h
index 243f05b21..fee05409a 100644
--- a/common/core/app_registry.h
+++ b/common/core/app_registry.h
@@ -23,7 +23,7 @@
* MACROS AND DEFINES
*****************************************************************************/
-#define REGISTRY_MAX_APPS 22
+#define REGISTRY_MAX_APPS 23
/*****************************************************************************
* TYPEDEFS
diff --git a/common/core/core_flow_init.c b/common/core/core_flow_init.c
index 1faddf4f6..4f8f97f98 100644
--- a/common/core/core_flow_init.c
+++ b/common/core/core_flow_init.c
@@ -73,6 +73,7 @@
#include "eth_app.h"
#include "evm_main.h"
#include "fantom_app.h"
+#include "icp_main.h"
#include "inheritance_main.h"
#include "ltc_app.h"
#include "main_menu.h"
@@ -185,4 +186,5 @@ 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_icp_app_desc());
}
diff --git a/common/cypherock-common b/common/cypherock-common
index 4b5ee9d84..c2526545f 160000
--- a/common/cypherock-common
+++ b/common/cypherock-common
@@ -1 +1 @@
-Subproject commit 4b5ee9d840b5eb512b38c03167c06fc65d81041c
+Subproject commit c2526545fa69280bc06c7cc4770a734118568dae
diff --git a/common/libraries/crypto/base32.c b/common/libraries/crypto/base32.c
index ef9b76bec..1b098393e 100644
--- a/common/libraries/crypto/base32.c
+++ b/common/libraries/crypto/base32.c
@@ -25,6 +25,7 @@
#include
const char *BASE32_ALPHABET_RFC4648 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ23456789";
+const char *BASE32_ALPHABET_RFC4648_SMALLCASE = "abcdefghijklmnopqrstuvwxyz234567";
static inline void base32_5to8(const uint8_t *in, uint8_t length, uint8_t *out);
static inline bool base32_8to5(const uint8_t *in, uint8_t length, uint8_t *out,
diff --git a/common/libraries/crypto/base32.h b/common/libraries/crypto/base32.h
index 8b5cc8513..345a443f7 100644
--- a/common/libraries/crypto/base32.h
+++ b/common/libraries/crypto/base32.h
@@ -28,6 +28,7 @@
#include
extern const char *BASE32_ALPHABET_RFC4648;
+extern const char *BASE32_ALPHABET_RFC4648_SMALLCASE;
char *base32_encode(const uint8_t *in, size_t inlen, char *out, size_t outlen,
const char *alphabet);
diff --git a/common/proto-options/icp/core.options b/common/proto-options/icp/core.options
new file mode 100644
index 000000000..22f6f23ff
--- /dev/null
+++ b/common/proto-options/icp/core.options
@@ -0,0 +1 @@
+# Options for file common/cypherock-common/proto/icp/core.proto
diff --git a/common/proto-options/icp/error.options b/common/proto-options/icp/error.options
new file mode 100644
index 000000000..77f8f4ec7
--- /dev/null
+++ b/common/proto-options/icp/error.options
@@ -0,0 +1 @@
+# Options for file common/cypherock-common/proto/icp/error.proto
diff --git a/common/proto-options/icp/get_public_key.options b/common/proto-options/icp/get_public_key.options
new file mode 100644
index 000000000..b306aae72
--- /dev/null
+++ b/common/proto-options/icp/get_public_key.options
@@ -0,0 +1,5 @@
+# Options for file common/cypherock-common/proto/icp/get_public_key.proto
+icp.GetPublicKeysDerivationPath.path type:FT_STATIC max_count:5 fixed_length:true
+icp.GetPublicKeysIntiateRequest.wallet_id type:FT_STATIC max_size:32 fixed_length:true
+icp.GetPublicKeysIntiateRequest.derivation_paths type:FT_STATIC max_count:100 fixed_length:true
+icp.GetPublicKeysResultResponse.public_keys type:FT_STATIC max_size:33 max_count:10 fixed_length:true
diff --git a/common/proto-options/icp/sign_txn.options b/common/proto-options/icp/sign_txn.options
new file mode 100644
index 000000000..2a4bb8493
--- /dev/null
+++ b/common/proto-options/icp/sign_txn.options
@@ -0,0 +1,14 @@
+# Options for file common/cypherock-common/proto/icp/sign_txn.proto
+icp.SignTxnInitiateRequest.wallet_id type:FT_STATIC max_size:32 fixed_length:true
+icp.SignTxnInitiateRequest.derivation_path type:FT_STATIC max_count:5 fixed_length:true
+icp.SignTxnSignatureResponse.transfer_req_signature type:FT_STATIC max_size:64 fixed_length:true
+icp.SignTxnSignatureResponse.read_state_req_signature type:FT_STATIC max_size:64 fixed_length:true
+
+icp.TransferRequest.request_type type:FT_STATIC max_size:4 fixed_length:true
+icp.TransferRequest.canister_id type:FT_STATIC max_size:29 fixed_length:false
+icp.TransferRequest.method_name type:FT_STATIC max_size:8 fixed_length:true
+icp.TransferRequest.arg type:FT_STATIC max_size:200 fixed_length:false
+icp.TransferRequest.sender type:FT_STATIC max_size:29 fixed_length:true
+icp.TransferRequest.ingress_expiry type:FT_STATIC max_size:10 fixed_length:false
+icp.TransferRequest.nonce type:FT_STATIC max_size:16 fixed_length:true
+
diff --git a/src/constant_texts.c b/src/constant_texts.c
index 6bd79af9a..ae32e2640 100644
--- a/src/constant_texts.c
+++ b/src/constant_texts.c
@@ -559,6 +559,11 @@ const char *ui_text_inheritance_decryption_flow_failure = "Decryption Failed";
// Solana Specific
const char *ui_text_solana_verify_mint_authority = "Verify Token Address";
+// ICP
+
+const char *ui_text_principal_id = "Principal Id";
+const char *ui_text_account_id = "Account Id";
+
#ifdef ALLOW_LOG_EXPORT
const char *ui_text_send_logs_prompt = "Send logs to the cySync app?";
#endif
diff --git a/src/constant_texts.h b/src/constant_texts.h
index 6da9cc898..c609d9e46 100644
--- a/src/constant_texts.h
+++ b/src/constant_texts.h
@@ -56,6 +56,7 @@
#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%llu"
// product hash
extern const char *product_hash;
@@ -393,6 +394,11 @@ extern const char *ui_text_inheritance_decryption_flow_confirmation_generic;
extern const char *ui_text_inheritance_decryption_flow_success;
extern const char *ui_text_inheritance_decryption_flow_failure;
+// ICP
+
+extern const char *ui_text_principal_id;
+extern const char *ui_text_account_id;
+
#ifdef ALLOW_LOG_EXPORT
extern const char *ui_text_send_logs_prompt;
#endif
diff --git a/utilities/cmake/firmware/firmware.cmake b/utilities/cmake/firmware/firmware.cmake
index f0b8a837d..e573bea43 100644
--- a/utilities/cmake/firmware/firmware.cmake
+++ b/utilities/cmake/firmware/firmware.cmake
@@ -62,6 +62,7 @@ target_include_directories(${EXECUTABLE} PRIVATE
apps/starknet_app
apps/xrp_app
+ apps/icp_app
src/
src/menu
@@ -188,6 +189,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/icp_app>
)
target_compile_options(${EXECUTABLE} PRIVATE
diff --git a/utilities/cmake/simulator/simulator.cmake b/utilities/cmake/simulator/simulator.cmake
index 21d5fa8c4..6058abc68 100644
--- a/utilities/cmake/simulator/simulator.cmake
+++ b/utilities/cmake/simulator/simulator.cmake
@@ -58,6 +58,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE
apps/inheritance_app
apps/xrp_app
apps/starknet_app
+ apps/icp_app
src/
src/menu
@@ -163,6 +164,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/icp_app>
)
IF(UNIT_TESTS_SWITCH)