diff --git a/Makefile b/Makefile index 9a910bff..d5d9aab0 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) diff --git a/libsol/common_byte_strings.h b/libsol/common_byte_strings.h index b38c675b..04f42451 100644 --- a/libsol/common_byte_strings.h +++ b/libsol/common_byte_strings.h @@ -88,3 +88,8 @@ 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/include/sol/offchain_message_signing.h b/libsol/include/sol/offchain_message_signing.h new file mode 100644 index 00000000..2bcbed5a --- /dev/null +++ b/libsol/include/sol/offchain_message_signing.h @@ -0,0 +1,21 @@ +#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) +* https://docs.solana.com/proposals/off-chain-message-signing +*/ +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..317725f4 100644 --- a/libsol/include/sol/parser.h +++ b/libsol/include/sol/parser.h @@ -59,9 +59,17 @@ typedef struct MessageHeader { size_t instructions_length; } MessageHeader; +#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 +77,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 +98,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..c28178f9 --- /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); diff --git a/libsol/include/sol/transaction_summary.h b/libsol/include/sol/transaction_summary.h index b0235344..e3db81bc 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 // @@ -46,6 +47,8 @@ enum SummaryItemKind { SummaryItemSizedString, SummaryItemString, SummaryItemTimestamp, + SummaryItemOffchainMessageApplicationDomain, + SummaryItemExtendedString, }; typedef enum SummaryItemKind SummaryItemKind_t; @@ -53,9 +56,13 @@ typedef enum SummaryItemKind SummaryItemKind_t; typedef struct SummaryItem SummaryItem; extern char G_transaction_summary_title[TITLE_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, @@ -71,6 +78,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(); + + + + @@ -90,4 +103,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/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..f194f1b2 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,51 @@ 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 + * https://docs.solana.com/proposals/off-chain-message-signing + */ 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/transaction_summary.c b/libsol/transaction_summary.c index a74a0458..bde22014 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,6 +122,9 @@ 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); @@ -130,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]; @@ -140,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(); @@ -148,6 +198,19 @@ int transaction_summary_set_fee_payer_pubkey(const Pubkey* pubkey) { return 0; } +/* + * 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 + */ +#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) { @@ -189,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; @@ -198,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; 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,