Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions apps/hedera_app/hedera_api.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* @file hedera_api.c
* @author Cypherock X1 Team
* @brief Defines helper APIs for the Hedera app.
* @copyright Copyright (c) 2025 HODL TECH PTE LTD
*/
#include "hedera_api.h"
#include <pb_decode.h>
#include <pb_encode.h>
#include "common_error.h"
#include "core_api.h"
#include "events.h"

bool decode_hedera_query(const uint8_t *data, uint16_t data_size, hedera_query_t *query_out) {
if (NULL == data || NULL == query_out || 0 == data_size) {
hedera_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, ERROR_DATA_FLOW_DECODING_FAILED);
return false;
}
memzero(query_out, sizeof(hedera_query_t));
pb_istream_t stream = pb_istream_from_buffer(data, data_size);
bool status = pb_decode(&stream, HEDERA_QUERY_FIELDS, query_out);
if (!status) {
hedera_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, ERROR_DATA_FLOW_DECODING_FAILED);
}
return status;
}

bool encode_hedera_result(const hedera_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;
pb_ostream_t stream = pb_ostream_from_buffer(buffer, max_buffer_len);
bool status = pb_encode(&stream, HEDERA_RESULT_FIELDS, result);
if (status) {
*bytes_written_out = stream.bytes_written;
}
return status;
}

bool check_hedera_query(const hedera_query_t *query, pb_size_t exp_query_tag) {
if ((NULL == query) || (exp_query_tag != query->which_request)) {
hedera_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, ERROR_DATA_FLOW_INVALID_QUERY);
return false;
}
return true;
}

hedera_result_t init_hedera_result(pb_size_t result_tag) {
hedera_result_t result = HEDERA_RESULT_INIT_ZERO;
result.which_response = result_tag;
return result;
}

void hedera_send_error(pb_size_t which_error, uint32_t error_code) {
hedera_result_t result = init_hedera_result(HEDERA_RESULT_COMMON_ERROR_TAG);
result.common_error = init_common_error(which_error, error_code);
hedera_send_result(&result);
}

void hedera_send_result(const hedera_result_t *result) {
uint8_t buffer[1700] = {0};
size_t bytes_encoded = 0;
ASSERT(encode_hedera_result(result, buffer, sizeof(buffer), &bytes_encoded));
send_response_to_host(buffer, bytes_encoded);
}

bool hedera_get_query(hedera_query_t *query, pb_size_t exp_query_tag) {
evt_status_t event = get_events(EVENT_CONFIG_USB, MAX_INACTIVITY_TIMEOUT);
if (event.p0_event.flag) {
return false;
}
if (!decode_hedera_query(event.usb_event.p_msg, event.usb_event.msg_size, query)) {
return false;
}
if (!check_hedera_query(query, exp_query_tag)) {
return false;
}
return true;
}
34 changes: 34 additions & 0 deletions apps/hedera_app/hedera_api.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* @file hedera_api.h
* @author Cypherock X1 Team
* @brief Header file for Hedera app helper functions.
* @copyright Copyright (c) 2025 HODL TECH PTE LTD
*/
#ifndef HEDERA_API_H
#define HEDERA_API_H

#include <hedera/core.pb.h>
#include <stdint.h>

// API to decode query from host with HEDERA_QUERY_FIELDS
bool decode_hedera_query(const uint8_t *data, uint16_t data_size, hedera_query_t *query_out);

// Encodes the Hedera result with HEDERA_RESULT_FIELDS to byte-stream
bool encode_hedera_result(const hedera_result_t *result, uint8_t *buffer, uint16_t max_buffer_len, size_t *bytes_written_out);

// Checks if the `which_request` field of the query matches the expected tag.
bool check_hedera_query(const hedera_query_t *query, pb_size_t exp_query_tag);

// Returns a zero-initialized hedera_result_t with the specified result tag.
hedera_result_t init_hedera_result(pb_size_t result_tag);

// Sends an error response to the host.
void hedera_send_error(pb_size_t which_error, uint32_t error_code);

// Encodes and sends a result to the host.
void hedera_send_result(const hedera_result_t *result);

// Waits for and decodes a query from the host, ensuring it matches the expected tag.
bool hedera_get_query(hedera_query_t *query, pb_size_t exp_query_tag);

