From 2d542f37d8bc7f0ce4fbae8f44d810aa0e142b1c Mon Sep 17 00:00:00 2001 From: GroM Date: Tue, 19 Nov 2024 15:07:56 +0100 Subject: [PATCH 1/5] Implement Get Challenge comand --- src/apdu_parser.c | 8 +++++++ src/command_dispatcher.c | 3 +++ src/commands.h | 2 ++ src/get_challenge_handler.c | 46 +++++++++++++++++++++++++++++++++++++ src/get_challenge_handler.h | 5 ++++ src/main.c | 4 ++++ src/states.h | 2 ++ 7 files changed, 70 insertions(+) create mode 100644 src/get_challenge_handler.c create mode 100644 src/get_challenge_handler.h diff --git a/src/apdu_parser.c b/src/apdu_parser.c index 54a3fa5d..d65309a1 100644 --- a/src/apdu_parser.c +++ b/src/apdu_parser.c @@ -103,6 +103,14 @@ static uint16_t check_instruction(uint8_t instruction, uint8_t subcommand) { check_current_state = TRANSACTION_RECEIVED; check_subcommand_context = true; break; + case GET_CHALLENGE_COMMAND: + check_current_state = SIGNATURE_CHECKED; + check_subcommand_context = true; + break; + case SEND_TRUSTED_NAME_DESCRIPTOR_COMMAND: + check_current_state = CHALLENGE_SENT; + check_subcommand_context = true; + break; case CHECK_PAYOUT_ADDRESS: case CHECK_ASSET_IN_AND_DISPLAY: case CHECK_ASSET_IN_NO_DISPLAY: diff --git a/src/command_dispatcher.c b/src/command_dispatcher.c index a2624744..d3129872 100644 --- a/src/command_dispatcher.c +++ b/src/command_dispatcher.c @@ -42,6 +42,9 @@ int dispatch_command(const command_t *cmd) { case CHECK_TRANSACTION_SIGNATURE_COMMAND: ret = check_tx_signature(cmd); break; + case GET_CHALLENGE_COMMAND: + ret = get_challenge_handler(); + break; case CHECK_PAYOUT_ADDRESS: case CHECK_ASSET_IN_AND_DISPLAY: case CHECK_ASSET_IN_NO_DISPLAY: diff --git a/src/commands.h b/src/commands.h index f6f33f1c..1d8ca4e9 100644 --- a/src/commands.h +++ b/src/commands.h @@ -13,6 +13,8 @@ typedef enum { CHECK_PARTNER_COMMAND = 0x05, PROCESS_TRANSACTION_RESPONSE_COMMAND = 0x06, CHECK_TRANSACTION_SIGNATURE_COMMAND = 0x07, + GET_CHALLENGE_COMMAND = 0x10, + SEND_TRUSTED_NAME_DESCRIPTOR_COMMAND = 0x11, CHECK_PAYOUT_ADDRESS = 0x08, CHECK_ASSET_IN_LEGACY_AND_DISPLAY = 0x08, // Same ID as CHECK_PAYOUT_ADDRESS, deprecated CHECK_ASSET_IN_AND_DISPLAY = 0x0B, // Do note the 0x0B diff --git a/src/get_challenge_handler.c b/src/get_challenge_handler.c new file mode 100644 index 00000000..cb8f7559 --- /dev/null +++ b/src/get_challenge_handler.c @@ -0,0 +1,46 @@ +#include +#include "get_challenge_handler.h" +#include "io.h" + +static uint32_t challenge; + +#define LAST_BYTE_MASK 0x000000FF + +/** + * Generate a new challenge from the Random Number Generator + */ +void roll_challenge(void) { +#ifdef HAVE_TRUSTED_NAME_TEST + challenge = 0xdeadbeef; +#else + challenge = cx_rng_u32(); +#endif +} + +/** + * Get the current challenge + * + * @return challenge + */ +uint32_t get_challenge(void) { + return challenge; +} + +/** + * Send back the current challenge + */ +int get_challenge_handler(void) { + PRINTF("New challenge -> %u\n", challenge); + uint8_t output_buffer[6]; + output_buffer[0] = (uint8_t)((challenge >> 24) & LAST_BYTE_MASK); + output_buffer[1] = (uint8_t)((challenge >> 16) & LAST_BYTE_MASK); + output_buffer[2] = (uint8_t)((challenge >> 8) & LAST_BYTE_MASK); + output_buffer[3] = (uint8_t)(challenge & LAST_BYTE_MASK); + output_buffer[4] = 0x90; + output_buffer[5] = 0x00; + if (send_apdu(output_buffer, sizeof(output_buffer)) < 0) { + return -1; + } + return 0; + +} \ No newline at end of file diff --git a/src/get_challenge_handler.h b/src/get_challenge_handler.h new file mode 100644 index 00000000..abe46eda --- /dev/null +++ b/src/get_challenge_handler.h @@ -0,0 +1,5 @@ +#pragma once + +void roll_challenge(void); +uint32_t get_challenge(void); +int get_challenge_handler(void); \ No newline at end of file diff --git a/src/main.c b/src/main.c index 7a1263d9..b370104a 100644 --- a/src/main.c +++ b/src/main.c @@ -29,6 +29,7 @@ #include "swap_errors.h" #include "apdu_parser.h" #include "sign_result.h" +#include "get_challenge_handler.h" #include "usbd_core.h" @@ -155,6 +156,9 @@ __attribute__((section(".boot"))) int main(__attribute__((unused)) int arg0) { BLE_power(1, NULL); #endif + // to prevent it from having a fixed value at boot + roll_challenge(); + app_main(); } CATCH(SWO_SEC_APP_14) { diff --git a/src/states.h b/src/states.h index f9fed247..42d2ddef 100644 --- a/src/states.h +++ b/src/states.h @@ -7,6 +7,8 @@ typedef enum { PROVIDER_CHECKED, TRANSACTION_RECEIVED, SIGNATURE_CHECKED, + CHALLENGE_SENT, + TRUSTED_NAME_DESCRIPTOR_RECEIVED, PAYOUT_ADDRESS_CHECKED, ALL_ADDRESSES_CHECKED, WAITING_USER_VALIDATION, From 32047edd0659afa292794381d08eee4ed40da17a Mon Sep 17 00:00:00 2001 From: GroM Date: Tue, 19 Nov 2024 15:26:14 +0100 Subject: [PATCH 2/5] Fixes after rebase against develop branch --- src/command_dispatcher.c | 1 + src/get_challenge_handler.c | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/command_dispatcher.c b/src/command_dispatcher.c index d3129872..50fcfa99 100644 --- a/src/command_dispatcher.c +++ b/src/command_dispatcher.c @@ -12,6 +12,7 @@ #include "start_signing_transaction.h" #include "check_addresses_and_amounts.h" #include "prompt_ui_display.h" +#include "get_challenge_handler.h" #include "io.h" diff --git a/src/get_challenge_handler.c b/src/get_challenge_handler.c index cb8f7559..2c51cffb 100644 --- a/src/get_challenge_handler.c +++ b/src/get_challenge_handler.c @@ -1,5 +1,7 @@ #include #include "get_challenge_handler.h" +#include "swap_errors.h" +#include "buffer.h" #include "io.h" static uint32_t challenge; @@ -31,14 +33,18 @@ uint32_t get_challenge(void) { */ int get_challenge_handler(void) { PRINTF("New challenge -> %u\n", challenge); - uint8_t output_buffer[6]; + uint8_t output_buffer[4]; output_buffer[0] = (uint8_t)((challenge >> 24) & LAST_BYTE_MASK); output_buffer[1] = (uint8_t)((challenge >> 16) & LAST_BYTE_MASK); output_buffer[2] = (uint8_t)((challenge >> 8) & LAST_BYTE_MASK); output_buffer[3] = (uint8_t)(challenge & LAST_BYTE_MASK); - output_buffer[4] = 0x90; - output_buffer[5] = 0x00; - if (send_apdu(output_buffer, sizeof(output_buffer)) < 0) { + + buffer_t output; + output.ptr = output_buffer; + output.size = 4; + output.offset = 0; + + if (io_send_response_buffers(&output, 1, SUCCESS) < 0) { return -1; } return 0; From 7e11628af77e80a08588f20d08062c1e07d7e79d Mon Sep 17 00:00:00 2001 From: GroM Date: Tue, 19 Nov 2024 18:03:38 +0100 Subject: [PATCH 3/5] Add Trusted Name descriptor support --- src/apdu_parser.c | 4 +- src/command_dispatcher.c | 6 +- src/commands.h | 4 +- src/get_challenge_handler.c | 13 +- src/trusted_name_descriptor_handler.c | 876 ++++++++++++++++++++++++++ src/trusted_name_descriptor_handler.h | 10 + 6 files changed, 903 insertions(+), 10 deletions(-) create mode 100644 src/trusted_name_descriptor_handler.c create mode 100644 src/trusted_name_descriptor_handler.h diff --git a/src/apdu_parser.c b/src/apdu_parser.c index d65309a1..dc1366aa 100644 --- a/src/apdu_parser.c +++ b/src/apdu_parser.c @@ -103,11 +103,11 @@ static uint16_t check_instruction(uint8_t instruction, uint8_t subcommand) { check_current_state = TRANSACTION_RECEIVED; check_subcommand_context = true; break; - case GET_CHALLENGE_COMMAND: + case GET_CHALLENGE: check_current_state = SIGNATURE_CHECKED; check_subcommand_context = true; break; - case SEND_TRUSTED_NAME_DESCRIPTOR_COMMAND: + case SEND_TRUSTED_NAME_DESCRIPTOR: check_current_state = CHALLENGE_SENT; check_subcommand_context = true; break; diff --git a/src/command_dispatcher.c b/src/command_dispatcher.c index 50fcfa99..1a769b93 100644 --- a/src/command_dispatcher.c +++ b/src/command_dispatcher.c @@ -13,6 +13,7 @@ #include "check_addresses_and_amounts.h" #include "prompt_ui_display.h" #include "get_challenge_handler.h" +#include "trusted_name_descriptor_handler.h" #include "io.h" @@ -43,9 +44,12 @@ int dispatch_command(const command_t *cmd) { case CHECK_TRANSACTION_SIGNATURE_COMMAND: ret = check_tx_signature(cmd); break; - case GET_CHALLENGE_COMMAND: + case GET_CHALLENGE: ret = get_challenge_handler(); break; + case SEND_TRUSTED_NAME_DESCRIPTOR: + ret = trusted_name_descriptor_handler(cmd); + break; case CHECK_PAYOUT_ADDRESS: case CHECK_ASSET_IN_AND_DISPLAY: case CHECK_ASSET_IN_NO_DISPLAY: diff --git a/src/commands.h b/src/commands.h index 1d8ca4e9..26fcf7fb 100644 --- a/src/commands.h +++ b/src/commands.h @@ -13,8 +13,8 @@ typedef enum { CHECK_PARTNER_COMMAND = 0x05, PROCESS_TRANSACTION_RESPONSE_COMMAND = 0x06, CHECK_TRANSACTION_SIGNATURE_COMMAND = 0x07, - GET_CHALLENGE_COMMAND = 0x10, - SEND_TRUSTED_NAME_DESCRIPTOR_COMMAND = 0x11, + GET_CHALLENGE = 0x10, + SEND_TRUSTED_NAME_DESCRIPTOR = 0x11, CHECK_PAYOUT_ADDRESS = 0x08, CHECK_ASSET_IN_LEGACY_AND_DISPLAY = 0x08, // Same ID as CHECK_PAYOUT_ADDRESS, deprecated CHECK_ASSET_IN_AND_DISPLAY = 0x0B, // Do note the 0x0B diff --git a/src/get_challenge_handler.c b/src/get_challenge_handler.c index 2c51cffb..55b6114c 100644 --- a/src/get_challenge_handler.c +++ b/src/get_challenge_handler.c @@ -3,6 +3,7 @@ #include "swap_errors.h" #include "buffer.h" #include "io.h" +#include "globals.h" static uint32_t challenge; @@ -34,10 +35,10 @@ uint32_t get_challenge(void) { int get_challenge_handler(void) { PRINTF("New challenge -> %u\n", challenge); uint8_t output_buffer[4]; - output_buffer[0] = (uint8_t)((challenge >> 24) & LAST_BYTE_MASK); - output_buffer[1] = (uint8_t)((challenge >> 16) & LAST_BYTE_MASK); - output_buffer[2] = (uint8_t)((challenge >> 8) & LAST_BYTE_MASK); - output_buffer[3] = (uint8_t)(challenge & LAST_BYTE_MASK); + output_buffer[0] = (uint8_t) ((challenge >> 24) & LAST_BYTE_MASK); + output_buffer[1] = (uint8_t) ((challenge >> 16) & LAST_BYTE_MASK); + output_buffer[2] = (uint8_t) ((challenge >> 8) & LAST_BYTE_MASK); + output_buffer[3] = (uint8_t) (challenge & LAST_BYTE_MASK); buffer_t output; output.ptr = output_buffer; @@ -47,6 +48,8 @@ int get_challenge_handler(void) { if (io_send_response_buffers(&output, 1, SUCCESS) < 0) { return -1; } - return 0; + G_swap_ctx.state = CHALLENGE_SENT; + + return 0; } \ No newline at end of file diff --git a/src/trusted_name_descriptor_handler.c b/src/trusted_name_descriptor_handler.c new file mode 100644 index 00000000..29551d0c --- /dev/null +++ b/src/trusted_name_descriptor_handler.c @@ -0,0 +1,876 @@ +#include +#include +#include +#include + +#include "globals.h" +#include "trusted_name_descriptor_handler.h" +#include "get_challenge_handler.h" +#include "io_helpers.h" + +#include "os_pki.h" +#include "cx.h" + +#define TYPE_ADDRESS 0x06 +#define TYPE_DYN_RESOLVER 0x06 + +#define STRUCT_TYPE_TRUSTED_NAME 0x03 +#define ALGO_SECP256K1 1 + +#define DER_LONG_FORM_FLAG 0x80 // 8th bit set +#define DER_FIRST_BYTE_VALUE_MASK 0x7f + +#define INT256_LENGTH 32 + +typedef enum { TLV_TAG, TLV_LENGTH, TLV_VALUE } e_tlv_step; + +// This enum needs to be ordered the same way as the e_tlv_tag one ! +typedef enum { + STRUCT_TYPE_RCV_BIT = 0, + STRUCT_VERSION_RCV_BIT, + TRUSTED_NAME_TYPE_RCV_BIT, + TRUSTED_NAME_SOURCE_RCV_BIT, + TRUSTED_NAME_RCV_BIT, + CHAIN_ID_RCV_BIT, + ADDRESS_RCV_BIT, + SOURCE_CONTRACT_RCV_BIT, + CHALLENGE_RCV_BIT, + SIGNER_KEY_ID_RCV_BIT, + SIGNER_ALGO_RCV_BIT, + SIGNATURE_RCV_BIT, +} e_tlv_rcv_bit; + +#define RCV_FLAG(a) (1 << a) + +/* +trusted_name_descriptor = tlv(TAG_STRUCTURE_TYPE, u8(TYPE_TRUSTED_NAME)) 3 bytes + + & tlv(TAG_VERSION, u8(0x02)) 3 bytes + + & tlv(TAG_TRUSTED_NAME_TYPE, 0x06) 3 bytes + + & tlv(TAG_TRUSTED_NAME_SOURCE, 0x06) 3 bytes + + & tlv(TAG_TRUSTED_NAME, trusted_name) 2 + 44 bytes + + & tlv(TAG_CHAIN_ID, chain_id) 2 + 8 bytes + + & tlv(TAG_ADDRESS, address) 2 + 44 bytes + + & tlv(TAG_SOURCE_CONTRACT, source_contract)* 2 + 44 bytes + + & tlv(TAG_CHALLENGE, challenge) 2 + 4 bytes + + & tlv(TAG_SIGNER_KEY_ID, key_id) 2 + 2 bytes + + & tlv(TAG_SIGNER_ALGORITHM, signature_algorithm) 2 + 1 byte + + & tlv(TAG_SIGNATURE, signature(~,~)) 2 + 72 bytes => 247 + +T L V +01 01 03 TAG_STRUCTURE_TYPE +02 01 02 TAG_VERSION +70 01 06 TAG_TRUSTED_NAME_TYPE +71 01 06 TAG_TRUSTED_NAME_SOURCE +20 20 276497ba0bb8659172b72edd8c66e18f561764d9c86a610a3a7e0f79c0baf9db TAG_TRUSTED_NAME +23 01 65 TAG_CHAIN_ID +22 20 606501b302e1801892f80a2979f585f8855d0f2034790a2455f744fac503d7b5 TAG_ADDRESS +73 20 c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61 TAG_SOURCE_CONTRACT +12 04 deadbeef TAG_CHALLENGE +13 01 03 TAG_SIGNER_KEY_ID +14 01 01 TAG_SIGNER_ALGORITHM +15 47 30..2e + + +01 01 03 +02 01 02 +70 01 06 +71 01 06 +20 20 276497BA0BB8659172B72EDD8C66E18F561764D9C86A610A3A7E0F79C0BAF9DB +23 01 65 +22 20 606501B302E1801892F80A2979F585F8855D0F2034790A2455F744FAC503D7B5 +73 20 C6FA7AF3BEDBAD3A3D65F36AABC97431B1BBE4C2D2F6E0E47CA60203452F5D61 +12 04 DEADBEEF +13 01 00 +14 01 01 +15 47 30..CF + +*/ + +#define TLV_BUFFER_LENGTH 255 /* >= 247, APDU max payload */ +static uint8_t tlv_buffer[TLV_BUFFER_LENGTH] = {0}; + +typedef enum { + STRUCT_TYPE = 0x01, + STRUCT_VERSION = 0x02, + TRUSTED_NAME_TYPE = 0x70, + TRUSTED_NAME_SOURCE = 0x71, + TRUSTED_NAME = 0x20, + CHAIN_ID = 0x23, + ADDRESS = 0x22, + SOURCE_CONTRACT = 0x73, + CHALLENGE = 0x12, + SIGNER_KEY_ID = 0x13, + SIGNER_ALGO = 0x14, + SIGNATURE = 0x15, +} e_tlv_tag; + +typedef enum { KEY_ID_TEST = 0x00, KEY_ID_PROD = 0x03 } e_key_id; + +typedef struct { + uint8_t *buf; + uint8_t size; + uint8_t expected_size; +} s_tlv_payload; + +typedef struct { + e_tlv_tag tag; + uint8_t length; + const uint8_t *value; +} s_tlv_data; + +typedef struct { + uint32_t rcv_flags; + bool valid; + uint8_t struct_version; + uint8_t token_account[MAX_ADDRESS_LENGTH + 1]; + uint8_t *owner; + uint8_t spl_token[MAX_ADDRESS_LENGTH + 1]; + uint64_t chain_id; + uint8_t name_type; + uint8_t name_source; +} s_trusted_name_info; + +typedef struct { + e_key_id key_id; + uint8_t input_sig_size; + const uint8_t *input_sig; + cx_sha256_t hash_ctx; +} s_sig_ctx; + +typedef bool(t_tlv_handler)(const s_tlv_data *data, + s_trusted_name_info *trusted_name_info, + s_sig_ctx *sig_ctx); + +typedef struct { + e_tlv_tag tag; + t_tlv_handler *func; + e_tlv_rcv_bit rcv_bit; +} s_tlv_handler; + +static s_tlv_payload g_tlv_payload = {0}; +static s_trusted_name_info g_trusted_name_info = {0}; + +uint8_t g_trusted_token_account_owner_pubkey[MAX_ADDRESS_LENGTH + 1] = {0}; +bool g_trusted_token_account_owner_pubkey_set = false; + +/** + * Get uint from tlv data + * + * Get an unsigned integer from variable length tlv data (up to 4 bytes) + * + * @param[in] data tlv data + * @param[out] value the returned value + * @return whether it was successful + */ +static bool get_uint_from_data(const s_tlv_data *data, uint32_t *value) { + uint8_t size_diff; + uint8_t buffer[sizeof(uint32_t)]; + + if (data->length > sizeof(buffer)) { + PRINTF("Unexpectedly long value (%u bytes) for tag 0x%x\n", data->length, data->tag); + return false; + } + size_diff = sizeof(buffer) - data->length; + memset(buffer, 0, size_diff); + memcpy(buffer + size_diff, data->value, data->length); + *value = U4BE(buffer, 0); + return true; +} + +/** + * Handler for tag \ref STRUCT_TYPE + * + * @param[in] data the tlv data + * @param[] trusted_name_info the trusted name information + * @param[] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_struct_type(const s_tlv_data *data, + s_trusted_name_info *trusted_name_info, + s_sig_ctx *sig_ctx) { + uint32_t value; + + (void) trusted_name_info; + (void) sig_ctx; + if (!get_uint_from_data(data, &value)) { + return false; + } + return (value == STRUCT_TYPE_TRUSTED_NAME); +} + +/** + * Handler for tag \ref STRUCT_VERSION + * + * @param[in] data the tlv data + * @param[out] trusted_name_info the trusted name information + * @param[] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_struct_version(const s_tlv_data *data, + s_trusted_name_info *trusted_name_info, + s_sig_ctx *sig_ctx) { + uint32_t value; + + (void) sig_ctx; + if (!get_uint_from_data(data, &value) || (value > UINT8_MAX)) { + return false; + } + trusted_name_info->struct_version = value; + return true; +} + +/** + * Handler for tag \ref CHALLENGE + * + * @param[in] data the tlv data + * @param[] trusted_name_info the trusted name information + * @param[] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_challenge(const s_tlv_data *data, + s_trusted_name_info *trusted_name_info, + s_sig_ctx *sig_ctx) { + uint32_t value; + (void) trusted_name_info; + (void) sig_ctx; + + if (!get_uint_from_data(data, &value)) { + return false; + } + return (value == get_challenge()); +} + +/** + * Handler for tag \ref SIGNER_KEY_ID + * + * @param[in] data the tlv data + * @param[] trusted_name_info the trusted name information + * @param[out] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_sign_key_id(const s_tlv_data *data, + s_trusted_name_info *trusted_name_info, + s_sig_ctx *sig_ctx) { + uint32_t value; + (void) trusted_name_info; + + if (!get_uint_from_data(data, &value) || (value > UINT8_MAX)) { + return false; + } + sig_ctx->key_id = value; + return true; +} + +/** + * Handler for tag \ref SIGNER_ALGO + * + * @param[in] data the tlv data + * @param[] trusted_name_info the trusted name information + * @param[] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_sign_algo(const s_tlv_data *data, + s_trusted_name_info *trusted_name_info, + s_sig_ctx *sig_ctx) { + uint32_t value; + + (void) trusted_name_info; + (void) sig_ctx; + if (!get_uint_from_data(data, &value)) { + return false; + } + return (value == ALGO_SECP256K1); +} + +/** + * Handler for tag \ref SIGNATURE + * + * @param[in] data the tlv data + * @param[] trusted_name_info the trusted name information + * @param[out] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_signature(const s_tlv_data *data, + s_trusted_name_info *trusted_name_info, + s_sig_ctx *sig_ctx) { + (void) trusted_name_info; + sig_ctx->input_sig_size = data->length; + sig_ctx->input_sig = data->value; + return true; +} + +/** + * Handler for tag \ref SOURCE_CONTRACT + * + * @param[in] data the tlv data + * @param[] trusted_name_info the trusted name information + * @param[out] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_source_contract(const s_tlv_data *data, + s_trusted_name_info *trusted_name_info, + s_sig_ctx *sig_ctx) { + (void) sig_ctx; + if (data->length > MAX_ADDRESS_LENGTH) { + PRINTF("SPL Token address too long! (%u)\n", data->length); + return false; + } + + memcpy(trusted_name_info->spl_token, data->value, data->length); + + trusted_name_info->spl_token[data->length] = '\0'; + return true; +} + +/** + * Tests if the given account name character is valid (in our subset of allowed characters) + * + * @param[in] c given character + * @return whether the character is valid + */ +/*static bool is_valid_account_character(char c) { + if (isalpha((int) c)) { + if (!islower((int) c)) { + return false; + } + } else if (!isdigit((int) c)) { + switch (c) { + case '.': + case '-': + case '_': + break; + default: + return false; + } + } + return true; +}*/ + +/** + * Handler for tag \ref TRUSTED_NAME + * + * @param[in] data the tlv data + * @param[out] trusted_name_info the trusted name information + * @param[] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_trusted_name(const s_tlv_data *data, + s_trusted_name_info *trusted_name_info, + s_sig_ctx *sig_ctx) { + (void) sig_ctx; + if (data->length > MAX_ADDRESS_LENGTH) { + PRINTF("Token Account address too long! (%u)\n", data->length); + return false; + } + + memcpy(trusted_name_info->token_account, data->value, data->length); + + trusted_name_info->token_account[data->length] = '\0'; + return true; +} + +/** + * Handler for tag \ref ADDRESS + * + * @param[in] data the tlv data + * @param[out] trusted_name_info the trusted name information + * @param[] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_address(const s_tlv_data *data, + s_trusted_name_info *trusted_name_info, + s_sig_ctx *sig_ctx) { + (void) sig_ctx; + if (data->length > MAX_ADDRESS_LENGTH) { + PRINTF("Address too long! (%u)\n", data->length); + return false; + } + memcpy(trusted_name_info->owner, data->value, data->length); + trusted_name_info->owner[data->length] = '\0'; + return true; +} + +/** + * Handler for tag \ref CHAIN_ID + * + * @param[in] data the tlv data + * @param[out] trusted_name_info the trusted name information + * @param[] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_chain_id(const s_tlv_data *data, + s_trusted_name_info *trusted_name_info, + s_sig_ctx *sig_ctx) { + (void) sig_ctx; + bool res = false; + + switch (data->length) { + case 1: { + trusted_name_info->chain_id = data->value[0]; + res = true; + break; + } + case 2: { + trusted_name_info->chain_id = (data->value[0] << 8) | data->value[1]; + res = true; + break; + } + default: + PRINTF("Error while parsing chain ID: length = %d\n", data->length); + } + return res; +} + +/** + * Handler for tag \ref TRUSTED_NAME_TYPE + * + * @param[in] data the tlv data + * @param[out] trusted_name_info the trusted name information + * @param[] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_trusted_name_type(const s_tlv_data *data, + s_trusted_name_info *trusted_name_info, + s_sig_ctx *sig_ctx) { + uint32_t value; + + (void) trusted_name_info; + (void) sig_ctx; + if (!get_uint_from_data(data, &value) || (value > UINT8_MAX)) { + return false; + } + + if (value != TYPE_ADDRESS) { + PRINTF("Error: unsupported trusted name type (%u)!\n", value); + return false; + } + trusted_name_info->name_type = value; + return true; +} + +/** + * Handler for tag \ref TRUSTED_NAME_SOURCE + * + * @param[in] data the tlv data + * @param[out] trusted_name_info the trusted name information + * @param[] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_trusted_name_source(const s_tlv_data *data, + s_trusted_name_info *trusted_name_info, + s_sig_ctx *sig_ctx) { + uint32_t value; + + (void) trusted_name_info; + (void) sig_ctx; + if (!get_uint_from_data(data, &value) || (value > UINT8_MAX)) { + return false; + } + + if (value != TYPE_DYN_RESOLVER) { + PRINTF("Error: unsupported trusted name source (%u)!\n", value); + return false; + } + + trusted_name_info->name_source = value; + return true; +} + +static int check_signature_with_pubkey(uint8_t *buffer, + const uint8_t bufLen, + const uint8_t keyUsageExp, + uint8_t *signature, + const uint8_t sigLen) { + cx_err_t error = CX_INTERNAL_ERROR; + uint8_t key_usage = 0; + size_t trusted_name_len = 0; + uint8_t trusted_name[CERTIFICATE_TRUSTED_NAME_MAXLEN] = {0}; + cx_ecfp_384_public_key_t public_key = {0}; + + PRINTF( + "=======================================================================================\n"); + + error = os_pki_get_info(&key_usage, trusted_name, &trusted_name_len, &public_key); + if ((error == 0) && (key_usage == keyUsageExp)) { + PRINTF("[%s] Certificate '%s' loaded for usage 0x%x \n", + tag, + trusted_name, + key_usage); + + // Checking the signature with PKI + if (!os_pki_verify(buffer, bufLen, signature, sigLen)) { + PRINTF("%s: Invalid signature\n", tag); + error = CX_INTERNAL_ERROR; + goto end; + } + } else { + PRINTF( + "********** Issue when loading PKI certificate, cannot check signature " + "**********\n"); + goto end; + } + error = CX_OK; +end: + return error; +} + +/** + * Verify the signature context + * + * Verify the SHA-256 hash of the payload against the public key + * + * @param[in] sig_ctx the signature context + * @return whether it was successful + */ +static bool verify_signature(const s_sig_ctx *sig_ctx) { + uint8_t hash[INT256_LENGTH]; + cx_err_t error = CX_INTERNAL_ERROR; + +#ifdef HAVE_TRUSTED_NAME_TEST + e_key_id valid_key_id = KEY_ID_TEST; +#else + e_key_id valid_key_id = KEY_ID_PROD; +#endif + bool ret_code = false; + + if (sig_ctx->key_id != valid_key_id) { + PRINTF("Error: Unknown metadata key ID %u\n", sig_ctx->key_id); + return false; + } + + CX_CHECK( + cx_hash_no_throw((cx_hash_t *) &sig_ctx->hash_ctx, CX_LAST, NULL, 0, hash, INT256_LENGTH)); + + CX_CHECK(check_signature_with_pubkey(hash, + sizeof(hash), + CERTIFICATE_PUBLIC_KEY_USAGE_TRUSTED_NAME, + (uint8_t *) (sig_ctx->input_sig), + sig_ctx->input_sig_size)); + + ret_code = true; +end: + return ret_code; +} + +/** + * Calls the proper handler for the given TLV data + * + * Checks if there is a proper handler function for the given TLV tag and then calls it + * + * @param[in] handlers list of tag / handler function pairs + * @param[in] handler_count number of handlers + * @param[in] data the TLV data + * @param[out] trusted_name_info the trusted name information + * @param[out] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_tlv_data(s_tlv_handler *handlers, + int handler_count, + const s_tlv_data *data, + s_trusted_name_info *trusted_name_info, + s_sig_ctx *sig_ctx) { + t_tlv_handler *fptr; + + // check if a handler exists for this tag + for (int idx = 0; idx < handler_count; ++idx) { + if (handlers[idx].tag == data->tag) { + trusted_name_info->rcv_flags |= RCV_FLAG(handlers[idx].rcv_bit); + fptr = PIC(handlers[idx].func); + if (!(*fptr)(data, trusted_name_info, sig_ctx)) { + PRINTF("Error while handling tag 0x%x\n", handlers[idx].tag); + return false; + } + break; + } + } + return true; +} + +/** + * Verify the validity of the received trusted struct + * + * @param[in] trusted_name_info the trusted name information + * @return whether the struct is valid + */ +static bool verify_struct(const s_trusted_name_info *trusted_name_info) { + uint32_t required_flags; + + if (!(RCV_FLAG(STRUCT_VERSION_RCV_BIT) & trusted_name_info->rcv_flags)) { + PRINTF("Error: no struct version specified!\n"); + return false; + } + required_flags = RCV_FLAG(STRUCT_TYPE_RCV_BIT) | RCV_FLAG(STRUCT_VERSION_RCV_BIT) | + RCV_FLAG(TRUSTED_NAME_TYPE_RCV_BIT) | RCV_FLAG(TRUSTED_NAME_SOURCE_RCV_BIT) | + RCV_FLAG(TRUSTED_NAME_RCV_BIT) | RCV_FLAG(CHAIN_ID_RCV_BIT) | + RCV_FLAG(ADDRESS_RCV_BIT) | RCV_FLAG(SOURCE_CONTRACT_RCV_BIT) | + RCV_FLAG(CHALLENGE_RCV_BIT) | RCV_FLAG(SIGNER_KEY_ID_RCV_BIT) | + RCV_FLAG(SIGNER_ALGO_RCV_BIT) | RCV_FLAG(SIGNATURE_RCV_BIT); + + switch (trusted_name_info->struct_version) { + case 2: + if ((trusted_name_info->rcv_flags & required_flags) != required_flags) { + PRINTF("Error: missing required fields in struct version 2\n"); + return false; + } + switch (trusted_name_info->name_type) { + case TYPE_ADDRESS: + if (trusted_name_info->name_source != TYPE_DYN_RESOLVER) { + PRINTF("Error: unsupported trusted name source (%u)!\n", + trusted_name_info->name_source); + return false; + } + break; + default: + PRINTF("Error: unsupported trusted name type (%u)!\n", + trusted_name_info->name_type); + return false; + } + break; + default: + PRINTF("Error: unsupported trusted name struct version (%u) !\n", + trusted_name_info->struct_version); + return false; + } + return true; +} + +/** Parse DER-encoded value + * + * Parses a DER-encoded value (up to 4 bytes long) + * https://en.wikipedia.org/wiki/X.690 + * + * @param[in] payload the TLV payload + * @param[in,out] offset the payload offset + * @param[out] value the parsed value + * @return whether it was successful + */ +static bool parse_der_value(const s_tlv_payload *payload, size_t *offset, uint32_t *value) { + bool ret = false; + uint8_t byte_length; + uint8_t buf[sizeof(*value)]; + + if (value != NULL) { + if (payload->buf[*offset] & DER_LONG_FORM_FLAG) { // long form + byte_length = payload->buf[*offset] & DER_FIRST_BYTE_VALUE_MASK; + *offset += 1; + if ((*offset + byte_length) > payload->size) { + PRINTF("TLV payload too small for DER encoded value\n"); + } else { + if (byte_length > sizeof(buf) || byte_length == 0) { + PRINTF("Unexpectedly long DER-encoded value (%u bytes)\n", byte_length); + } else { + memset(buf, 0, (sizeof(buf) - byte_length)); + memcpy(buf + (sizeof(buf) - byte_length), &payload->buf[*offset], byte_length); + *value = U4BE(buf, 0); + *offset += byte_length; + ret = true; + } + } + } else { // short form + *value = payload->buf[*offset]; + *offset += 1; + ret = true; + } + } + return ret; +} + +/** + * Get DER-encoded value as an uint8 + * + * Parses the value and checks if it fits in the given \ref uint8_t value + * + * @param[in] payload the TLV payload + * @param[in,out] offset + * @param[out] value the parsed value + * @return whether it was successful + */ +static bool get_der_value_as_uint8(const s_tlv_payload *payload, size_t *offset, uint8_t *value) { + bool ret = false; + uint32_t tmp_value; + + if (value != NULL) { + if (!parse_der_value(payload, offset, &tmp_value)) { + } else { + if (tmp_value <= UINT8_MAX) { + *value = tmp_value; + ret = true; + } else { + PRINTF("TLV DER-encoded value larger than 8 bits\n"); + } + } + } + return ret; +} + +/** + * Parse the TLV payload + * + * Does the TLV parsing but also the SHA-256 hash of the payload. + * + * @param[in] payload the raw TLV payload + * @param[out] trusted_name_info the trusted name information + * @param[out] sig_ctx the signature context + * @return whether it was successful + */ +static bool parse_tlv(const s_tlv_payload *payload, + s_trusted_name_info *trusted_name_info, + s_sig_ctx *sig_ctx) { + s_tlv_handler handlers[] = { + {.tag = STRUCT_TYPE, .func = &handle_struct_type}, + {.tag = STRUCT_VERSION, .func = &handle_struct_version}, + {.tag = TRUSTED_NAME_TYPE, .func = &handle_trusted_name_type}, + {.tag = TRUSTED_NAME_SOURCE, .func = &handle_trusted_name_source}, + {.tag = TRUSTED_NAME, .func = &handle_trusted_name}, + {.tag = CHAIN_ID, .func = &handle_chain_id}, + {.tag = ADDRESS, .func = &handle_address}, + {.tag = SOURCE_CONTRACT, .func = &handle_source_contract}, + {.tag = CHALLENGE, .func = &handle_challenge}, + {.tag = SIGNER_KEY_ID, .func = &handle_sign_key_id}, + {.tag = SIGNER_ALGO, .func = &handle_sign_algo}, + {.tag = SIGNATURE, .func = &handle_signature}, + }; + e_tlv_step step = TLV_TAG; + s_tlv_data data; + size_t offset = 0; + size_t tag_start_off; + + for (size_t i = 0; i < ARRAYLEN(handlers); ++i) handlers[i].rcv_bit = i; + cx_sha256_init(&sig_ctx->hash_ctx); + // handle TLV payload + while (offset < payload->size) { + switch (step) { + case TLV_TAG: + tag_start_off = offset; + if (!get_der_value_as_uint8(payload, &offset, &data.tag)) { + return false; + } + step = TLV_LENGTH; + break; + + case TLV_LENGTH: + if (!get_der_value_as_uint8(payload, &offset, &data.length)) { + return false; + } + step = TLV_VALUE; + break; + + case TLV_VALUE: + if ((offset + data.length) > payload->size) { + PRINTF("Error: value would go beyond the TLV payload!\n"); + return false; + } + data.value = &payload->buf[offset]; + if (!handle_tlv_data(handlers, + (sizeof(handlers) / sizeof(handlers[0])), + &data, + trusted_name_info, + sig_ctx)) { + return false; + } + offset += data.length; + if (data.tag != SIGNATURE) { // the signature wasn't computed on itself + CX_ASSERT(cx_hash_no_throw((cx_hash_t *) &sig_ctx->hash_ctx, + 0, + &payload->buf[tag_start_off], + (offset - tag_start_off), + NULL, + 0)); + } + step = TLV_TAG; + break; + + default: + return false; + } + } + if (step != TLV_TAG) { + PRINTF("Error: unexpected data at the end of the TLV payload!\n"); + return false; + } + return verify_struct(trusted_name_info); +} + +/** + * Deallocate and unassign TLV payload + * + * @param[in] payload payload structure + */ +static void free_payload(s_tlv_payload *payload) { + memset(tlv_buffer, 0, sizeof(tlv_buffer)); + memset(payload, 0, sizeof(*payload)); +} + +static bool init_tlv_payload(uint8_t length, s_tlv_payload *payload) { + // check if no payload is already in memory + if (payload->buf != NULL) { + free_payload(payload); + return false; + } + + payload->buf = tlv_buffer; + payload->expected_size = length; + + return true; +} + +/** + * Handle provide trusted info APDU + * + * @param[in] is_first_chunk first APDU instruction parameter + * @param[in] data APDU payload + * @param[in] length payload size + */ +int trusted_name_descriptor_handler(const command_t *cmd) { + s_sig_ctx sig_ctx; + + uint8_t *data = cmd->data.bytes; + uint8_t data_length = cmd->data.size; + + PRINTF("Received chunk of trusted info, length = %d\n", data_length); + if (!init_tlv_payload(data_length, &g_tlv_payload)) { + free_payload(&g_tlv_payload); + PRINTF("Error while initializing TLV payload\n"); + return reply_error(INTERNAL_ERROR); + } + + PRINTF("Expected size of trusted info: %d\n", g_tlv_payload.expected_size); + + if ((g_tlv_payload.size + data_length) > g_tlv_payload.expected_size) { + free_payload(&g_tlv_payload); + PRINTF("TLV payload size mismatch!\n"); + return reply_error(INTERNAL_ERROR); + } + // feed into tlv payload + memcpy(g_tlv_payload.buf + g_tlv_payload.size, data, data_length); + g_tlv_payload.size += data_length; + + PRINTF("Received %d bytes of trusted info\n", g_tlv_payload.size); + + // everything has been received + if (g_tlv_payload.size == g_tlv_payload.expected_size) { + g_trusted_name_info.owner = g_trusted_token_account_owner_pubkey; + g_trusted_token_account_owner_pubkey_set = true; + if (!parse_tlv(&g_tlv_payload, &g_trusted_name_info, &sig_ctx) || + !verify_signature(&sig_ctx)) { + free_payload(&g_tlv_payload); + roll_challenge(); // prevent brute-force guesses + g_trusted_name_info.rcv_flags = 0; + memset(g_trusted_token_account_owner_pubkey, + 0, + sizeof(g_trusted_token_account_owner_pubkey)); + g_trusted_token_account_owner_pubkey_set = false; + return reply_error(INTERNAL_ERROR); + } + + PRINTF("Token account : %s owned by %s\n", + g_trusted_name_info.token_account, + g_trusted_token_account_owner_pubkey); + + free_payload(&g_tlv_payload); + roll_challenge(); // prevent replays + return reply_success(); + } + return reply_error(INTERNAL_ERROR); +} + diff --git a/src/trusted_name_descriptor_handler.h b/src/trusted_name_descriptor_handler.h new file mode 100644 index 00000000..d10b5524 --- /dev/null +++ b/src/trusted_name_descriptor_handler.h @@ -0,0 +1,10 @@ +#pragma once + +#include "commands.h" + +int trusted_name_descriptor_handler(const command_t *cmd); + +#define MAX_ADDRESS_LENGTH 44 + +extern uint8_t g_trusted_token_account_owner_pubkey[MAX_ADDRESS_LENGTH + 1]; +extern bool g_trusted_token_account_owner_pubkey_set; \ No newline at end of file From 0cf15a4171fd906975aa1cb870158209ec77c1f4 Mon Sep 17 00:00:00 2001 From: GroM Date: Fri, 22 Nov 2024 13:09:18 +0800 Subject: [PATCH 4/5] Update Ragger tests for Solana: SPL token swap support --- test/.DS_Store | Bin 0 -> 6148 bytes test/python/.DS_Store | Bin 0 -> 6148 bytes test/python/apps/cal.py | 3 +- test/python/apps/solana_utils.py | 25 +++++++-- test/python/requirements.txt | 2 + test/python/test_solana.py | 84 +++++++++++++++++++++++++++++-- 6 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 test/.DS_Store create mode 100644 test/python/.DS_Store diff --git a/test/.DS_Store b/test/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..b2093fe5931e01c66084eb3dd9ff598c7d0b1f93 GIT binary patch literal 6148 zcmeHK%}T>S5Z<-5O({YS3Oz1(Eg0J(h?fxS3mDOZN=-S5XWcRY7?rEgF=r1uLWyc1o0ATeE}nSP>BgG8jRVJwDwR6IqM7gBtDNb zyBnp{UOWhv8JPX;$4r*}C+ubcK=h}>BLD{gEOcVU#^MK~aq<!}~&xjy~6hg?5 zZ2v(4dUrdJ;QNXoK>vP!Bgms!K8_L@Va$6pPU3vhZhweMwYIfgx9V2Iy7M39%%6ml zY2FXB8){ujnM7qhjIQF*EO7SDWI74sbd;%tcsRn4+v_+T%2{7d(_yZ1eLY~=mK`|F z`MlR|x7<#*yJ)%dlMdE<$GyeEw)PJWPcMR}pyS^7cOB0zs zU`&}`<}q0TR)7`QWChHgXEio?k-RomfEDOgib~3RAH_dLZ@TDYvMe^LQ|)MP&4Cm%*?{vP=uNt`&|_d!qeoE6<`IH z6{zZ_L+Ahb`uqQK61P|ZR$!wP5Ve6n=;M;i*}AegI%{3@dvr32D>T1Su%eD)jHRQv ciLMO$U8*2G!$K2#(D+9{$-o6G@TUrV0;>g2E&u=k literal 0 HcmV?d00001 diff --git a/test/python/apps/cal.py b/test/python/apps/cal.py index f057f10d..d76300a1 100644 --- a/test/python/apps/cal.py +++ b/test/python/apps/cal.py @@ -14,7 +14,7 @@ from .litecoin import LTC_PACKED_DERIVATION_PATH, LTC_CONF from .bitcoin import BTC_PACKED_DERIVATION_PATH, BTC_CONF from .stellar import XLM_PACKED_DERIVATION_PATH, XLM_CONF -from .solana_utils import SOL_PACKED_DERIVATION_PATH, SOL_CONF +from .solana_utils import SOL_PACKED_DERIVATION_PATH, SOL_CONF, JUP_CONF, JUP_PACKED_DERIVATION_PATH from .xrp import XRP_PACKED_DERIVATION_PATH, XRP_CONF from .tezos import XTZ_PACKED_DERIVATION_PATH, XTZ_CONF from .polkadot import DOT_PACKED_DERIVATION_PATH, DOT_CONF @@ -54,6 +54,7 @@ def get_conf_for_ticker(self, overload_signer: Optional[SigningAuthority]=None) USDC_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="USDC", conf=TRX_USDC_CONF, packed_derivation_path=TRX_PACKED_DERIVATION_PATH) TUSD_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="TUSD", conf=TRX_TUSD_CONF, packed_derivation_path=TRX_PACKED_DERIVATION_PATH) USDD_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="USDD", conf=TRX_USDD_CONF, packed_derivation_path=TRX_PACKED_DERIVATION_PATH) +JUP_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="JUP", conf=JUP_CONF, packed_derivation_path=JUP_PACKED_DERIVATION_PATH) # Helper that can be called from outside if we want to generate errors easily diff --git a/test/python/apps/solana_utils.py b/test/python/apps/solana_utils.py index d45db03c..61d697d3 100644 --- a/test/python/apps/solana_utils.py +++ b/test/python/apps/solana_utils.py @@ -2,6 +2,8 @@ from ragger.utils import create_currency_config from ragger.bip import pack_derivation_path +from typing import Optional +import struct ### Some utilities functions for amounts conversions ### @@ -34,15 +36,18 @@ def lamports_to_bytes(lamports: int) -> str: ### Proposed foreign and owned addresses ### # "Foreign" Solana public key (actually the device public key derived on m/44'/501'/11111') -FOREIGN_ADDRESS = b"AxmUF3qkdz1zs151Q5WttVMkFpFGQPwghZs4d1mwY55d" +FOREIGN_ADDRESS_STR = "AxmUF3qkdz1zs151Q5WttVMkFpFGQPwghZs4d1mwY55d" +FOREIGN_ADDRESS = FOREIGN_ADDRESS_STR.encode('utf-8') FOREIGN_PUBLIC_KEY = base58.b58decode(FOREIGN_ADDRESS) # "Foreign" Solana public key (actually the device public key derived on m/44'/501'/11112') -FOREIGN_ADDRESS_2 = b"8bjDMujLMttbmkTtoFgfw2sPYchSzzcTCEPGYDaNs3nj" +FOREIGN_ADDRESS_2_STR = "8bjDMujLMttbmkTtoFgfw2sPYchSzzcTCEPGYDaNs3nj" +FOREIGN_ADDRESS_2 = FOREIGN_ADDRESS_2_STR.encode('utf-8') FOREIGN_PUBLIC_KEY_2 = base58.b58decode(FOREIGN_ADDRESS_2) # Device Solana public key (derived on m/44'/501'/12345') -OWNED_ADDRESS = b"3GJzvStsiYZonWE7WTsmt1BpWXkfcgWMGinaDwNs9HBc" +OWNED_ADDRESS_STR = "3GJzvStsiYZonWE7WTsmt1BpWXkfcgWMGinaDwNs9HBc" +OWNED_ADDRESS = OWNED_ADDRESS_STR.encode('utf-8') OWNED_PUBLIC_KEY = base58.b58decode(OWNED_ADDRESS) @@ -55,3 +60,17 @@ def lamports_to_bytes(lamports: int) -> str: ### Package this currency configuration in exchange format ### SOL_CONF = create_currency_config("SOL", "Solana") + +def get_sub_config(ticker: str, decimals: int, chain_id: Optional[int] = None) -> bytes: + cfg = bytearray() + cfg.append(len(ticker)) + cfg += ticker.encode() + cfg.append(decimals) + if chain_id is not None: + cfg += struct.pack(">Q", chain_id) + return cfg + +JUP_CONF = create_currency_config("JUP", "Solana", get_sub_config("JUP", 6)) +JUP_PACKED_DERIVATION_PATH = SOL_PACKED_DERIVATION_PATH + +JUP_MINT_ADDRESS_STR = "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN" \ No newline at end of file diff --git a/test/python/requirements.txt b/test/python/requirements.txt index 91cdfff4..fa47a07b 100644 --- a/test/python/requirements.txt +++ b/test/python/requirements.txt @@ -9,3 +9,5 @@ embit fastcrc ledger_app_clients.ethereum @ https://github.com/LedgerHQ/app-ethereum/archive/develop.zip#subdirectory=client tonsdk +solders +solana diff --git a/test/python/test_solana.py b/test/python/test_solana.py index 89e85e87..8979e4e2 100644 --- a/test/python/test_solana.py +++ b/test/python/test_solana.py @@ -2,11 +2,17 @@ from .apps.exchange_test_runner import ExchangeTestRunner, ALL_TESTS_EXCEPT_MEMO_THORSWAP_AND_FEES from .apps.solana import SolanaClient, ErrorType -from .apps.solana_utils import SOL_PACKED_DERIVATION_PATH from .apps.solana_cmd_builder import SystemInstructionTransfer, ComputeBudgetInstructionSetComputeUnitLimit, ComputeBudgetInstructionSetComputeUnitPrice, Message, verify_signature from .apps import solana_utils as SOL from .apps import cal as cal +from solders.pubkey import Pubkey +from solders.transaction import Transaction +from solders.message import Message +from solders.hash import Hash +from spl.token.constants import TOKEN_PROGRAM_ID +from spl.token.instructions import TransferCheckedParams, transfer_checked, get_associated_token_address + # A bit hacky but way less hassle than actually writing an actual address decoder SOLANA_ADDRESS_DECODER = { SOL.FOREIGN_ADDRESS: SOL.FOREIGN_PUBLIC_KEY, @@ -36,7 +42,7 @@ def perform_final_tx(self, destination, send_amount, fees, memo): instruction: SystemInstructionTransfer = SystemInstructionTransfer(SOL.OWNED_PUBLIC_KEY, decoded_destination, send_amount) message: bytes = Message([instruction]).serialize() sol = SolanaClient(self.backend) - with sol.send_async_sign_message(SOL_PACKED_DERIVATION_PATH, message): + with sol.send_async_sign_message(SOL.SOL_PACKED_DERIVATION_PATH, message): pass signature: bytes = sol.get_async_response().data verify_signature(SOL.OWNED_PUBLIC_KEY, message, signature) @@ -49,6 +55,77 @@ class TestsSolana: def test_solana(self, backend, exchange_navigation_helper, test_to_run): GenericSolanaTests(backend, exchange_navigation_helper).run_test(test_to_run) +class SPLTokenTests(GenericSolanaTests): + currency_configuration = cal.JUP_CURRENCY_CONFIGURATION + + valid_destination_1 = str( + get_associated_token_address( + Pubkey.from_string(SOL.FOREIGN_ADDRESS_STR), + Pubkey.from_string(SOL.JUP_MINT_ADDRESS_STR) + ) + ) + valid_destination_2 = str( + get_associated_token_address( + Pubkey.from_string(SOL.FOREIGN_ADDRESS_2_STR), + Pubkey.from_string(SOL.JUP_MINT_ADDRESS_STR) + ) + ) + valid_refund = str( + get_associated_token_address( + Pubkey.from_string(SOL.OWNED_ADDRESS_STR), + Pubkey.from_string(SOL.JUP_MINT_ADDRESS_STR) + ) + ) + valid_send_amount_1 = SOL.AMOUNT + valid_send_amount_2 = SOL.AMOUNT_2 + valid_fees_1 = SOL.FEES + valid_fees_2 = SOL.FEES_2 + fake_refund = SOL.FOREIGN_ADDRESS_STR + fake_payout = SOL.FOREIGN_ADDRESS_STR + signature_refusal_error_code = ErrorType.SOLANA_SUMMARY_FINALIZE_FAILED + + def perform_final_tx(self, destination, send_amount, fees, memo): + # Define the sender and receiver public keys + sender_public_key = Pubkey.from_string(SOL.OWNED_ADDRESS_STR) + + # Get the associated token addresses for the sender + sender_ata = get_associated_token_address(sender_public_key, SOL.JUP_MINT_ADDRESS) + + # Define the amount to send (in the smallest unit, e.g., if JUP has 6 decimals, 1 JUP = 1_000_000) + amount = send_amount + + # Create the transaction + transfer_instruction = transfer_checked( + TransferCheckedParams( + program_id=TOKEN_PROGRAM_ID, + source=sender_ata, + mint=SOL.JUP_MINT_ADDRESS, + dest=destination, + owner=sender_public_key, + amount=amount, + decimals=6 # Number of decimals for JUP token + ) + ) + + blockhash = Hash.default() + message = Message.new_with_blockhash([transfer_instruction], sender_public_key, blockhash) + tx = Transaction.new_unsigned(message) + + # Dump the message embedded in the transaction + message = tx.message_data() + + sol = SolanaClient(self.backend) + with sol.send_async_sign_message(SOL.SOL_PACKED_DERIVATION_PATH, message): + pass + signature: bytes = sol.get_async_response().data + verify_signature(SOL.OWNED_PUBLIC_KEY, message, signature) + +# Use a class to reuse the same Speculos instance +class TestsSPLToken: + @pytest.mark.parametrize('test_to_run', ALL_TESTS_EXCEPT_MEMO_THORSWAP_AND_FEES) + def test_solana_spl_token(self, backend, exchange_navigation_helper, test_to_run): + SPLTokenTests(backend, exchange_navigation_helper).run_test(test_to_run) + # ExchangeTestRunner implementation for Solana class SolanaPriorityFeesTests(GenericSolanaTests): def perform_final_tx(self, destination, send_amount, fees, memo): @@ -58,12 +135,11 @@ def perform_final_tx(self, destination, send_amount, fees, memo): instruction: SystemInstructionTransfer = SystemInstructionTransfer(SOL.OWNED_PUBLIC_KEY, decoded_destination, send_amount) message: bytes = Message([computeUnitLimit, computeUnitPrice, instruction]).serialize() sol = SolanaClient(self.backend) - with sol.send_async_sign_message(SOL_PACKED_DERIVATION_PATH, message): + with sol.send_async_sign_message(SOL.SOL_PACKED_DERIVATION_PATH, message): pass signature: bytes = sol.get_async_response().data verify_signature(SOL.OWNED_PUBLIC_KEY, message, signature) - # Use a class to reuse the same Speculos instance class TestsSolanaPriorityFees: @pytest.mark.parametrize('test_to_run', ALL_TESTS_EXCEPT_MEMO_THORSWAP_AND_FEES) From 8d7ade49b692470d7234b31a77127356ce79697f Mon Sep 17 00:00:00 2001 From: GroM Date: Fri, 22 Nov 2024 13:17:53 +0800 Subject: [PATCH 5/5] Fix solana client --- test/python/apps/solana.py | 7 +++---- test/python/test_solana.py | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/test/python/apps/solana.py b/test/python/apps/solana.py index 69bd96bc..e5afaec2 100644 --- a/test/python/apps/solana.py +++ b/test/python/apps/solana.py @@ -88,11 +88,10 @@ def get_public_key(self, derivation_path: bytes) -> bytes: def split_and_prefix_message(self, derivation_path : bytes, message: bytes) -> List[bytes]: assert len(message) <= 65535, "Message to send is too long" header: bytes = _extend_and_serialize_multiple_derivations_paths([derivation_path]) - # Check to see if this data needs to be split up and sent in chunks. - max_size = MAX_CHUNK_SIZE - len(header) + max_size = MAX_CHUNK_SIZE message_splited = [message[x:x + max_size] for x in range(0, len(message), max_size)] - # Add the header to every chunk - return [header + s for s in message_splited] + # The first chunk is the header, then all chunks with max size + return [header] + message_splited def send_first_message_batch(self, messages: List[bytes], p1: int) -> RAPDU: diff --git a/test/python/test_solana.py b/test/python/test_solana.py index 8979e4e2..b2082c33 100644 --- a/test/python/test_solana.py +++ b/test/python/test_solana.py @@ -85,7 +85,7 @@ class SPLTokenTests(GenericSolanaTests): signature_refusal_error_code = ErrorType.SOLANA_SUMMARY_FINALIZE_FAILED def perform_final_tx(self, destination, send_amount, fees, memo): - # Define the sender and receiver public keys + # Get the sender public key sender_public_key = Pubkey.from_string(SOL.OWNED_ADDRESS_STR) # Get the associated token addresses for the sender