From 9490256069dd6b1ca7384a0466eb9120f8e85eac Mon Sep 17 00:00:00 2001 From: Maciek Malik Date: Thu, 14 Dec 2023 03:19:13 +0100 Subject: [PATCH 1/2] Extended signing support for Off-chain message signing --- Makefile | 13 +- libsol/Makefile | 1 - libsol/common_byte_strings.h | 11 +- libsol/compute_budget_instruction.c | 100 ++++++++++ libsol/compute_budget_instruction.h | 46 +++++ libsol/compute_budget_instruction_test.c | 76 ++++++++ libsol/include/sol/offchain_message_signing.h | 20 ++ libsol/include/sol/parser.h | 19 +- libsol/include/sol/string_utils.h | 7 + libsol/include/sol/transaction_summary.h | 36 +++- libsol/instruction.c | 5 + libsol/instruction.h | 4 + libsol/instruction_test.c | 76 ++++++++ libsol/message.c | 13 +- libsol/message_test.c | 103 +++++++++- libsol/offchain_message_signing.c | 5 + libsol/parser.c | 56 ++++-- libsol/parser_test.c | 87 ++++++++- libsol/string_utils.c | 68 +++++++ libsol/string_utils_test.c | 103 ++++++++++ libsol/system_instruction.c | 1 + libsol/transaction_printers.c | 19 ++ libsol/transaction_summary.c | 74 +++++++- libsol/transaction_summary_test.c | 1 + libsol/util.h | 5 +- src/globals.h | 18 +- src/handle_sign_message.c | 16 +- src/handle_sign_offchain_message.c | 178 ++++++++---------- src/ui/sign_message_bagl.c | 58 +++--- src/ui/ui_api.h | 4 +- src/utils.c | 12 +- src/utils.h | 2 + 32 files changed, 1063 insertions(+), 174 deletions(-) create mode 100644 libsol/compute_budget_instruction.c create mode 100644 libsol/compute_budget_instruction.h create mode 100644 libsol/compute_budget_instruction_test.c create mode 100644 libsol/include/sol/offchain_message_signing.h create mode 100644 libsol/include/sol/string_utils.h create mode 100644 libsol/offchain_message_signing.c create mode 100644 libsol/string_utils.c create mode 100644 libsol/string_utils_test.c diff --git a/Makefile b/Makefile index 9a910bff..2c22a88a 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ APP_LOAD_PARAMS += $(COMMON_LOAD_PARAMS) APPNAME = "Solana" APPVERSION_M = 1 APPVERSION_N = 4 -APPVERSION_P = 2 +APPVERSION_P = 3 APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)" ifeq ($(TARGET_NAME),TARGET_NANOS) @@ -68,6 +68,17 @@ DEFINES += BLE_SEGMENT_SIZE=32 DEFINES += HAVE_WEBUSB WEBUSB_URL_SIZE_B=0 WEBUSB_URL="" DEFINES += UNUSED\(x\)=\(void\)x + +# Inject the target name into the build +# Required in transaction summary to display screens correctly +ifneq ($(TARGET_NAME),) + DEFINES += SDK_$(TARGET_NAME) +else + # Use as default value + DEFINES += SDK_TARGET_UNKNOWN +endif + + ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_NANOX TARGET_STAX)) DEFINES += HAVE_BLE BLE_COMMAND_TIMEOUT_MS=2000 HAVE_BLE_APDU endif diff --git a/libsol/Makefile b/libsol/Makefile index 1067343e..ffbf3fed 100644 --- a/libsol/Makefile +++ b/libsol/Makefile @@ -18,7 +18,6 @@ ifeq ($(COVERAGE),1) CFLAGS += --coverage endif -debug_CFLAGS = -g -fsanitize=address -fsanitize=undefined release_CFLAGS = -O2 libsol_source_files = $(filter-out %_test.c,$(wildcard *.c)) diff --git a/libsol/common_byte_strings.h b/libsol/common_byte_strings.h index 7afeeda1..27101171 100644 --- a/libsol/common_byte_strings.h +++ b/libsol/common_byte_strings.h @@ -77,9 +77,18 @@ 0x7c, 0x7c, 0x35, 0xb5, 0xdd, 0xbc, 0x92, 0xbb, 0x81, 0xe4, 0x1f, 0xa8, 0x40, 0x41, 0x05, \ 0x44, 0x8d -// Sysvars +#define PROGRAM_ID_COMPUTE_BUDGET /* "ComputeBudget111111111111111111111111111111" */ \ + 0x03, 0x06, 0x46, 0x6f, 0xe5, 0x21, 0x17, 0x32, 0xff, 0xec, 0xad, 0xba, 0x72, 0xc3, \ + 0x9b, 0xe7, 0xbc, 0x8c, 0xe5, 0xbb, 0xc5, 0xf7, 0x12, 0x6b, 0x2c, 0x43, 0x9b, 0x3a, 0x40, 0x00, \ + 0x00, 0x00 +// Sysvars #define SYSVAR_RENT /* "SysvarRent111111111111111111111111111111111" */ \ 0x06, 0xa7, 0xd5, 0x17, 0x19, 0x2c, 0x5c, 0x51, 0x21, 0x8c, 0xc9, 0x4c, 0x3d, 0x4a, 0xf1, \ 0x7f, 0x58, 0xda, 0xee, 0x08, 0x9b, 0xa1, 0xfd, 0x44, 0xe3, 0xdb, 0xd9, 0x8a, 0x00, 0x00, \ 0x00, 0x00 + +// Domain specifiers +#define OFFCHAIN_MESSAGE_SIGNING_DOMAIN /* "\xffsolana offchain" */ \ + 0xff, 0x73, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x20, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, \ + 0x6e diff --git a/libsol/compute_budget_instruction.c b/libsol/compute_budget_instruction.c new file mode 100644 index 00000000..70896285 --- /dev/null +++ b/libsol/compute_budget_instruction.c @@ -0,0 +1,100 @@ +#include "compute_budget_instruction.h" +#include "sol/transaction_summary.h" + +const Pubkey compute_budget_program_id = {{PROGRAM_ID_COMPUTE_BUDGET}}; + +static int parse_compute_budget_instruction_kind(Parser* parser, + enum ComputeBudgetInstructionKind* kind) { + uint8_t maybe_kind; + BAIL_IF(parse_u8(parser, &maybe_kind)); + switch (maybe_kind) { + case ComputeBudgetRequestHeapFrame: + case ComputeBudgetChangeUnitLimit: + case ComputeBudgetChangeUnitPrice: + case ComputeBudgetSetLoadedAccountsDataSizeLimit: + *kind = (enum ComputeBudgetInstructionKind) maybe_kind; + return 0; + default: + return 1; + } +} + +static int parse_request_heap_frame_instruction(Parser* parser, + ComputeBudgetRequestHeapFrameInfo* info) { + BAIL_IF(parse_u32(parser, &info->bytes)); + + return 0; +} + +static int parse_unit_limit_instruction(Parser* parse, ComputeBudgetChangeUnitLimitInfo* info) { + BAIL_IF(parse_u32(parse, &info->units)); + + return 0; +} + +static int parse_unit_price_instruction(Parser* parse, ComputeBudgetChangeUnitPriceInfo* info) { + BAIL_IF(parse_u64(parse, &info->units)); + + return 0; +} + +static int parse_loaded_accounts_data_size_limit(Parser* parse, ComputeBudgetSetLoadedAccountsDataSizeLimitInfo* info) { + BAIL_IF(parse_u32(parse, &info->units)); + + return 0; +} + +static int print_compute_budget_unit_price(ComputeBudgetChangeUnitPriceInfo* info, const PrintConfig* print_config) { + UNUSED(print_config); + + SummaryItem* item; + + item = transaction_summary_general_item(); + summary_item_set_u64(item, "Unit price", info->units); + + return 0; +} + +static int print_compute_budget_unit_limit(ComputeBudgetChangeUnitLimitInfo* info, const PrintConfig* print_config) { + UNUSED(print_config); + + SummaryItem* item; + + item = transaction_summary_general_item(); + summary_item_set_u64(item, "Unit limit", info->units); + + return 0; +} + +int print_compute_budget(ComputeBudgetInfo* info, const PrintConfig* print_config) { + switch (info->kind) { + case ComputeBudgetChangeUnitLimit: + return print_compute_budget_unit_limit(&info->change_unit_limit, print_config); + case ComputeBudgetChangeUnitPrice: + return print_compute_budget_unit_price(&info->change_unit_price, print_config); + case ComputeBudgetRequestUnits: + case ComputeBudgetRequestHeapFrame: + case ComputeBudgetSetLoadedAccountsDataSizeLimit: + break; + } + return 1; +} + +int parse_compute_budget_instructions(const Instruction* instruction, ComputeBudgetInfo* info) { + Parser parser = {instruction->data, instruction->data_length}; + + BAIL_IF(parse_compute_budget_instruction_kind(&parser, &info->kind)); + + switch (info->kind) { + case ComputeBudgetRequestHeapFrame: + return parse_request_heap_frame_instruction(&parser, &info->request_heap_frame); + case ComputeBudgetChangeUnitLimit: + return parse_unit_limit_instruction(&parser, &info->change_unit_limit); + case ComputeBudgetChangeUnitPrice: + return parse_unit_price_instruction(&parser, &info->change_unit_price); + case ComputeBudgetSetLoadedAccountsDataSizeLimit: + return parse_loaded_accounts_data_size_limit(&parser, &info->set_loaded_accounts_data_size_limit); + default: + return 1; + } +} diff --git a/libsol/compute_budget_instruction.h b/libsol/compute_budget_instruction.h new file mode 100644 index 00000000..61f8cbf4 --- /dev/null +++ b/libsol/compute_budget_instruction.h @@ -0,0 +1,46 @@ +#pragma once + +#include "common_byte_strings.h" +#include "util.h" +#include "sol/parser.h" +#include "sol/print_config.h" + +extern const Pubkey compute_budget_program_id; + +enum ComputeBudgetInstructionKind { + ComputeBudgetRequestUnits = 0,//Deprecated + ComputeBudgetRequestHeapFrame, + ComputeBudgetChangeUnitLimit, + ComputeBudgetChangeUnitPrice, + ComputeBudgetSetLoadedAccountsDataSizeLimit +}; + +typedef struct ComputeBudgetRequestHeapFrameInfo { + uint32_t bytes; +} ComputeBudgetRequestHeapFrameInfo; + +typedef struct ComputeBudgetChangeUnitLimitInfo { + uint32_t units; +} ComputeBudgetChangeUnitLimitInfo; + +typedef struct ComputeBudgetChangeUnitPriceInfo { + uint64_t units; +} ComputeBudgetChangeUnitPriceInfo; + +typedef struct ComputeBudgetSetLoadedAccountsDataSizeLimitInfo{ + uint32_t units; +} ComputeBudgetSetLoadedAccountsDataSizeLimitInfo; + +typedef struct ComputeBudgetInfo { + enum ComputeBudgetInstructionKind kind; + union { + ComputeBudgetRequestHeapFrameInfo request_heap_frame; + ComputeBudgetChangeUnitLimitInfo change_unit_limit; + ComputeBudgetChangeUnitPriceInfo change_unit_price; + ComputeBudgetSetLoadedAccountsDataSizeLimitInfo set_loaded_accounts_data_size_limit; + }; +} ComputeBudgetInfo; + +int parse_compute_budget_instructions(const Instruction* instruction, ComputeBudgetInfo* info); + +int print_compute_budget(ComputeBudgetInfo* info, const PrintConfig* print_config); diff --git a/libsol/compute_budget_instruction_test.c b/libsol/compute_budget_instruction_test.c new file mode 100644 index 00000000..55c8508b --- /dev/null +++ b/libsol/compute_budget_instruction_test.c @@ -0,0 +1,76 @@ +#include "compute_budget_instruction.c" +#include "sol/parser.h" +#include +#include + +void test_parse_compute_budget_instruction_kind() { + uint8_t message[] = {0, 1, 2, 3, 4}; + + enum ComputeBudgetInstructionKind instruction_kind_target; + + Parser parser = {message, sizeof(message)}; + + // ComputeBudgetRequestUnits - Deprecated + assert(parse_compute_budget_instruction_kind(&parser, &instruction_kind_target) == 1); + + // ComputeBudgetRequestHeapFrame + assert(parse_compute_budget_instruction_kind(&parser, &instruction_kind_target) == 0); + assert(instruction_kind_target == ComputeBudgetRequestHeapFrame); + + // ComputeBudgetChangeUnitLimit + assert(parse_compute_budget_instruction_kind(&parser, &instruction_kind_target) == 0); + assert(instruction_kind_target == ComputeBudgetChangeUnitLimit); + + // ComputeBudgetChangeUnitPrice + assert(parse_compute_budget_instruction_kind(&parser, &instruction_kind_target) == 0); + assert(instruction_kind_target == ComputeBudgetChangeUnitPrice); + + // ComputeBudgetSetLoadedAccountsDataSizeLimit + assert(parse_compute_budget_instruction_kind(&parser, &instruction_kind_target) == 0); + assert(instruction_kind_target == ComputeBudgetSetLoadedAccountsDataSizeLimit); + + // Buffer empty, should fail + // parse_u8 should return 1 + assert(parse_compute_budget_instruction_kind(&parser, &instruction_kind_target) == 1); +} + +/** + * Buffer with invalid data should return error and not modify passed *kind pointer + */ +void test_parse_compute_budget_instruction_kind_invalid() { + uint8_t message[] = {0x21, 0x37}; + + enum ComputeBudgetInstructionKind instruction_kind_target = 0; + + Parser parser = {message, sizeof(message)}; + + // instruction kind not found with given value + assert(parse_compute_budget_instruction_kind(&parser, &instruction_kind_target) == 1); + assert(instruction_kind_target == 0); // instruction_kind_target is not modified +} + +/** + * Test parsing instruction kind. + * Code execution should not get to the switch statement + */ +void test_parse_compute_budget_instructions_invalid_kind() { + uint8_t message[] = {5}; + + Instruction instruction = {.data = message, + .data_length = sizeof(message), + .accounts_length = 0}; + ComputeBudgetInfo info; + + int result = parse_compute_budget_instructions(&instruction, &info); + + assert(result == 1); // Invalid instruction kind for ComputeBudget program +} + +int main() { + test_parse_compute_budget_instruction_kind(); + test_parse_compute_budget_instruction_kind_invalid(); + test_parse_compute_budget_instructions_invalid_kind(); + + printf("passed\n"); + return 0; +} \ No newline at end of file diff --git a/libsol/include/sol/offchain_message_signing.h b/libsol/include/sol/offchain_message_signing.h new file mode 100644 index 00000000..cd861fd7 --- /dev/null +++ b/libsol/include/sol/offchain_message_signing.h @@ -0,0 +1,20 @@ +#pragma once +#include + +#define OFFCHAIN_MESSAGE_SIGNING_DOMAIN_LENGTH 16 + +/** +* 1. Signing domain (16 bytes) +* 2. Header version (1 byte) +* 3. Application domain (32 bytes) +* 4. Message format (1 byte) +* 5. Signer count (1 bytes) +* 6. Signers (signer_count * 32 bytes) - assume that only one signer is present +* 7. Message length (2 bytes) +*/ +typedef struct OffchainMessageSigningDomain { + uint8_t data[OFFCHAIN_MESSAGE_SIGNING_DOMAIN_LENGTH]; +} OffchainMessageSigningDomain; + +extern const OffchainMessageSigningDomain offchain_message_signing_domain; + diff --git a/libsol/include/sol/parser.h b/libsol/include/sol/parser.h index f5b2b1ef..1db634f0 100644 --- a/libsol/include/sol/parser.h +++ b/libsol/include/sol/parser.h @@ -59,9 +59,18 @@ typedef struct MessageHeader { size_t instructions_length; } MessageHeader; +//@TODO move to offchain message sign .h +#define OFFCHAIN_MESSAGE_APPLICATION_DOMAIN_LENGTH 32 +typedef struct OffchainMessageApplicationDomain { + uint8_t data[OFFCHAIN_MESSAGE_APPLICATION_DOMAIN_LENGTH]; +} OffchainMessageApplicationDomain; + typedef struct OffchainMessageHeader { uint8_t version; + const OffchainMessageApplicationDomain* application_domain; uint8_t format; + size_t signers_length; + const Pubkey* signers; uint16_t length; } OffchainMessageHeader; @@ -69,6 +78,9 @@ static inline int parser_is_empty(Parser* parser) { return parser->buffer_length == 0; } +void advance(Parser* parser, size_t num); +int check_buffer_length(Parser* parser, size_t num); + int parse_u8(Parser* parser, uint8_t* value); int parse_u32(Parser* parser, uint32_t* value); @@ -87,13 +99,18 @@ int parse_pubkey(Parser* parser, const Pubkey** pubkey); int parse_pubkeys_header(Parser* parser, PubkeysHeader* header); -int parse_pubkeys(Parser* parser, PubkeysHeader* header, const Pubkey** pubkeys); +int parse_pubkeys(Parser* parser, size_t num_pubkeys, const Pubkey** pubkeys); int parse_blockhash(Parser* parser, const Hash** hash); #define parse_blockhash parse_hash int parse_message_header(Parser* parser, MessageHeader* header); +int parse_offchain_message_application_domain( + Parser* parser, + const OffchainMessageApplicationDomain** app_domain +); + int parse_offchain_message_header(Parser* parser, OffchainMessageHeader* header); int parse_instruction(Parser* parser, Instruction* instruction); diff --git a/libsol/include/sol/string_utils.h b/libsol/include/sol/string_utils.h new file mode 100644 index 00000000..a1396b90 --- /dev/null +++ b/libsol/include/sol/string_utils.h @@ -0,0 +1,7 @@ +#pragma once +#include +#include +#include + +bool is_data_utf8(const uint8_t *data, size_t length); +bool is_data_ascii(const uint8_t *data, size_t length); \ No newline at end of file diff --git a/libsol/include/sol/transaction_summary.h b/libsol/include/sol/transaction_summary.h index b087210c..cab8135a 100644 --- a/libsol/include/sol/transaction_summary.h +++ b/libsol/include/sol/transaction_summary.h @@ -2,6 +2,7 @@ #include "sol/parser.h" #include "sol/printer.h" +#include "offchain_message_signing.h" // TransactionSummary management // @@ -19,7 +20,17 @@ // // If all _Required_ `SummaryItem`s have not been set, finalization will fail. -#define NUM_GENERAL_ITEMS 11 + +#if defined(SDK_TARGET_NANOX) || defined(SDK_TARGET_NANOS2) || defined(SDK_TARGET_STAX) + #define NUM_GENERAL_ITEMS 40 +#else + //Memory constraints on Nano S does not allow for more than 13 general items + //Or if target is unknown + #define NUM_GENERAL_ITEMS 12 +#endif + +#define DEFAULT_COMPUTE_UNIT_LIMIT 200000 +#define COMPUTE_UNIT_PRICE_DIVIDER 1000000 #define MAX_TRANSACTION_SUMMARY_ITEMS \ (1 /* primary */ \ + NUM_GENERAL_ITEMS + 1 /* nonce_account */ \ @@ -44,15 +55,24 @@ enum SummaryItemKind { SummaryItemSizedString, SummaryItemString, SummaryItemTimestamp, + SummaryItemOffchainMessageApplicationDomain, + SummaryItemExtendedString, }; + typedef enum SummaryItemKind SummaryItemKind_t; typedef struct SummaryItem SummaryItem; extern char G_transaction_summary_title[TITLE_SIZE]; + +// Text buffer needs to be large enough to hold the longest possible message text to sign +//#define TEXT_BUFFER_LENGTH (OFFCHAIN_MESSAGE_MAXIMUM_MESSAGE_LENGTH - OFFCHAIN_MESSAGE_MINIMAL_HEADER_SIZE) #define TEXT_BUFFER_LENGTH BASE58_PUBKEY_LENGTH + extern char G_transaction_summary_text[TEXT_BUFFER_LENGTH]; +extern char* G_transaction_summary_extended_text; + void transaction_summary_reset(); enum DisplayFlags { DisplayFlagNone = 0, @@ -68,6 +88,12 @@ SummaryItem* transaction_summary_fee_payer_item(); SummaryItem* transaction_summary_nonce_account_item(); SummaryItem* transaction_summary_nonce_authority_item(); SummaryItem* transaction_summary_general_item(); +uint8_t transaction_summary_general_item_count(); +uint64_t calculate_additional_transaction_fees(); + + + + int transaction_summary_set_fee_payer_pubkey(const Pubkey* pubkey); @@ -84,4 +110,12 @@ void summary_item_set_pubkey(SummaryItem* item, const char* title, const Pubkey* void summary_item_set_hash(SummaryItem* item, const char* title, const Hash* value); void summary_item_set_sized_string(SummaryItem* item, const char* title, const SizedString* value); void summary_item_set_string(SummaryItem* item, const char* title, const char* value); +void summary_item_safe_set_string(SummaryItem* item, const char* title, const char* value); void summary_item_set_timestamp(SummaryItem* item, const char* title, int64_t value); +void summary_item_set_offchain_message_application_domain( + SummaryItem* item, + const char* title, + const OffchainMessageApplicationDomain* value +); +void summary_item_set_extended_string(SummaryItem* item, const char* title, const char* value); +void summary_item_safe_set_extended_string(SummaryItem* item, const char* title, const char* value); \ No newline at end of file diff --git a/libsol/instruction.c b/libsol/instruction.c index 1ea7a84e..fa9f2c70 100644 --- a/libsol/instruction.c +++ b/libsol/instruction.c @@ -2,6 +2,7 @@ #include "serum_assert_owner_instruction.h" #include "spl_memo_instruction.h" #include "spl_token_instruction.h" +#include "compute_budget_instruction.h" #include "stake_instruction.h" #include "system_instruction.h" #include "util.h" @@ -23,6 +24,8 @@ enum ProgramId instruction_program_id(const Instruction* instruction, const Mess return ProgramIdSerumAssertOwner; } else if (memcmp(program_id, &spl_memo_program_id, PUBKEY_SIZE) == 0) { return ProgramIdSplMemo; + } else if (memcmp(program_id, &compute_budget_program_id, PUBKEY_SIZE) == 0) { + return ProgramIdComputeBudget; } return ProgramIdUnknown; @@ -45,6 +48,8 @@ bool instruction_info_matches_brief(const InstructionInfo* info, const Instructi return true; case ProgramIdSplMemo: return true; + case ProgramIdComputeBudget: + return (brief->compute_budget == info->compute_budget.kind); case ProgramIdSplToken: return (brief->spl_token == info->spl_token.kind); case ProgramIdStake: diff --git a/libsol/instruction.h b/libsol/instruction.h index b0d4fe40..34edd94d 100644 --- a/libsol/instruction.h +++ b/libsol/instruction.h @@ -6,6 +6,7 @@ #include "stake_instruction.h" #include "system_instruction.h" #include "vote_instruction.h" +#include "compute_budget_instruction.h" #include enum ProgramId { @@ -17,6 +18,7 @@ enum ProgramId { ProgramIdSplAssociatedTokenAccount, ProgramIdSplMemo, ProgramIdSerumAssertOwner, + ProgramIdComputeBudget }; typedef struct InstructionInfo { @@ -27,6 +29,7 @@ typedef struct InstructionInfo { StakeInfo stake; SystemInfo system; VoteInfo vote; + ComputeBudgetInfo compute_budget; }; } InstructionInfo; @@ -41,6 +44,7 @@ typedef struct InstructionBrief { enum SystemInstructionKind system; enum StakeInstructionKind stake; enum VoteInstructionKind vote; + enum ComputeBudgetInstructionKind compute_budget; }; } InstructionBrief; diff --git a/libsol/instruction_test.c b/libsol/instruction_test.c index 5ab0b128..63838f28 100644 --- a/libsol/instruction_test.c +++ b/libsol/instruction_test.c @@ -1,6 +1,8 @@ #include "common_byte_strings.h" #include "instruction.h" #include "sol/parser.h" +#include "spl_memo_instruction.h" +#include "compute_budget_instruction.h" #include "stake_instruction.h" #include "system_instruction.h" #include "util.h" @@ -8,6 +10,34 @@ #include #include +void test_instruction_program_id_compute_budget() { + Pubkey program_id; + memcpy(&program_id, &compute_budget_program_id, PUBKEY_SIZE); + Instruction instruction = {0, NULL, 0, NULL, 0}; + { + MessageHeader header = {false, 0, {0, 0, 0, 1}, &program_id, NULL, 1}; + assert(instruction_program_id(&instruction, &header) == ProgramIdComputeBudget); + } + { + MessageHeader header = {true, 0, {0, 0, 0, 1}, &program_id, NULL, 1}; + assert(instruction_program_id(&instruction, &header) == ProgramIdComputeBudget); + } +} + +void test_instruction_program_id_spl_memo() { + Pubkey program_id; + memcpy(&program_id, &spl_memo_program_id, PUBKEY_SIZE); + Instruction instruction = {0, NULL, 0, NULL, 0}; + { + MessageHeader header = {false, 0, {0, 0, 0, 1}, &program_id, NULL, 1}; + assert(instruction_program_id(&instruction, &header) == ProgramIdSplMemo); + } + { + MessageHeader header = {true, 0, {0, 0, 0, 1}, &program_id, NULL, 1}; + assert(instruction_program_id(&instruction, &header) == ProgramIdSplMemo); + } +} + void test_instruction_program_id_system() { Pubkey program_id; memcpy(&program_id, &system_program_id, PUBKEY_SIZE); @@ -110,6 +140,48 @@ void test_static_brief_initializer_macros() { assert(memcmp(&stake_test, &stake_expect, sizeof(InstructionBrief)) == 0); } +void test_instruction_info_matches_brief_constants(){ + { + InstructionInfo info = {.kind = ProgramIdSerumAssertOwner}; + InstructionBrief brief_pass = {.program_id = ProgramIdSerumAssertOwner}; + assert(instruction_info_matches_brief(&info, &brief_pass)); + } + + { + InstructionInfo info = {.kind = ProgramIdSplAssociatedTokenAccount}; + InstructionBrief brief_pass = {.program_id = ProgramIdSplAssociatedTokenAccount}; + assert(instruction_info_matches_brief(&info, &brief_pass)); + } + + { + InstructionInfo info = {.kind = ProgramIdSplMemo}; + InstructionBrief brief_pass = {.program_id = ProgramIdSplMemo}; + assert(instruction_info_matches_brief(&info, &brief_pass)); + } +} + +void test_instruction_compute_budget_matches_brief(){ + InstructionInfo info = { + .kind = ProgramIdComputeBudget, + .compute_budget = { + .kind = ComputeBudgetChangeUnitLimit, + .change_unit_price = {.units = 0xAABBCCDD} + } + }; + + { + InstructionBrief brief_pass = {.program_id = ProgramIdComputeBudget, + .compute_budget = ComputeBudgetChangeUnitLimit}; + assert(instruction_info_matches_brief(&info, &brief_pass)); + } + + { + InstructionBrief brief_fail = {.program_id = ProgramIdComputeBudget, + .compute_budget = ComputeBudgetRequestHeapFrame}; + assert(!instruction_info_matches_brief(&info, &brief_fail)); + } +} + void test_instruction_info_matches_brief() { InstructionInfo info = { .kind = ProgramIdSystem, @@ -218,6 +290,10 @@ int main() { test_instruction_info_matches_brief(); test_instruction_infos_match_briefs(); test_instruction_accounts_iterator_next(); + test_instruction_program_id_spl_memo(); + test_instruction_program_id_compute_budget(); + test_instruction_info_matches_brief_constants(); + test_instruction_compute_budget_matches_brief(); printf("passed\n"); return 0; diff --git a/libsol/message.c b/libsol/message.c index 620ea244..53005dad 100644 --- a/libsol/message.c +++ b/libsol/message.c @@ -1,5 +1,4 @@ #include "instruction.h" -#include "serum_assert_owner_instruction.h" #include "sol/parser.h" #include "sol/message.h" #include "sol/print_config.h" @@ -10,6 +9,7 @@ #include "vote_instruction.h" #include "transaction_printers.h" #include "util.h" +#include "compute_budget_instruction.h" #include #define MAX_INSTRUCTIONS 4 @@ -53,7 +53,7 @@ int process_message_body(const uint8_t* message_body, break; } case ProgramIdSplMemo: { - // SPL Memo only has one instruction and we ignore it for now + // SPL Memo only has one instruction, and we ignore it for now info->kind = program_id; break; } @@ -80,6 +80,12 @@ int process_message_body(const uint8_t* message_body, } break; } + case ProgramIdComputeBudget: { + if (parse_compute_budget_instructions(&instruction, &info->compute_budget) == 0) { + info->kind = program_id; + } + break; + } case ProgramIdUnknown: break; } @@ -89,6 +95,7 @@ int process_message_body(const uint8_t* message_body, case ProgramIdSystem: case ProgramIdStake: case ProgramIdVote: + case ProgramIdComputeBudget: case ProgramIdUnknown: display_instruction_info[display_instruction_count++] = info; break; @@ -108,7 +115,7 @@ int process_message_body(const uint8_t* message_body, // Ensure we've consumed the entire message body BAIL_IF(!parser_is_empty(&parser)); - // If we don't know about all of the instructions, bail + // If we don't know about all the instructions, bail for (size_t i = 0; i < instruction_count; i++) { BAIL_IF(instruction_info[i].kind == ProgramIdUnknown); } diff --git a/libsol/message_test.c b/libsol/message_test.c index 709e816d..aedd0c4a 100644 --- a/libsol/message_test.c +++ b/libsol/message_test.c @@ -1,7 +1,8 @@ #include "common_byte_strings.h" -#include "message.c" #include "sol/parser.h" +#include "sol/print_config.h" #include "sol/transaction_summary.h" +#include "message.c" #include "util.h" #include #include @@ -147,6 +148,102 @@ static void process_message_body_and_sanity_check(const uint8_t* message, size_t } } +/** + * Transfer 6 lamports with compute budget limit instruction + */ +void test_process_message_body_transfer_with_compute_budget_limit(){ + + + uint8_t message[] = { + 2, 0, 2, + 5, 21, 114, 229, 47, 44, 94, 126, 102, 188, 25, 172, 108, 211, 11, 109, 105, 110, 167, 153, 207, 230, 215, 132, 84, 42, 183, 216, 183, 254, 49, 91, 92, 151, 92, 21, 68, 212, 0, 50, 152, 29, 184, 10, 237, 93, 26, 195, 28, 41, 242, 83, 160, 179, 163, 125, 22, 218, 2, 189, 250, 180, 15, 129, 237, 121, 159, 134, 35, 112, 111, 25, 35, 23, 57, 215, 23, 85, 213, 131, 83, 179, 66, 3, 70, 50, 124, 61, 59, 195, 97, 48, 196, 191, 215, 90, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3, 6, 70, 111, 229, 33, 23, 50, 255, 236, 173, 186, 114, 195, 155, 231, 188, 140, 229, 187, 197, 247, 18, 107, 44, 67, 155, 58, 64, 0, 0, 0, 218, 136, 223, 51, 158, 165, 31, 125, 51, 95, 141, 189, 233, 28, 179, 134, 163, 220, 32, 253, 196, 249, 160, 163, 50, 179, 54, 211, 231, 31, 42, 94, 2, + //compute budget - unit limit + 4, 0, 5, 2, + 205, 171, 0, 0, // New compute budget limit + //system - transfer + 3, 2, 1, 2, 12, 2, 0, 0, 0, + 6, // Transfer 6 lamports + 0, 0, 0, 0, 0, 0, 0 + }; + + process_message_body_and_sanity_check(message, sizeof(message), 5); +} + +/** + * Transfer 6 lamports with compute budget limit and unit price instructions + */ +void test_process_message_body_transfer_with_compute_budget_limit_and_unit_price(){ + + uint8_t message[] = { + 2, 0, 2, + 5, 21, 114, 229, 47, 44, 94, 126, 102, 188, 25, 172, 108, 211, 11, 109, 105, 110, 167, 153, 207, 230, 215, 132, 84, 42, 183, 216, 183, 254, 49, 91, 92, 151, 92, 21, 68, 212, 0, 50, 152, 29, 184, 10, 237, 93, 26, 195, 28, 41, 242, 83, 160, 179, 163, 125, 22, 218, 2, 189, 250, 180, 15, 129, 237, 121, 159, 134, 35, 112, 111, 25, 35, 23, 57, 215, 23, 85, 213, 131, 83, 179, 66, 3, 70, 50, 124, 61, 59, 195, 97, 48, 196, 191, 215, 90, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3, 6, 70, 111, 229, 33, 23, 50, 255, 236, 173, 186, 114, 195, 155, 231, 188, 140, 229, 187, 197, 247, 18, 107, 44, 67, 155, 58, 64, 0, 0, 0, 218, 136, 223, 51, 158, 165, 31, 125, 51, 95, 141, 189, 233, 28, 179, 134, 163, 220, 32, 253, 196, 249, 160, 163, 50, 179, 54, 211, 231, 31, 42, 94, 3, + //compute budget - unit limit + 4, 0, 5, 2, + 205, 171, 0, 0, // New compute budget limit + 4, 0, 9, 3, + 16, 0, 0, 0, 0, 0, 0, 0, // Unit price 16 lamports + //system - transfer + 3, 2, 1, 2, 12, 2, 0, 0, 0, + 6, // Transfer 6 lamports + 0, 0, 0, 0, 0, 0, 0 + }; + + process_message_body_and_sanity_check(message, sizeof(message), 6); + +} + +/* + * Transfer 5 lamports with additional request units instruction + * Should fail - RequestUnits is deprecated and not supported + */ +void test_process_message_body_transfer_with_request_units(){ + + uint8_t message[] = { + 2, 0, 2, + 5, 21, 114, 229, 47, 44, 94, 126, 102, 188, 25, 172, 108, 211, 11, 109, 105, 110, 167, 153, 207, 230, 215, 132, 84, 42, 183, 216, 183, 254, 49, 91, 92, 151, 92, 21, 68, 212, 0, 50, 152, 29, 184, 10, 237, 93, 26, 195, 28, 41, 242, 83, 160, 179, 163, 125, 22, 218, 2, 189, 250, 180, 15, 129, 237, 190, 59, 47, 250, 178, 1, 186, 135, 5, 102, 210, 129, 210, 83, 78, 169, 121, 6, 224, 25, 147, 92, 26, 51, 146, 176, 173, 69, 99, 246, 206, 134, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3, 6, 70, 111, 229, 33, 23, 50, 255, 236, 173, 186, 114, 195, 155, 231, 188, 140, 229, 187, 197, 247, 18, 107, 44, 67, 155, 58, 64, 0, 0, 0, 250, 188, 80, 35, 70, 214, 185, 174, 0, 85, 72, 57, 132, 27, 182, 217, 110, 155, 200, 108, 152, 84, 26, 168, 236, 225, 222, 143, 69, 140, 60, 201, 2, + //compute budget - unit limit + 4, 0, 9, 0, + 2, 0, 0, 0, // Units (lamports) = 2 + 1, 0, 0, 0, // Additional fee = 1, + //system - transfer + 3, 2, 1, 2, 12, 2, 0, 0, 0, + 5, // Transfer 5 lamports + 0, 0, 0, 0, 0, 0, 0 + }; + + PrintConfig print_config; + Parser parser = { message, sizeof(message) }; + assert(parse_message_header(&parser, &print_config.header) == 0); + + transaction_summary_reset(); + assert(process_message_body(parser.buffer, parser.buffer_length, &print_config) == 1); + +} + +void test_process_message_body_transfer_with_heap_frame(){ + + uint8_t message[] = { + 2, 0, 2, + 5, 21, 114, 229, 47, 44, 94, 126, 102, 188, 25, 172, 108, 211, 11, 109, 105, 110, 167, 153, 207, 230, 215, 132, 84, 42, 183, 216, 183, 254, 49, 91, 92, 151, 92, 21, 68, 212, 0, 50, 152, 29, 184, 10, 237, 93, 26, 195, 28, 41, 242, 83, 160, 179, 163, 125, 22, 218, 2, 189, 250, 180, 15, 129, 237, 85, 241, 243, 183, 151, 207, 142, 210, 108, 198, 224, 74, 55, 239, 133, 160, 214, 5, 194, 146, 151, 245, 221, 22, 236, 240, 44, 213, 111, 1, 175, 209, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3, 6, 70, 111, 229, 33, 23, 50, 255, 236, 173, 186, 114, 195, 155, 231, 188, 140, 229, 187, 197, 247, 18, 107, 44, 67, 155, 58, 64, 0, 0, 0, 8, 142, 237, 31, 151, 51, 227, 223, 112, 195, 240, 42, 185, 66, 83, 129, 10, 87, 175, 124, 84, 108, 23, 194, 79, 130, 56, 226, 197, 55, 25, 151, 2, + //compute budget - unit limit + 4, 0, 5, 1, + 204, 214, 90, 29, // Bytes 0x1D 0x5A 0xD6 0xCC + //system - transfer + 3, 2, 1, 2, 12, 2, 0, 0, 0, + 7, // Transfer 7 lamports + 0, 0, 0, 0, 0, 0, 0 + }; + + process_message_body_and_sanity_check(message, sizeof(message), 4); + +} + + + void test_process_message_body_nonced_stake_create_with_seed() { uint8_t message[] = { 2, 1, 4, @@ -1931,6 +2028,10 @@ int main() { test_process_message_body_stake_split_with_seed_v1_1(); test_process_message_body_stake_split_with_seed_v1_2(); test_process_message_body_stake_merge(); + test_process_message_body_transfer_with_compute_budget_limit(); + test_process_message_body_transfer_with_compute_budget_limit_and_unit_price(); + test_process_message_body_transfer_with_request_units(); + test_process_message_body_transfer_with_heap_frame(); printf("passed\n"); return 0; diff --git a/libsol/offchain_message_signing.c b/libsol/offchain_message_signing.c new file mode 100644 index 00000000..2b0e53f5 --- /dev/null +++ b/libsol/offchain_message_signing.c @@ -0,0 +1,5 @@ +#include "sol/offchain_message_signing.h" +#include "common_byte_strings.h" + +const OffchainMessageSigningDomain offchain_message_signing_domain = + {{OFFCHAIN_MESSAGE_SIGNING_DOMAIN}}; diff --git a/libsol/parser.c b/libsol/parser.c index 8f1ef419..d5c6cdb2 100644 --- a/libsol/parser.c +++ b/libsol/parser.c @@ -1,15 +1,12 @@ #include "sol/parser.h" +#include "sol/offchain_message_signing.h" #include "util.h" -#define OFFCHAIN_MESSAGE_SIGNING_DOMAIN \ - "\xff" \ - "solana offchain" - -static int check_buffer_length(Parser* parser, size_t num) { +int check_buffer_length(Parser* parser, size_t num) { return parser->buffer_length < num ? 1 : 0; } -static void advance(Parser* parser, size_t num) { +void advance(Parser* parser, size_t num) { parser->buffer += num; parser->buffer_length -= num; } @@ -106,9 +103,8 @@ int parse_pubkeys_header(Parser* parser, PubkeysHeader* header) { return 0; } -int parse_pubkeys(Parser* parser, PubkeysHeader* header, const Pubkey** pubkeys) { - BAIL_IF(parse_pubkeys_header(parser, header)); - size_t pubkeys_size = header->pubkeys_length * PUBKEY_SIZE; +int parse_pubkeys(Parser* parser, size_t num_pubkeys, const Pubkey** pubkeys) { + size_t pubkeys_size = num_pubkeys * PUBKEY_SIZE; BAIL_IF(check_buffer_length(parser, pubkeys_size)); *pubkeys = (const Pubkey*) parser->buffer; advance(parser, pubkeys_size); @@ -138,24 +134,50 @@ int parse_version(Parser* parser, MessageHeader* header) { int parse_message_header(Parser* parser, MessageHeader* header) { BAIL_IF(parse_version(parser, header)); - BAIL_IF(parse_pubkeys(parser, &header->pubkeys_header, &header->pubkeys)); + BAIL_IF(parse_pubkeys_header(parser, &header->pubkeys_header)); + BAIL_IF(parse_pubkeys(parser, header->pubkeys_header.pubkeys_length, &header->pubkeys)); BAIL_IF(parse_blockhash(parser, &header->blockhash)); BAIL_IF(parse_length(parser, &header->instructions_length)); return 0; } +int parse_offchain_message_application_domain( + Parser* parser, + const OffchainMessageApplicationDomain** app_domain +) { + BAIL_IF(check_buffer_length(parser, OFFCHAIN_MESSAGE_APPLICATION_DOMAIN_LENGTH)); + *app_domain = (const OffchainMessageApplicationDomain*) parser->buffer; + advance(parser, OFFCHAIN_MESSAGE_APPLICATION_DOMAIN_LENGTH); + return 0; +} + +/** + * Field Start offset Length (bytes) + * Signing Domain 0x00 16 + * Header version 0x10 1 + * Application domain 0x11 32 + * Message format 0x31 1 + * Signer count 0x32 1 + * Signers 0x33 SIGNER_COUNT * 32 + * Message length 0x33 + SIGNER_CNT * 32 2 + */ int parse_offchain_message_header(Parser* parser, OffchainMessageHeader* header) { - const size_t domain_len = strlen(OFFCHAIN_MESSAGE_SIGNING_DOMAIN); + const size_t domain_len = OFFCHAIN_MESSAGE_SIGNING_DOMAIN_LENGTH; BAIL_IF(check_buffer_length(parser, domain_len)); int res; - if ((res = memcmp(OFFCHAIN_MESSAGE_SIGNING_DOMAIN, parser->buffer, domain_len)) != 0) { + if ((res = memcmp((const void*)&offchain_message_signing_domain, parser->buffer, domain_len)) != 0) { return res; } - advance(parser, domain_len); - - BAIL_IF(parse_u8(parser, &header->version)); - BAIL_IF(parse_u8(parser, &header->format)); - BAIL_IF(parse_u16(parser, &header->length)); + advance(parser, domain_len);//Signing domain - 16 bytes + + BAIL_IF(parse_u8(parser, &header->version));// Header version + BAIL_IF(parse_offchain_message_application_domain(parser, &header->application_domain)); + BAIL_IF(parse_u8(parser, &header->format));// Message format + uint8_t signers_length = 0; + BAIL_IF(parse_u8(parser, &signers_length));// Signer count + header->signers_length = signers_length; + BAIL_IF(parse_pubkeys(parser, header->signers_length, &header->signers)); + BAIL_IF(parse_u16(parser, &header->length));// Message length return 0; } diff --git a/libsol/parser_test.c b/libsol/parser_test.c index 80b70941..a3a8d06b 100644 --- a/libsol/parser_test.c +++ b/libsol/parser_test.c @@ -1,6 +1,9 @@ +#include "common_byte_strings.h" #include "instruction.h" #include "parser.c" +#include "sol/parser.h" #include "sol/printer.h" +#include "sol/offchain_message_signing.h" #include #include #include @@ -246,22 +249,23 @@ void test_parse_pubkeys_header() { } void test_parse_pubkeys() { - uint8_t message[PUBKEY_SIZE + 4] = {1, 2, 3, 1, 42}; + uint8_t num_pubkeys = 1; + uint8_t message[] = {BYTES32_BS58_2}; Parser parser = {message, sizeof(message)}; - PubkeysHeader header; const Pubkey* pubkeys; - assert(parse_pubkeys(&parser, &header, &pubkeys) == 0); + assert(parse_pubkeys(&parser, num_pubkeys, &pubkeys) == 0); assert(parser_is_empty(&parser)); - assert(parser.buffer == message + PUBKEY_SIZE + 4); - assert(pubkeys->data[0] == 42); + assert(parser.buffer == message + ARRAY_LEN(message)); + const Pubkey expected_pubkey = {{BYTES32_BS58_2}}; + assert_pubkey_equal(&pubkeys[0], &expected_pubkey); } void test_parse_pubkeys_too_short() { - uint8_t message[] = {1, 2, 3, 1}; + uint8_t num_pubkeys = 1; + uint8_t message[] = {num_pubkeys}; Parser parser = {message, sizeof(message)}; - PubkeysHeader header; const Pubkey* pubkeys; - assert(parse_pubkeys(&parser, &header, &pubkeys) == 1); + assert(parse_pubkeys(&parser, num_pubkeys, &pubkeys) == 1); } void test_parse_hash() { @@ -320,6 +324,71 @@ void test_parser_is_empty() { assert(parser_is_empty(&empty)); } +#define TEST_OFFCHAIN_MESSAGE /* "test message" */ \ + 0x74, 0x65, 0x73, 0x74, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65 + +#define OFFCHAIN_MESSAGE_SIGNING_DOMAIN_CHANGED \ + 0x00, 0xaa, 0xbb, 0xcc, 0x00, 0xaa, 0xbb, 0xcc, 0x00, 0xaa, 0xbb, 0xcc + +void test_parse_offline_message_header_invalid_sgn_domain(){ + uint8_t test_message[] = {TEST_OFFCHAIN_MESSAGE}; + uint16_t message_length = ARRAY_LEN(test_message); + uint8_t message_length0 = message_length & 0x00ff; + uint8_t message_length1 = (message_length & 0xff00) >> 8; + uint8_t buf[] = { + OFFCHAIN_MESSAGE_SIGNING_DOMAIN_CHANGED, + 0, + BYTES32_BS58_2, + 0, + 1, + BYTES32_BS58_3, + message_length0, message_length1, + TEST_OFFCHAIN_MESSAGE + }; + + Parser parser = {buf, ARRAY_LEN(buf)}; + struct OffchainMessageHeader header; + //Parse header should return error - invalid signing domain + assert(parse_offchain_message_header(&parser, &header) > 0); +} + + +void test_parse_offline_message_header() { + uint8_t test_message[] = {TEST_OFFCHAIN_MESSAGE}; + uint16_t message_length = ARRAY_LEN(test_message); + uint8_t message_length0 = message_length & 0x00ff; + uint8_t message_length1 = (message_length & 0xff00) >> 8; + uint8_t buf[] = { + OFFCHAIN_MESSAGE_SIGNING_DOMAIN, + 0, + BYTES32_BS58_2, + 0, + 1, + BYTES32_BS58_3, + message_length0, message_length1, + TEST_OFFCHAIN_MESSAGE + }; + + Parser parser = {buf, ARRAY_LEN(buf)}; + struct OffchainMessageHeader header; + assert(parse_offchain_message_header(&parser, &header) == 0); + assert(header.version == 0); + const OffchainMessageApplicationDomain expected_application_domain = + {{BYTES32_BS58_2}}; + assert(memcmp( + header.application_domain, + &expected_application_domain, + OFFCHAIN_MESSAGE_APPLICATION_DOMAIN_LENGTH + ) == 0); + assert(header.format == 0); + assert(header.signers_length == 1); + const Pubkey expected_signer = {{BYTES32_BS58_3}}; + assert_pubkey_equal(&header.signers[0], &expected_signer); + assert(memcmp(parser.buffer, test_message, parser.buffer_length) == 0); + advance(&parser, message_length); + assert(parser_is_empty(&parser)); +} + int main() { test_parse_u8(); test_parse_u8_too_short(); @@ -340,6 +409,8 @@ int main() { test_parse_data_too_short(); test_parse_instruction(); test_parser_is_empty(); + test_parse_offline_message_header_invalid_sgn_domain(); + test_parse_offline_message_header(); printf("passed\n"); return 0; diff --git a/libsol/string_utils.c b/libsol/string_utils.c new file mode 100644 index 00000000..285c07b9 --- /dev/null +++ b/libsol/string_utils.c @@ -0,0 +1,68 @@ +#include "include/sol/string_utils.h" + + +/** + * Checks if data is in UTF-8 format. + * Adapted from: https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c + */ +bool is_data_utf8(const uint8_t *data, size_t length) { + if (!data) { + return false; + } + size_t i = 0; + while (i < length) { + if (data[i] < 0x80) { + /* 0xxxxxxx */ + ++i; + } else if ((data[i] & 0xe0) == 0xc0) { + /* 110XXXXx 10xxxxxx */ + if (i + 1 >= length || (data[i + 1] & 0xc0) != 0x80 || + (data[i] & 0xfe) == 0xc0) /* overlong? */ { + return false; + } else { + i += 2; + } + } else if ((data[i] & 0xf0) == 0xe0) { + /* 1110XXXX 10Xxxxxx 10xxxxxx */ + if (i + 2 >= length || (data[i + 1] & 0xc0) != 0x80 || (data[i + 2] & 0xc0) != 0x80 || + (data[i] == 0xe0 && (data[i + 1] & 0xe0) == 0x80) || /* overlong? */ + (data[i] == 0xed && (data[i + 1] & 0xe0) == 0xa0) || /* surrogate? */ + (data[i] == 0xef && data[i + 1] == 0xbf && + (data[i + 2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */ { + return false; + } else { + i += 3; + } + } else if ((data[i] & 0xf8) == 0xf0) { + /* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */ + if (i + 3 >= length || (data[i + 1] & 0xc0) != 0x80 || (data[i + 2] & 0xc0) != 0x80 || + (data[i + 3] & 0xc0) != 0x80 || + (data[i] == 0xf0 && (data[i + 1] & 0xf0) == 0x80) || /* overlong? */ + (data[i] == 0xf4 && data[i + 1] > 0x8f) || data[i] > 0xf4) /* > U+10FFFF? */ { + return false; + } else { + i += 4; + } + } else { + return false; + } + } + return true; +} + +/* + * Checks if data is in ASCII format + */ +bool is_data_ascii(const uint8_t *data, size_t length) { + if (!data) { + return false; + } + for (size_t i = 0; i < length; ++i) { + //Line feed char is accepted + if (((data[i] < 0x20 && data[i] != 0x0A) || data[i] > 0x7e)) { + return false; + } + } + return true; +} + diff --git a/libsol/string_utils_test.c b/libsol/string_utils_test.c new file mode 100644 index 00000000..9b214d16 --- /dev/null +++ b/libsol/string_utils_test.c @@ -0,0 +1,103 @@ +#include "include/sol/string_utils.h" +#include +#include +#include + +void test_is_ascii(){ + uint8_t message[] = "normal ascii text"; + // don't count 0x00 byte at the end + assert(is_data_ascii(message, sizeof(message) -1 ) == true); +} + +void test_is_ascii_invalid_end_char(){ + uint8_t message[] = "normal ascii text"; + + //Null terminated string should not be recognized as ascii + assert(is_data_ascii(message, sizeof(message) ) == false); +} + +void test_is_ascii_invalid_emoji(){ + uint8_t message[] = "๐Ÿ‘"; + + assert(is_data_ascii(message, sizeof(message) ) == false); +} + +void test_is_ascii_invalid_null(){ + uint8_t* message = NULL; + assert(is_data_ascii(message, sizeof(message)) == false); +} + + + + + +// test if emoji is going to be recognized as utf8 string +void test_is_utf8(){ + uint8_t message[] = "๐Ÿ‘"; + + assert(is_data_utf8(message, sizeof(message) ) == true); +} + +void test_is_utf8_2(){ + uint8_t message[] = "ลผรณล‚ฤ‡ ์•ˆ๋…•ํ•˜์„ธ์š” ะฟั€ะธะฒะตั‚"; + + assert(is_data_ascii(message, sizeof(message) -1 ) == false);//And we ignore null terminator + assert(is_data_utf8(message, sizeof(message) ) == true); +} + +void test_is_utf8_invalid_1(){ + //Invalid Sequence Identifier + uint8_t message[] = {0xa0, 0xa1}; + + assert(is_data_utf8(message, sizeof(message) ) == false); +} + +void test_is_utf8_invalid_overlong(){ + // Invalid UTF-8 (overlong) + uint8_t message[] = { 0xC0, 0xAF}; + uint8_t message2[] = { 0xC0, 0x80 }; + + assert(is_data_utf8(message, sizeof(message) ) == false); + assert(is_data_utf8(message2, sizeof(message2) ) == false); +} + +void test_is_utf8_invalid_surrogate(){ + // Invalid UTF-8 (surrogate) + uint8_t message[] = { 0xED, 0xA0, 0x80}; + + assert(is_data_utf8(message, sizeof(message) ) == false); +} + +void test_is_utf8_invalid_3(){ + uint8_t message1[] = { 0x80 }; // Invalid UTF-8 (starts with 10xxxxxx) + uint8_t message2[] = { 0xF4, 0x90, 0x80, 0x80 }; // Invalid UTF-8 (> U+10FFFF) + + assert(is_data_utf8(message1, sizeof(message1) ) == false); + assert(is_data_utf8(message2, sizeof(message2) ) == false); + +} + +void test_is_utf8_invalid_null(){ + uint8_t* message = NULL; + + assert(is_data_utf8(message, sizeof(message) ) == false); +} + + +int main(){ + test_is_ascii(); + test_is_ascii_invalid_end_char(); + test_is_ascii_invalid_emoji(); + test_is_ascii_invalid_null(); + + test_is_utf8(); + test_is_utf8_2(); + test_is_utf8_invalid_1(); + test_is_utf8_invalid_overlong(); + test_is_utf8_invalid_surrogate(); + test_is_utf8_invalid_3(); + test_is_utf8_invalid_null(); + + printf("passed\n"); + return 0; +} \ No newline at end of file diff --git a/libsol/system_instruction.c b/libsol/system_instruction.c index f32ef82a..87ecc8db 100644 --- a/libsol/system_instruction.c +++ b/libsol/system_instruction.c @@ -260,6 +260,7 @@ static int print_system_transfer_info(const SystemTransferInfo* info, SummaryItem* item; item = transaction_summary_primary_item(); + summary_item_set_amount(item, "Transfer", info->lamports); if (print_config_show_authority(print_config, info->from)) { diff --git a/libsol/transaction_printers.c b/libsol/transaction_printers.c index 7fab1aaf..596e68dd 100644 --- a/libsol/transaction_printers.c +++ b/libsol/transaction_printers.c @@ -532,6 +532,8 @@ static int print_spl_associated_token_account_create_with_transfer(const PrintCo return 0; } + + static int print_transaction_nonce_processed(const PrintConfig* print_config, InstructionInfo* const* infos, size_t infos_length) { @@ -552,6 +554,8 @@ static int print_transaction_nonce_processed(const PrintConfig* print_config, print_config); case ProgramIdSerumAssertOwner: case ProgramIdSplMemo: + // If pointers are advanced before, this case won't be needed + case ProgramIdComputeBudget: case ProgramIdUnknown: break; } @@ -634,5 +638,20 @@ int print_transaction(const PrintConfig* print_config, infos_length--; } + if (infos_length > 1) { + // Iterate over infos and print compute budget instructions and offset pointers + // Handle ComputeBudget instructions first due to teh limitations of the print_transaction_nonce_processed. + // We can get one or 4 ComputeBudget instructions in a single transaction, so we are not able to handle it in a static switch case. + size_t infos_length_initial = infos_length; + for (size_t info_idx = 0; info_idx < infos_length_initial; ++info_idx) { + InstructionInfo* instruction_info = infos[0]; + if (instruction_info->kind == ProgramIdComputeBudget) { + print_compute_budget(&instruction_info->compute_budget, print_config); + infos++; + infos_length--; + } + } + } + return print_transaction_nonce_processed(print_config, infos, infos_length); } diff --git a/libsol/transaction_summary.c b/libsol/transaction_summary.c index a30c78a9..0f09846a 100644 --- a/libsol/transaction_summary.c +++ b/libsol/transaction_summary.c @@ -15,6 +15,7 @@ struct SummaryItem { const char* string; SizedString sized_string; TokenAmount token_amount; + const OffchainMessageApplicationDomain* application_domain; }; }; @@ -67,18 +68,47 @@ void summary_item_set_sized_string(SummaryItem* item, const char* title, const S item->sized_string.string = value->string; } +/** + * Set the string value of a SummaryItem, but only if the value is not NULL. + */ +void summary_item_safe_set_string(SummaryItem* item, const char* title, const char* value) { + if(value != NULL){ + summary_item_set_string(item, title, value); + } +} + void summary_item_set_string(SummaryItem* item, const char* title, const char* value) { item->kind = SummaryItemString; item->title = title; item->string = value; } +void summary_item_safe_set_extended_string(SummaryItem* item, const char* title, const char* value){ + if(value != NULL){ + summary_item_set_extended_string(item, title, value); + } +} + +void summary_item_set_extended_string(SummaryItem* item, const char* title, const char* value) { + item->kind = SummaryItemExtendedString; + item->title = title; + item->string = value; +} + void summary_item_set_timestamp(SummaryItem* item, const char* title, int64_t value) { item->kind = SummaryItemTimestamp; item->title = title; item->i64 = value; } +void summary_item_set_offchain_message_application_domain(SummaryItem* item, + const char* title, + const OffchainMessageApplicationDomain* value) { + item->kind = SummaryItemOffchainMessageApplicationDomain; + item->title = title; + item->application_domain = value; +} + typedef struct TransactionSummary { SummaryItem primary; SummaryItem fee_payer; @@ -92,12 +122,16 @@ static TransactionSummary G_transaction_summary; char G_transaction_summary_title[TITLE_SIZE]; char G_transaction_summary_text[TEXT_BUFFER_LENGTH]; +char* G_transaction_summary_extended_text; + + void transaction_summary_reset() { explicit_bzero(&G_transaction_summary, sizeof(TransactionSummary)); explicit_bzero(&G_transaction_summary_title, TITLE_SIZE); explicit_bzero(&G_transaction_summary_text, TEXT_BUFFER_LENGTH); } + static bool is_summary_item_used(const SummaryItem* item) { return (item->kind != SummaryItemNone); } @@ -129,6 +163,9 @@ SummaryItem* transaction_summary_nonce_authority_item() { return summary_item_as_unused(item); } +/* + * Returns first available unused general item + */ SummaryItem* transaction_summary_general_item() { for (size_t i = 0; i < NUM_GENERAL_ITEMS; i++) { SummaryItem* item = &G_transaction_summary.general[i]; @@ -139,6 +176,20 @@ SummaryItem* transaction_summary_general_item() { return NULL; } +/* + * Returns number of used general items + */ +uint8_t transaction_summary_general_item_count() { + uint8_t count = 0; + for (size_t i = 0; i < NUM_GENERAL_ITEMS; i++) { + SummaryItem* item = &G_transaction_summary.general[i]; + if (is_summary_item_used(item)) { + count++; + } + } + return count; +} + #define FEE_PAYER_TITLE "Fee payer" int transaction_summary_set_fee_payer_pubkey(const Pubkey* pubkey) { SummaryItem* item = transaction_summary_fee_payer_item(); @@ -147,6 +198,19 @@ int transaction_summary_set_fee_payer_pubkey(const Pubkey* pubkey) { return 0; } +/* + * Supress warning about const qualifier - changes required in ledger's SDK + * warning suppression is used on purpose to avoid casting pointers without const qualifier + * G_ux.externalText is not modified anywhere + */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +static void set_extended_string(const char* value){ + //Set pointer to the temporary buffer, so 'libsol' does not have to include ux API from SDK + G_transaction_summary_extended_text = (char*) value; +} +#pragma GCC diagnostic pop + static int transaction_summary_update_display_for_item(const SummaryItem* item, enum DisplayFlags flags) { switch (item->kind) { @@ -188,6 +252,9 @@ static int transaction_summary_update_display_for_item(const SummaryItem* item, G_transaction_summary_text, TEXT_BUFFER_LENGTH)); break; + case SummaryItemExtendedString: + set_extended_string(item->string); + break; case SummaryItemString: print_string(item->string, G_transaction_summary_text, TEXT_BUFFER_LENGTH); break; @@ -197,6 +264,12 @@ static int transaction_summary_update_display_for_item(const SummaryItem* item, case SummaryItemTimestamp: BAIL_IF(print_timestamp(item->i64, G_transaction_summary_text, TEXT_BUFFER_LENGTH)); break; + case SummaryItemOffchainMessageApplicationDomain: + BAIL_IF(encode_base58(item->application_domain, + OFFCHAIN_MESSAGE_APPLICATION_DOMAIN_LENGTH, + G_transaction_summary_text, + TEXT_BUFFER_LENGTH)); + break; } print_string(item->title, G_transaction_summary_title, TITLE_SIZE); return 0; @@ -208,7 +281,6 @@ static int transaction_summary_update_display_for_item(const SummaryItem* item, // used summary->nonce_account // used summary->nonce_authority // summary->fee_payer - static SummaryItem* transaction_summary_find_item(size_t item_index) { struct TransactionSummary* summary = &G_transaction_summary; size_t current_index = 0; diff --git a/libsol/transaction_summary_test.c b/libsol/transaction_summary_test.c index 3acfa5dc..35883b2d 100644 --- a/libsol/transaction_summary_test.c +++ b/libsol/transaction_summary_test.c @@ -1,4 +1,5 @@ #include "common_byte_strings.h" +#include "sol/transaction_summary.h" #include "transaction_summary.c" #include #include diff --git a/libsol/util.h b/libsol/util.h index 3e041767..43fa3dc3 100644 --- a/libsol/util.h +++ b/libsol/util.h @@ -7,7 +7,10 @@ int err = x; \ if (err) return err; \ } while (0) -#define MIN(a, b) ((a) < (b) ? (a) : (b)); + +#ifndef MIN + #define MIN(a, b) ((a) < (b) ? (a) : (b)); +#endif #define assert_string_equal(actual, expected) assert(strcmp(actual, expected) == 0) diff --git a/src/globals.h b/src/globals.h index ee138595..fcbfff72 100644 --- a/src/globals.h +++ b/src/globals.h @@ -31,14 +31,28 @@ #define MAX_DERIVATION_PATH_BUFFER_LENGTH (1 + MAX_BIP32_PATH_LENGTH * 4) #define TOTAL_SIGN_MESSAGE_BUFFER_LENGTH (PACKET_DATA_SIZE + MAX_DERIVATION_PATH_BUFFER_LENGTH) +#define MAX_OFFCHAIN_MESSAGE_LENGTH PACKET_DATA_SIZE + +// 1232 + 32 + 1 + 1 + 32 + 1 + 16 +// Assuming that only one signer is present and that message is in ASCII format +#define OFFCHAIN_MESSAGE_HEADER_LENGTH 85 + +// Application buffer - no content reference value +#define OFFCHAIN_EMPTY_APPLICATION_DOMAIN 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + #define MAX_MESSAGE_LENGTH ROUND_TO_NEXT(TOTAL_SIGN_MESSAGE_BUFFER_LENGTH, USB_SEGMENT_SIZE) #define SIGNATURE_LENGTH 64 #define HASH_LENGTH 32 #define PUBKEY_LENGTH HASH_LENGTH #define PRIVATEKEY_LENGTH HASH_LENGTH -#define MAX_OFFCHAIN_MESSAGE_LENGTH (MAX_MESSAGE_LENGTH - 1 > 1212 ? 1212 : MAX_MESSAGE_LENGTH - 1) -#define OFFCHAIN_MESSAGE_HEADER_LENGTH 20 +// credit: https://stackoverflow.com/questions/807244/c-compiler-asserts-how-to-implement +#define CASSERT(predicate, file) _impl_CASSERT_LINE(predicate,__LINE__,file) +#define _impl_PASTE(a,b) a##b +#define _impl_CASSERT_LINE(predicate, line, file) \ + typedef char _impl_PASTE(assertion_failed_##file##_,line)[2*!!(predicate)-1]; typedef enum InstructionCode { // DEPRECATED - Use non "16" suffixed variants below diff --git a/src/handle_sign_message.c b/src/handle_sign_message.c index 37bce491..9c1caa8f 100644 --- a/src/handle_sign_message.c +++ b/src/handle_sign_message.c @@ -15,16 +15,12 @@ static int scan_header_for_signer(const uint32_t *derivation_path, uint32_t derivation_path_length, size_t *signer_index, const MessageHeader *header) { - uint8_t signer_pubkey[PUBKEY_SIZE]; - get_public_key(signer_pubkey, derivation_path, derivation_path_length); - for (size_t i = 0; i < header->pubkeys_header.num_required_signatures; ++i) { - const Pubkey *current_pubkey = &(header->pubkeys[i]); - if (memcmp(current_pubkey, signer_pubkey, PUBKEY_SIZE) == 0) { - *signer_index = i; - return 0; - } - } - return -1; + Pubkey signer_pubkey; + get_public_key(signer_pubkey.data, derivation_path, derivation_path_length); + return get_pubkey_index(&signer_pubkey, + header->pubkeys, + header->pubkeys_header.num_required_signatures, + signer_index); } void handle_sign_message_parse_message(volatile unsigned int *tx) { diff --git a/src/handle_sign_offchain_message.c b/src/handle_sign_offchain_message.c index 1b5a725d..df6444e3 100644 --- a/src/handle_sign_offchain_message.c +++ b/src/handle_sign_offchain_message.c @@ -3,84 +3,74 @@ #include "ux.h" #include "cx.h" #include "utils.h" +#include "sol/string_utils.h" #include "sol/parser.h" -#include "sol/printer.h" -#include "sol/print_config.h" -#include "sol/message.h" #include "sol/transaction_summary.h" #include "globals.h" #include "apdu.h" #include "handle_sign_offchain_message.h" #include "ui_api.h" -// Store locally the derived public key content -static Pubkey G_publicKey; -/** - * Checks if data is in UTF-8 format. - * Adapted from: https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c - */ -static bool is_data_utf8(const uint8_t *data, size_t length) { - if (!data) { - return false; +// ensure the command buffer has space to append a NUL terminal +CASSERT(MAX_OFFCHAIN_MESSAGE_LENGTH < MAX_MESSAGE_LENGTH, global_h); + +void ui_general(OffchainMessageHeader *header, bool is_ascii, Parser *parser) { + const uint8_t empty_application_domain[OFFCHAIN_MESSAGE_APPLICATION_DOMAIN_LENGTH] = { + OFFCHAIN_EMPTY_APPLICATION_DOMAIN}; + // Check if application domain buffer contains only zeroes + SummaryItem *item = transaction_summary_general_item(); + if (memcmp(header->application_domain, + empty_application_domain, + OFFCHAIN_MESSAGE_APPLICATION_DOMAIN_LENGTH) == 0) { + // Application buffer contains only zeroes - displaying base58 encoded string not needed + summary_item_set_string(item, "Application", "Domain not provided"); + } else { + summary_item_set_offchain_message_application_domain(item, + "Application", + header->application_domain); + } + + if (N_storage.settings.display_mode == DisplayModeExpert) { + summary_item_set_u64(transaction_summary_general_item(), "Version", header->version); + summary_item_set_u64(transaction_summary_general_item(), "Format", header->format); + summary_item_set_u64(transaction_summary_general_item(), "Size", header->length); + summary_item_set_hash(transaction_summary_general_item(), "Hash", &G_command.message_hash); + } else if (!is_ascii) { + summary_item_set_hash(transaction_summary_general_item(), "Hash", &G_command.message_hash); } - size_t i = 0; - while (i < length) { - if (data[i] < 0x80) { - /* 0xxxxxxx */ - ++i; - } else if ((data[i] & 0xe0) == 0xc0) { - /* 110XXXXx 10xxxxxx */ - if (i + 1 >= length || (data[i + 1] & 0xc0) != 0x80 || - (data[i] & 0xfe) == 0xc0) /* overlong? */ { - return false; - } else { - i += 2; - } - } else if ((data[i] & 0xf0) == 0xe0) { - /* 1110XXXX 10Xxxxxx 10xxxxxx */ - if (i + 2 >= length || (data[i + 1] & 0xc0) != 0x80 || (data[i + 2] & 0xc0) != 0x80 || - (data[i] == 0xe0 && (data[i + 1] & 0xe0) == 0x80) || /* overlong? */ - (data[i] == 0xed && (data[i + 1] & 0xe0) == 0xa0) || /* surrogate? */ - (data[i] == 0xef && data[i + 1] == 0xbf && - (data[i + 2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */ { - return false; - } else { - i += 3; - } - } else if ((data[i] & 0xf8) == 0xf0) { - /* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */ - if (i + 3 >= length || (data[i + 1] & 0xc0) != 0x80 || (data[i + 2] & 0xc0) != 0x80 || - (data[i + 3] & 0xc0) != 0x80 || - (data[i] == 0xf0 && (data[i + 1] & 0xf0) == 0x80) || /* overlong? */ - (data[i] == 0xf4 && data[i + 1] > 0x8f) || data[i] > 0xf4) /* > U+10FFFF? */ { - return false; - } else { - i += 4; - } - } else { - return false; - } + + if (is_ascii) { + item = transaction_summary_general_item(); + summary_item_set_extended_string(item, "Message", (const char *) parser->buffer); } - return true; } -/* - * Checks if data is in ASCII format - */ -static bool is_data_ascii(const uint8_t *data, size_t length) { - if (!data) { - return false; +void setup_ui(OffchainMessageHeader *header, bool is_ascii, Parser *parser, size_t signer_index) { + // fill out UI steps + transaction_summary_reset(); + SummaryItem *item = transaction_summary_primary_item(); + summary_item_set_string(item, "Sign", "Off-Chain Message"); + + item = transaction_summary_fee_payer_item(); + // First signer + summary_item_set_pubkey(item, "Signer", &header->signers[signer_index]); + + if (header->signers_length > 1) { + item = transaction_summary_general_item(); + summary_item_set_u64(item, "Other signers", header->signers_length); } - for (size_t i = 0; i < length; ++i) { - if (data[i] < 0x20 || data[i] > 0x7e) { - return false; - } + + ui_general(header, is_ascii, parser); + + enum SummaryItemKind summary_step_kinds[MAX_TRANSACTION_SUMMARY_ITEMS]; + size_t num_summary_steps = 0; + if (transaction_summary_finalize(summary_step_kinds, &num_summary_steps)) { + THROW(ApduReplySolanaSummaryFinalizeFailed); } - return true; -} -////////////////////////////////////////////////////////////////////// + start_sign_offchain_message_ui(num_summary_steps, summary_step_kinds); +} void handle_sign_offchain_message(volatile unsigned int *flags, volatile unsigned int *tx) { if (!tx || G_command.instruction != InsSignOffchainMessage || @@ -96,63 +86,53 @@ void handle_sign_offchain_message(volatile unsigned int *flags, volatile unsigne THROW(ApduReplySdkNotSupported); } + if (G_command.message_length > MAX_OFFCHAIN_MESSAGE_LENGTH) { + THROW(ApduReplySolanaInvalidMessageSize); + } + // parse header Parser parser = {G_command.message, G_command.message_length}; OffchainMessageHeader header; + Pubkey public_key; + if (parse_offchain_message_header(&parser, &header)) { THROW(ApduReplySolanaInvalidMessageHeader); } // validate message - if (header.version != 0 || header.format > 1 || header.length > MAX_OFFCHAIN_MESSAGE_LENGTH || - header.length + OFFCHAIN_MESSAGE_HEADER_LENGTH != G_command.message_length) { + if (header.version != 0 || header.format > 1 || header.length == 0 || + header.length != parser.buffer_length || header.signers_length == 0) { THROW(ApduReplySolanaInvalidMessageHeader); } - const bool is_ascii = - is_data_ascii(G_command.message + OFFCHAIN_MESSAGE_HEADER_LENGTH, header.length); - const bool is_utf8 = - is_ascii ? true - : is_data_utf8(G_command.message + OFFCHAIN_MESSAGE_HEADER_LENGTH, header.length); - if (!is_ascii && (!is_utf8 || header.format == 0)) { + + get_public_key(public_key.data, G_command.derivation_path, G_command.derivation_path_length); + + // assert that the requested signer exists in the signers list + size_t signer_index; + if (get_pubkey_index(&public_key, header.signers, header.signers_length, &signer_index) != 0) { + THROW(ApduReplySolanaInvalidMessageHeader); + } + + const bool is_ascii = is_data_ascii(parser.buffer, parser.buffer_length); + const bool is_utf8 = is_ascii ? true : is_data_utf8(parser.buffer, parser.buffer_length); + + if ((!is_ascii && header.format != 1) || !is_utf8) { + // Message has invalid header version or is not valid utf8 string THROW(ApduReplySolanaInvalidMessageFormat); } else if (!is_ascii && N_storage.settings.allow_blind_sign != BlindSignEnabled) { + // UTF-8 messages are allowed only with blind sign enabled THROW(ApduReplySdkNotSupported); } // compute message hash if needed if (!is_ascii || N_storage.settings.display_mode == DisplayModeExpert) { - cx_hash_sha256(G_command.message, - G_command.message_length, + cx_hash_sha256(parser.buffer, // Only message content is hashed + header.length, (uint8_t *) &G_command.message_hash, HASH_LENGTH); } - // fill out UX steps - transaction_summary_reset(); - SummaryItem *item = transaction_summary_primary_item(); - summary_item_set_string(item, "Sign", "Off-Chain Message"); - - if (N_storage.settings.display_mode == DisplayModeExpert) { - summary_item_set_u64(transaction_summary_general_item(), "Version", header.version); - summary_item_set_u64(transaction_summary_general_item(), "Format", header.format); - summary_item_set_u64(transaction_summary_general_item(), "Size", header.length); - summary_item_set_hash(transaction_summary_general_item(), "Hash", &G_command.message_hash); - - get_public_key(G_publicKey.data, - G_command.derivation_path, - G_command.derivation_path_length); - summary_item_set_pubkey(transaction_summary_general_item(), "Signer", &G_publicKey); - } else if (!is_ascii) { - summary_item_set_hash(transaction_summary_general_item(), "Hash", &G_command.message_hash); - } - - enum SummaryItemKind summary_step_kinds[MAX_TRANSACTION_SUMMARY_ITEMS]; - size_t num_summary_steps = 0; - if (transaction_summary_finalize(summary_step_kinds, &num_summary_steps)) { - THROW(ApduReplySolanaSummaryFinalizeFailed); - } - - start_sign_offchain_message_ui(is_ascii, num_summary_steps); + setup_ui(&header, is_ascii, &parser, signer_index); *flags |= IO_ASYNCH_REPLY; } diff --git a/src/ui/sign_message_bagl.c b/src/ui/sign_message_bagl.c index 22e81a39..6b635107 100644 --- a/src/ui/sign_message_bagl.c +++ b/src/ui/sign_message_bagl.c @@ -3,21 +3,9 @@ #include "io.h" #include "utils.h" #include "sol/parser.h" -#include "sol/printer.h" -#include "sol/print_config.h" -#include "sol/message.h" #include "sol/transaction_summary.h" #include "apdu.h" -#include "handle_sign_message.h" - -// Display offchain message screen -UX_STEP_NOCB(ux_sign_msg_text_step, - bnnn_paging, - { - .title = "Message", - .text = (const char *) G_command.message + OFFCHAIN_MESSAGE_HEADER_LENGTH, - }); // Display dynamic transaction item screen UX_STEP_NOCB_INIT(ux_summary_step, @@ -37,6 +25,21 @@ UX_STEP_NOCB_INIT(ux_summary_step, .text = G_transaction_summary_text, }); +UX_STEP_NOCB_INIT(ux_summary_step_extended, + bnnn_paging, + { + size_t step_index = G_ux.flow_stack[stack_slot].index; + if (transaction_summary_display_item(step_index, DisplayFlagNone)) { + THROW(ApduReplySolanaSummaryUpdateFailed); + } + G_ux.externalText = G_transaction_summary_extended_text; + }, + { + .title = G_transaction_summary_title, + //Text is set into the global buffer G_ux in transaction summary + }); + + // Approve and sign screen UX_STEP_CB(ux_approve_step, pb, @@ -60,6 +63,7 @@ UX_STEP_CB(ux_reject_step, + 1 /* reject */ \ + 1 /* FLOW_END_STEP */ \ ) + /* OFFCHAIN UX Steps: - Sign Message @@ -83,6 +87,13 @@ if ascii: ) static ux_flow_step_t const *flow_steps[MAX(MAX_FLOW_STEPS_ONCHAIN, MAX_FLOW_STEPS_OFFCHAIN)]; +void start_ui_common_end(size_t* num_flow_steps ){ + flow_steps[(*num_flow_steps)++] = &ux_reject_step; + flow_steps[(*num_flow_steps)++] = FLOW_END_STEP; + + ux_flow_init(0, flow_steps, NULL); +} + void start_sign_tx_ui(size_t num_summary_steps) { MEMCLEAR(flow_steps); size_t num_flow_steps = 0; @@ -91,26 +102,23 @@ void start_sign_tx_ui(size_t num_summary_steps) { } flow_steps[num_flow_steps++] = &ux_approve_step; - flow_steps[num_flow_steps++] = &ux_reject_step; - flow_steps[num_flow_steps++] = FLOW_END_STEP; - - ux_flow_init(0, flow_steps, NULL); + start_ui_common_end(&num_flow_steps); } -void start_sign_offchain_message_ui(bool is_ascii, size_t num_summary_steps) { + +void start_sign_offchain_message_ui(size_t num_summary_steps, const enum SummaryItemKind* summary_step_kinds) { MEMCLEAR(flow_steps); size_t num_flow_steps = 0; for (size_t i = 0; i < num_summary_steps; i++) { - flow_steps[num_flow_steps++] = &ux_summary_step; - } - if (is_ascii) { - flow_steps[num_flow_steps++] = &ux_sign_msg_text_step; + if(summary_step_kinds[i] == SummaryItemExtendedString){ + flow_steps[num_flow_steps++] = &ux_summary_step_extended; + }else{ + flow_steps[num_flow_steps++] = &ux_summary_step; + } } - flow_steps[num_flow_steps++] = &ux_approve_step; - flow_steps[num_flow_steps++] = &ux_reject_step; - flow_steps[num_flow_steps++] = FLOW_END_STEP; - ux_flow_init(0, flow_steps, NULL); + flow_steps[num_flow_steps++] = &ux_approve_step; + start_ui_common_end(&num_flow_steps); } #endif diff --git a/src/ui/ui_api.h b/src/ui/ui_api.h index 0c5cc054..5419521b 100644 --- a/src/ui/ui_api.h +++ b/src/ui/ui_api.h @@ -1,6 +1,8 @@ #pragma once #include "os.h" +#include "sol/parser.h" +#include "sol/transaction_summary.h" void ui_idle(void); @@ -8,4 +10,4 @@ void ui_get_public_key(void); void start_sign_tx_ui(size_t num_summary_steps); -void start_sign_offchain_message_ui(bool is_ascii, size_t num_summary_steps); +void start_sign_offchain_message_ui(size_t num_summary_steps, const enum SummaryItemKind* summary_step_kinds); diff --git a/src/utils.c b/src/utils.c index a1696bdf..9ffb44d8 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,6 +1,5 @@ #include "os.h" #include "cx.h" -#include #include #include "utils.h" @@ -31,6 +30,17 @@ void get_public_key(uint8_t *publicKeyArray, const uint32_t *derivationPath, siz } } +int get_pubkey_index(const Pubkey* needle, const Pubkey* haystack, size_t haystack_len, size_t* index) { + for (size_t i = 0; i < haystack_len; ++i) { + const Pubkey *current_pubkey = &(haystack[i]); + if (memcmp(current_pubkey, needle, PUBKEY_SIZE) == 0) { + *index = i; + return 0; + } + } + return -1; +} + uint32_t readUint32BE(uint8_t *buffer) { return ((buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | (buffer[3])); } diff --git a/src/utils.h b/src/utils.h index 6961b711..407e8ceb 100644 --- a/src/utils.h +++ b/src/utils.h @@ -23,6 +23,8 @@ typedef enum rlpTxType { void get_public_key(uint8_t *publicKeyArray, const uint32_t *derivationPath, size_t pathLength); +int get_pubkey_index(const Pubkey* needle, const Pubkey* haystack, size_t haystack_len, size_t* index); + uint32_t readUint32BE(uint8_t *buffer); void get_private_key(cx_ecfp_private_key_t *privateKey, From bdba90280a6a7f15e998d9d3599aa5c5da0fc584 Mon Sep 17 00:00:00 2001 From: Maciek Malik Date: Fri, 5 Jan 2024 14:14:34 +0100 Subject: [PATCH 2/2] Review fixes: * fixed comments formatting * removed unnecessary whitespace changes --- libsol/Makefile | 1 + libsol/common_byte_strings.h | 1 + libsol/include/sol/offchain_message_signing.h | 1 + libsol/include/sol/parser.h | 1 - libsol/include/sol/string_utils.h | 2 +- libsol/include/sol/transaction_summary.h | 2 -- libsol/parser.c | 17 +++++++++-------- libsol/transaction_summary.c | 2 +- 8 files changed, 14 insertions(+), 13 deletions(-) diff --git a/libsol/Makefile b/libsol/Makefile index ffbf3fed..1067343e 100644 --- a/libsol/Makefile +++ b/libsol/Makefile @@ -18,6 +18,7 @@ ifeq ($(COVERAGE),1) CFLAGS += --coverage endif +debug_CFLAGS = -g -fsanitize=address -fsanitize=undefined release_CFLAGS = -O2 libsol_source_files = $(filter-out %_test.c,$(wildcard *.c)) diff --git a/libsol/common_byte_strings.h b/libsol/common_byte_strings.h index 27101171..04f42451 100644 --- a/libsol/common_byte_strings.h +++ b/libsol/common_byte_strings.h @@ -83,6 +83,7 @@ 0x00, 0x00 // Sysvars + #define SYSVAR_RENT /* "SysvarRent111111111111111111111111111111111" */ \ 0x06, 0xa7, 0xd5, 0x17, 0x19, 0x2c, 0x5c, 0x51, 0x21, 0x8c, 0xc9, 0x4c, 0x3d, 0x4a, 0xf1, \ 0x7f, 0x58, 0xda, 0xee, 0x08, 0x9b, 0xa1, 0xfd, 0x44, 0xe3, 0xdb, 0xd9, 0x8a, 0x00, 0x00, \ diff --git a/libsol/include/sol/offchain_message_signing.h b/libsol/include/sol/offchain_message_signing.h index cd861fd7..2bcbed5a 100644 --- a/libsol/include/sol/offchain_message_signing.h +++ b/libsol/include/sol/offchain_message_signing.h @@ -11,6 +11,7 @@ * 5. Signer count (1 bytes) * 6. Signers (signer_count * 32 bytes) - assume that only one signer is present * 7. Message length (2 bytes) +* https://docs.solana.com/proposals/off-chain-message-signing */ typedef struct OffchainMessageSigningDomain { uint8_t data[OFFCHAIN_MESSAGE_SIGNING_DOMAIN_LENGTH]; diff --git a/libsol/include/sol/parser.h b/libsol/include/sol/parser.h index 1db634f0..317725f4 100644 --- a/libsol/include/sol/parser.h +++ b/libsol/include/sol/parser.h @@ -59,7 +59,6 @@ typedef struct MessageHeader { size_t instructions_length; } MessageHeader; -//@TODO move to offchain message sign .h #define OFFCHAIN_MESSAGE_APPLICATION_DOMAIN_LENGTH 32 typedef struct OffchainMessageApplicationDomain { uint8_t data[OFFCHAIN_MESSAGE_APPLICATION_DOMAIN_LENGTH]; diff --git a/libsol/include/sol/string_utils.h b/libsol/include/sol/string_utils.h index a1396b90..c28178f9 100644 --- a/libsol/include/sol/string_utils.h +++ b/libsol/include/sol/string_utils.h @@ -4,4 +4,4 @@ #include bool is_data_utf8(const uint8_t *data, size_t length); -bool is_data_ascii(const uint8_t *data, size_t length); \ No newline at end of file +bool is_data_ascii(const uint8_t *data, size_t length); diff --git a/libsol/include/sol/transaction_summary.h b/libsol/include/sol/transaction_summary.h index ca4c23c3..e3db81bc 100644 --- a/libsol/include/sol/transaction_summary.h +++ b/libsol/include/sol/transaction_summary.h @@ -57,8 +57,6 @@ typedef struct SummaryItem SummaryItem; extern char G_transaction_summary_title[TITLE_SIZE]; -// Text buffer needs to be large enough to hold the longest possible message text to sign -//#define TEXT_BUFFER_LENGTH (OFFCHAIN_MESSAGE_MAXIMUM_MESSAGE_LENGTH - OFFCHAIN_MESSAGE_MINIMAL_HEADER_SIZE) #define TEXT_BUFFER_LENGTH BASE58_PUBKEY_LENGTH extern char G_transaction_summary_text[TEXT_BUFFER_LENGTH]; diff --git a/libsol/parser.c b/libsol/parser.c index d5c6cdb2..f194f1b2 100644 --- a/libsol/parser.c +++ b/libsol/parser.c @@ -152,14 +152,15 @@ int parse_offchain_message_application_domain( } /** - * Field Start offset Length (bytes) - * Signing Domain 0x00 16 - * Header version 0x10 1 - * Application domain 0x11 32 - * Message format 0x31 1 - * Signer count 0x32 1 - * Signers 0x33 SIGNER_COUNT * 32 - * Message length 0x33 + SIGNER_CNT * 32 2 + * Field Start offset Length (bytes) + * Signing Domain 0x00 16 + * Header version 0x10 1 + * Application domain 0x11 32 + * Message format 0x31 1 + * Signer count 0x32 1 + * Signers 0x33 SIGNER_COUNT * 32 + * Message length 0x33 + SIGNER_CNT * 32 2 + * https://docs.solana.com/proposals/off-chain-message-signing */ int parse_offchain_message_header(Parser* parser, OffchainMessageHeader* header) { const size_t domain_len = OFFCHAIN_MESSAGE_SIGNING_DOMAIN_LENGTH; diff --git a/libsol/transaction_summary.c b/libsol/transaction_summary.c index 0f09846a..bde22014 100644 --- a/libsol/transaction_summary.c +++ b/libsol/transaction_summary.c @@ -199,7 +199,7 @@ int transaction_summary_set_fee_payer_pubkey(const Pubkey* pubkey) { } /* - * Supress warning about const qualifier - changes required in ledger's SDK + * Suppress warning about const qualifier - changes required in ledger's SDK * warning suppression is used on purpose to avoid casting pointers without const qualifier * G_ux.externalText is not modified anywhere */