#endif
30 changes: 30 additions & 0 deletions apps/hedera_app/hedera_context.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* @file hedera_context.h
* @author Cypherock X1 Team
* @brief Header file defining typedefs and MACROS for the Hedera app.
* @copyright Copyright (c) 2025 HODL TECH PTE LTD
*/
#ifndef HEDERA_CONTEXT_H
#define HEDERA_CONTEXT_H

#include <stdbool.h>
#include <stdint.h>

#define HEDERA_NAME "Hedera"
#define HEDERA_LUNIT "HBAR"

#define HEDERA_COIN_DEPTH 5

// Derivation path: m/44'/3030'/0'/0'/i'
#define HEDERA_PURPOSE_INDEX (0x80000000 | 44)
#define HEDERA_COIN_INDEX (0x80000000 | 3030)
#define HEDERA_ACCOUNT_INDEX (0x80000000 | 0)
#define HEDERA_CHANGE_INDEX (0x00000000 | 0)

#define HEDERA_PUB_KEY_SIZE 32 // Raw Ed25519 public key
#define HEDERA_ADDRESS_STRING_SIZE (HEDERA_PUB_KEY_SIZE * 2 + 1) // Hex string + null
#define HEDERA_SIGNATURE_SIZE 64 // Raw Ed25519 signature

#define MAX_TXN_SIZE 512 // Maximum size of a serialized transaction body

#endif /* HEDERA_CONTEXT_H */
60 changes: 60 additions & 0 deletions apps/hedera_app/hedera_helpers.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* @file hedera_helpers.c
* @author Cypherock X1 Team
* @brief Utilities specific to the Hedera app.
* @copyright Copyright (c) 2025 HODL TECH PTE LTD
*/
#include "hedera_helpers.h"
#include "coin_utils.h"
#include "hedera_context.h"
#include <stdio.h>
#include <string.h>

bool hedera_derivation_path_guard(const uint32_t *path, uint8_t levels) {
if (levels != HEDERA_COIN_DEPTH) {
return false;
}

// Path must be m/44'/3030'/0'/0'/i'
return (path[0] == HEDERA_PURPOSE_INDEX &&
path[1] == HEDERA_COIN_INDEX &&
path[2] == HEDERA_ACCOUNT_INDEX &&
path[3] == HEDERA_CHANGE_INDEX &&
is_non_hardened(path[4]));
}

void hedera_format_pubkey(const uint8_t *pubkey, char *out_str) {
byte_array_to_hex_string(pubkey, HEDERA_PUB_KEY_SIZE, out_str, HEDERA_ADDRESS_STRING_SIZE);
}

void hedera_format_account_id(const Hedera_AccountID *account_id, char *out_str) {
snprintf(out_str, 40, "%lld.%lld.%lld",
(long long)account_id->shardNum,
(long long)account_id->realmNum,
(long long)account_id->account.accountNum);
}

void hedera_format_tinybars_to_hbar_string(int64_t tinybars, char *out_str) {
char temp_str[30];
const int64_t hbar_div = 100000000;

int sign = (tinybars < 0) ? -1 : 1;
if (tinybars < 0) tinybars = -tinybars;

int64_t whole_part = tinybars / hbar_div;
int64_t frac_part = tinybars % hbar_div;

// Format fractional part with leading zeros
snprintf(temp_str, sizeof(temp_str), "%lld.%08lld", (long long)whole_part, (long long)frac_part);

// Trim trailing zeros
char *end = temp_str + strlen(temp_str) - 1;
while (end > temp_str && *end == '0') {
*end-- = '\0';
}
if (end > temp_str && *end == '.') {
*end = '\0';
}

snprintf(out_str, 40, "%s%s %s", (sign == -1) ? "-" : "", temp_str, HEDERA_LUNIT);
}
27 changes: 27 additions & 0 deletions apps/hedera_app/hedera_helpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* @file hedera_helpers.h
* @author Cypherock X1 Team
* @brief Utilities API definitions for the Hedera app.
* @copyright Copyright (c) 2025 HODL TECH PTE LTD
*/
#ifndef HEDERA_HELPERS_H
#define HEDERA_HELPERS_H

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "proto/basic_types.pb.h"

