-
Notifications
You must be signed in to change notification settings - Fork 2
Added Off Chain message signing #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
37e19e7
5331908
11f8a06
028bfc0
3d081f3
1c81b50
9dca0dc
792bbd7
a51f3a4
e9ac0db
cac73a1
ce1371a
e6c5b44
769b843
1d803c5
fb5a6c1
bc3237f
595b233
0ae90fb
c1d6b52
abe02a9
0268176
eafb75e
7908f8a
556cb54
5d5d4aa
1ce1615
a643d98
056203f
dd38637
e0c7884
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| #pragma once | ||
| #include <stdint.h> | ||
|
|
||
| #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; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| #pragma once | ||
| #include <stdint.h> | ||
| #include <string.h> | ||
| #include <stdbool.h> | ||
|
|
||
| bool is_data_utf8(const uint8_t *data, size_t length); | ||
| bool is_data_ascii(const uint8_t *data, size_t length); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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}}; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,7 @@ | ||
| #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) { | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved out to |
||
| return parser->buffer_length < num ? 1 : 0; | ||
| } | ||
|
|
@@ -115,6 +112,14 @@ int parse_pubkeys(Parser *parser, PubkeysHeader *header, const Pubkey **pubkeys) | |
| return 0; | ||
| } | ||
|
|
||
| int parse_pubkeys_with_len(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); | ||
| return 0; | ||
| } | ||
|
|
||
| int parse_hash(Parser *parser, const Hash **hash) { | ||
| BAIL_IF(check_buffer_length(parser, HASH_SIZE)); | ||
| *hash = (const Hash *) parser->buffer; | ||
|
|
@@ -136,26 +141,43 @@ int parse_version(Parser *parser, MessageHeader *header) { | |
| 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; | ||
| } | ||
|
|
||
| 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_with_len(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_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_with_len(parser, header->signers_length, &header->signers)); | ||
| BAIL_IF(parse_u16(parser, &header->length)); // Message length | ||
| return 0; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| #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; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| #include "include/sol/string_utils.h" | ||
| #include <assert.h> | ||
| #include <stdio.h> | ||
| #include <stdbool.h> | ||
|
|
||
| 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; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ struct SummaryItem { | |
| const char *string; | ||
| SizedString sized_string; | ||
| TokenAmount token_amount; | ||
| const OffchainMessageApplicationDomain *application_domain; | ||
| }; | ||
| }; | ||
|
|
||
|
|
@@ -79,6 +80,15 @@ void summary_item_set_timestamp(SummaryItem *item, const char *title, int64_t va | |
| 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; | ||
| } | ||
|
|
||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New item required for showing app domain |
||
| typedef struct TransactionSummary { | ||
| SummaryItem primary; | ||
| SummaryItem fee_payer; | ||
|
|
@@ -197,6 +207,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; | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handling new item type |
||
| } | ||
| print_string(item->title, G_transaction_summary_title, TITLE_SIZE); | ||
| return 0; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That util functions are required for parsing off chain payload