// Verifies the derivation path for Hedera (m/44'/3030'/0'/0'/i').
bool hedera_derivation_path_guard(const uint32_t *path, uint8_t levels);

// Formats a raw public key into a hex string for display.
void hedera_format_pubkey(const uint8_t *pubkey, char *out_str);

// Formats an AccountID protobuf struct into a human-readable string "shard.realm.num".
void hedera_format_account_id(const Hedera_AccountID *account_id, char *out_str);

// Formats a tinybar amount into an HBAR string with 8 decimal places.
void hedera_format_tinybars_to_hbar_string(int64_t tinybars, char *out_str);

#endif // HEDERA_HELPERS_H
49 changes: 49 additions & 0 deletions apps/hedera_app/hedera_main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* @file hedera_main.c
* @author Cypherock X1 Team
* @brief A common entry point to various Hedera coin actions.
* @copyright Copyright (c) 2025 HODL TECH PTE LTD
*/
#include "hedera_main.h"
#include "hedera_api.h"
#include "hedera_priv.h"
#include "status_api.h"

void hedera_main(usb_event_t usb_evt, const void *hedera_app_config);

static const cy_app_desc_t hedera_app_desc = {
.id = 24, // IMPORTANT: Use an unused app ID here.
.version = {.major = 1, .minor = 0, .patch = 0},
.app = hedera_main,
.app_config = NULL
};

void hedera_main(usb_event_t usb_evt, const void *hedera_app_config) {
hedera_query_t query = HEDERA_QUERY_INIT_DEFAULT;

if (!decode_hedera_query(usb_evt.p_msg, usb_evt.msg_size, &query)) {
return;
}

core_status_set_idle_state(CORE_DEVICE_IDLE_STATE_USB);

switch ((uint8_t)query.which_request) {
case HEDERA_QUERY_GET_PUBLIC_KEYS_TAG:
case HEDERA_QUERY_GET_USER_VERIFIED_PUBLIC_KEY_TAG: {
hedera_get_pub_keys(&query);
break;
}
case HEDERA_QUERY_SIGN_TXN_TAG: {
hedera_sign_transaction(&query);
break;
}
default: {
hedera_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG, ERROR_DATA_FLOW_INVALID_QUERY);
break;
}
}
}

const cy_app_desc_t *get_hedera_app_desc() {
return &hedera_app_desc;
}
16 changes: 16 additions & 0 deletions apps/hedera_app/hedera_main.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* @file hedera_main.h
* @author Cypherock X1 Team
* @brief Header for the main entry point of the Hedera app.
* @copyright Copyright (c) 2025 HODL TECH PTE LTD
*/
#ifndef HEDERA_MAIN_H
#define HEDERA_MAIN_H

#include "app_registry.h"
#include "events.h"

// Returns the config for the Hedera app descriptor.
const cy_app_desc_t *get_hedera_app_desc();

#endif /* HEDERA_MAIN_H */
37 changes: 37 additions & 0 deletions apps/hedera_app/hedera_priv.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* @file hedera_priv.h
* @author Cypherock X1 Team
* @brief Support for Hedera app internal operations.
* @copyright Copyright (c) 2025 HODL TECH PTE LTD
*/
#ifndef HEDERA_PRIV_H
#define HEDERA_PRIV_H

#include "hedera/core.pb.h"
#include "hedera/sign_txn.pb.h"
#include "proto/transaction_body.pb.h" // Nanopb generated header
#include "hedera_context.h"

// Context for the transaction signing flow
typedef struct {
// The structure holds the wallet information of the transaction.
hedera_sign_txn_initiate_request_t init_info;

// Decoded protobuf transaction body for UI display
Hedera_TransactionBody txn;

// Raw serialized transaction bytes received from host. This is what we sign.
uint8_t raw_txn_bytes[MAX_TXN_SIZE];
size_t raw_txn_len;

} hedera_txn_context_t;

/* --- FUNCTION PROTOTYPES --- */

// Handler for public key derivation flows
void hedera_get_pub_keys(hedera_query_t *query);

// Handler for transaction signing flows
void hedera_sign_transaction(hedera_query_t *query);

#endif /* HEDERA_PRIV_H */
Loading
Loading