Skip to content

Commit

Permalink
Merge pull request #48 from askibin/versioned_transactions
Browse files Browse the repository at this point in the history
Add versioned transactions support
  • Loading branch information
fbeutin-ledger authored Feb 7, 2023
2 parents 57aeca6 + a5c5ce8 commit bc5e535
Show file tree
Hide file tree
Showing 13 changed files with 696 additions and 244 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ APP_LOAD_PARAMS += $(COMMON_LOAD_PARAMS)
APPNAME = "Solana"
APPVERSION_M = 1
APPVERSION_N = 3
APPVERSION_P = 0
APPVERSION_P = 1
APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)"

ifeq ($(TARGET_NAME),TARGET_NANOS)
Expand Down
4 changes: 4 additions & 0 deletions doc/api.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Solana application : Common Technical Specifications

## 1.3.1

- Add support for versioned messages

## 1.3.0

- Add SIGN SOLANA OFF-CHAIN MESSAGE
Expand Down
29 changes: 26 additions & 3 deletions examples/example-sign.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,9 @@ async function solanaLedgerGetPubkey(transport, derivation_path) {
async function solanaLedgerSignTransaction(
transport,
derivation_path,
transaction
message
) {
const msg_bytes = transaction.compileMessage().serialize();
const msg_bytes = message.serialize();

// XXX: Ledger app only supports a single derivation_path per call ATM
var num_paths = Buffer.alloc(1);
Expand Down Expand Up @@ -325,7 +325,7 @@ async function solanaLedgerSignOffchainMessage(
let sig_bytes = await solanaLedgerSignTransaction(
transport,
from_derivation_path,
tx
tx.compileMessage()
);

let sig_string = bs58.encode(sig_bytes);
Expand All @@ -335,6 +335,29 @@ async function solanaLedgerSignOffchainMessage(
tx.addSignature(from_pubkey, sig_bytes);
console.log("Sig verifies:", tx.verifySignatures());

// create and sign versioned transfer transaction
const messageV0 = solana.MessageV0.compile({
addressLookupTableAccounts: [],
instructions: [ix],
payerKey: from_pubkey,
recentBlockhash,
});

sig_bytes = await solanaLedgerSignTransaction(
transport,
from_derivation_path,
messageV0
);
sig_string = bs58.encode(sig_bytes);
console.log("Sig len:", sig_bytes.length, "sig:", sig_string);

let verifies = nacl.sign.detached.verify(
messageV0.serialize(),
sig_bytes,
from_pubkey.toBuffer()
);
console.log("Sig verifies:", verifies);

// create and sign off-chain message in ascii
// TIP: enable expert mode in Ledger to see message details
let message = new OffchainMessage({
Expand Down
2 changes: 2 additions & 0 deletions libsol/include/sol/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ typedef struct PubkeysHeader {
} PubkeysHeader;

typedef struct MessageHeader {
bool versioned;
uint8_t version;
PubkeysHeader pubkeys_header;
const Pubkey* pubkeys;
const Blockhash* blockhash;
Expand Down
72 changes: 58 additions & 14 deletions libsol/instruction_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,51 +12,93 @@ void test_instruction_program_id_system() {
Pubkey program_id;
memcpy(&program_id, &system_program_id, PUBKEY_SIZE);
Instruction instruction = {0, NULL, 0, NULL, 0};
MessageHeader header = {{0, 0, 0, 1}, &program_id, NULL, 1};
assert(instruction_program_id(&instruction, &header) == ProgramIdSystem);
{
MessageHeader header = {false, 0, {0, 0, 0, 1}, &program_id, NULL, 1};
assert(instruction_program_id(&instruction, &header) == ProgramIdSystem);
}
{
MessageHeader header = {true, 0, {0, 0, 0, 1}, &program_id, NULL, 1};
assert(instruction_program_id(&instruction, &header) == ProgramIdSystem);
}
}

void test_instruction_program_id_stake() {
Pubkey program_id;
memcpy(&program_id, &stake_program_id, PUBKEY_SIZE);
Instruction instruction = {0, NULL, 0, NULL, 0};
MessageHeader header = {{0, 0, 0, 1}, &program_id, NULL, 1};
assert(instruction_program_id(&instruction, &header) == ProgramIdStake);
{
MessageHeader header = {false, 0, {0, 0, 0, 1}, &program_id, NULL, 1};
assert(instruction_program_id(&instruction, &header) == ProgramIdStake);
}
{
MessageHeader header = {true, 0, {0, 0, 0, 1}, &program_id, NULL, 1};
assert(instruction_program_id(&instruction, &header) == ProgramIdStake);
}
}

void test_instruction_program_id_unknown() {
Pubkey program_id = {{BYTES32_BS58_2}};
Instruction instruction = {0, NULL, 0, NULL, 0};
MessageHeader header = {{0, 0, 0, 1}, &program_id, NULL, 1};
assert(instruction_program_id(&instruction, &header) == ProgramIdUnknown);
{
MessageHeader header = {false, 0, {0, 0, 0, 1}, &program_id, NULL, 1};
assert(instruction_program_id(&instruction, &header) == ProgramIdUnknown);
}
{
MessageHeader header = {true, 0, {0, 0, 0, 1}, &program_id, NULL, 1};
assert(instruction_program_id(&instruction, &header) == ProgramIdUnknown);
}
}

void test_instruction_validate_ok() {
uint8_t accounts[] = {1, 2, 3};
Instruction instruction = {0, accounts, 3, NULL, 0};
MessageHeader header = {{0, 0, 0, 4}, NULL, NULL, 1};
assert(instruction_validate(&instruction, &header) == 0);
{
MessageHeader header = {false, 0, {0, 0, 0, 4}, NULL, NULL, 1};
assert(instruction_validate(&instruction, &header) == 0);
}
{
MessageHeader header = {true, 0, {0, 0, 0, 4}, NULL, NULL, 1};
assert(instruction_validate(&instruction, &header) == 0);
}
}

void test_instruction_validate_bad_program_id_index_fail() {
uint8_t accounts[] = {1, 2, 3};
Instruction instruction = {4, accounts, 3, NULL, 0};
MessageHeader header = {{0, 0, 0, 4}, NULL, NULL, 1};
assert(instruction_validate(&instruction, &header) == 1);
{
MessageHeader header = {false, 0, {0, 0, 0, 4}, NULL, NULL, 1};
assert(instruction_validate(&instruction, &header) == 1);
}
{
MessageHeader header = {true, 0, {0, 0, 0, 4}, NULL, NULL, 1};
assert(instruction_validate(&instruction, &header) == 1);
}
}

void test_instruction_validate_bad_first_account_index_fail() {
uint8_t accounts[] = {4, 2, 3};
Instruction instruction = {0, accounts, 3, NULL, 0};
MessageHeader header = {{0, 0, 0, 4}, NULL, NULL, 1};
assert(instruction_validate(&instruction, &header) == 1);
{
MessageHeader header = {false, 0, {0, 0, 0, 4}, NULL, NULL, 1};
assert(instruction_validate(&instruction, &header) == 1);
}
{
MessageHeader header = {true, 0, {0, 0, 0, 4}, NULL, NULL, 1};
assert(instruction_validate(&instruction, &header) == 1);
}
}

void test_instruction_validate_bad_last_account_index_fail() {
uint8_t accounts[] = {1, 2, 4};
Instruction instruction = {0, accounts, 3, NULL, 0};
MessageHeader header = {{0, 0, 0, 4}, NULL, NULL, 1};
assert(instruction_validate(&instruction, &header) == 1);
{
MessageHeader header = {false, 0, {0, 0, 0, 4}, NULL, NULL, 1};
assert(instruction_validate(&instruction, &header) == 1);
}
{
MessageHeader header = {true, 0, {0, 0, 0, 4}, NULL, NULL, 1};
assert(instruction_validate(&instruction, &header) == 1);
}
}

void test_static_brief_initializer_macros() {
Expand Down Expand Up @@ -133,6 +175,8 @@ void test_instruction_accounts_iterator_next() {
{{BYTES32_BS58_5}},
};
MessageHeader header = {
false,
0,
{0, 0, 0, ARRAY_LEN(header_pubkeys)},
header_pubkeys,
NULL,
Expand Down
6 changes: 6 additions & 0 deletions libsol/message.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ int process_message_body(const uint8_t* message_body,
}
}

if (header->versioned) {
size_t account_tables_length;
BAIL_IF(parse_length(&parser, &account_tables_length));
BAIL_IF(account_tables_length > 0);
}

// Ensure we've consumed the entire message body
BAIL_IF(!parser_is_empty(&parser));

Expand Down
18 changes: 9 additions & 9 deletions libsol/message_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ void test_process_message_body_ok() {
{{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}},
};
Blockhash blockhash = {{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}};
PrintConfig print_config = { .header = {{1, 0, 1, 3}, accounts, &blockhash, 1}, .expert_mode = true };
PrintConfig print_config = { .header = {false, 0, {1, 0, 1, 3}, accounts, &blockhash, 1}, .expert_mode = true };
uint8_t msg_body[] = {2, 2, 0, 1, 12, 2, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0};

transaction_summary_reset();
Expand All @@ -35,7 +35,7 @@ void test_process_message_body_xfer_w_nonce_ok() {
{{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}},
};
Blockhash blockhash = {{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}};
PrintConfig print_config = { .header = {{1, 0, 1, 3}, accounts, &blockhash, 2}, .expert_mode = true };
PrintConfig print_config = { .header = {false, 0, {1, 0, 1, 3}, accounts, &blockhash, 2}, .expert_mode = true };
uint8_t msg_body[] = {
2, 3, 0, 1, 0, 4, 4, 0, 0, 0, // Nonce
2, 2, 0, 1, 12, 2, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0
Expand All @@ -50,7 +50,7 @@ void test_process_message_body_xfer_w_nonce_ok() {
}

void test_process_message_body_too_few_ix_fail() {
PrintConfig print_config = { .header = {{0, 0, 0, 0}, NULL, NULL, 0}, .expert_mode = true };
PrintConfig print_config = { .header = {false, 0, {0, 0, 0, 0}, NULL, NULL, 0}, .expert_mode = true };
assert(process_message_body(NULL, 0, &print_config) == 1);
}

Expand All @@ -71,12 +71,12 @@ void test_process_message_body_too_many_ix_fail() {
uint8_t* start = msg_body + (i * XFER_IX_LEN);
memcpy(start, xfer_ix, XFER_IX_LEN);
}
PrintConfig print_config = { .header = {{1, 0, 1, 3}, accounts, &blockhash, TOO_MANY_IX}, .expert_mode = true };
PrintConfig print_config = { .header = {false, 0, {1, 0, 1, 3}, accounts, &blockhash, TOO_MANY_IX}, .expert_mode = true };
assert(process_message_body(msg_body, ARRAY_LEN(msg_body), &print_config) == 1);
}

void test_process_message_body_data_too_short_fail() {
PrintConfig print_config = { .header = {{0, 0, 0, 0}, NULL, NULL, 1}, .expert_mode = true };
PrintConfig print_config = { .header = {false, 0, {0, 0, 0, 0}, NULL, NULL, 1}, .expert_mode = true };
assert(process_message_body(NULL, 0, &print_config) == 1);
}

Expand All @@ -87,7 +87,7 @@ void test_process_message_body_data_too_long_fail() {
{{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}},
};
Blockhash blockhash = {{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}};
PrintConfig print_config = { .header = {{1, 0, 1, 3}, accounts, &blockhash, 1}, .expert_mode = true };
PrintConfig print_config = { .header = {false, 0, {1, 0, 1, 3}, accounts, &blockhash, 1}, .expert_mode = true };
uint8_t msg_body[] = {
2, 2, 0, 1, 12, 2, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0,
0
Expand All @@ -96,7 +96,7 @@ void test_process_message_body_data_too_long_fail() {
}

void test_process_message_body_bad_ix_account_index_fail() {
PrintConfig print_config = { .header = {{0, 0, 0, 1}, NULL, NULL, 1}, .expert_mode = true };
PrintConfig print_config = { .header = {false, 0, {0, 0, 0, 1}, NULL, NULL, 1}, .expert_mode = true };
uint8_t msg_body[] = {1, 0, 0};
assert(process_message_body(msg_body, ARRAY_LEN(msg_body), &print_config) == 1);
}
Expand All @@ -108,7 +108,7 @@ void test_process_message_body_unknown_ix_enum_fail() {
{{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}},
};
Blockhash blockhash = {{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}};
PrintConfig print_config = { .header = {{1, 0, 1, 3}, accounts, &blockhash, 1}, .expert_mode = true };
PrintConfig print_config = { .header = {false, 0, {1, 0, 1, 3}, accounts, &blockhash, 1}, .expert_mode = true };
uint8_t msg_body[] = {
2, 2, 0, 1, 12, 255, 255, 255, 255, 42, 0, 0, 0, 0, 0, 0, 0,
};
Expand All @@ -122,7 +122,7 @@ void test_process_message_body_ix_with_unknown_program_id_fail() {
{{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
};
Blockhash blockhash = {{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}};
PrintConfig print_config = { .header = {{1, 0, 1, 3}, accounts, &blockhash, 1}, .expert_mode = true };
PrintConfig print_config = { .header = {false, 0, {1, 0, 1, 3}, accounts, &blockhash, 1}, .expert_mode = true };
uint8_t msg_body[] = {
2, 2, 0, 1, 12, 2, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0,
};
Expand Down
15 changes: 15 additions & 0 deletions libsol/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,22 @@ int parse_hash(Parser* parser, const Hash** hash) {
return 0;
}

int parse_version(Parser* parser, MessageHeader* header) {
BAIL_IF(check_buffer_length(parser, 1));
const uint8_t version = *parser->buffer;
if (version & 0x80) {
header->versioned = true;
header->version = version & 0x7f;
advance(parser, 1);
} else {
header->versioned = false;
header->version = 0;
}
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_blockhash(parser, &header->blockhash));
BAIL_IF(parse_length(parser, &header->instructions_length));
Expand Down
2 changes: 1 addition & 1 deletion libsol/parser_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ void test_parse_instruction() {
Parser parser = {message, sizeof(message)};
Instruction instruction;
assert(parse_instruction(&parser, &instruction) == 0);
MessageHeader header = {{0, 0, 0, 35}, NULL, NULL, 1};
MessageHeader header = {false, 0, {0, 0, 0, 35}, NULL, NULL, 1};
assert(instruction_validate(&instruction, &header) == 0);
assert(parser_is_empty(&parser));
assert(instruction.accounts[0] == 33);
Expand Down
2 changes: 1 addition & 1 deletion libsol/stake_instruction_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ void test_parse_stake_initialize_instruction() {
};
memcpy(&pubkeys[2], &stake_program_id, PUBKEY_SIZE);
Blockhash blockhash = {{BYTES32_BS58_6}};
MessageHeader header = {{1, 0, 1, ARRAY_LEN(pubkeys)}, pubkeys, &blockhash, 1};
MessageHeader header = {false, 0, {1, 0, 1, ARRAY_LEN(pubkeys)}, pubkeys, &blockhash, 1};
uint8_t accounts[] = {0, 1};
uint8_t ix_data[] = {/* kind */
0x00,
Expand Down
2 changes: 2 additions & 0 deletions libsol/system_instruction_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ void test_system_create_account_with_seed_instruction() {
memcpy(&pubkeys[2], &system_program_id, PUBKEY_SIZE);
Blockhash blockhash = {{BYTES32_BS58_5}};
MessageHeader header = {
false,
0,
{2, 0, 0, ARRAY_LEN(pubkeys)},
pubkeys,
&blockhash,
Expand Down
Loading

0 comments on commit bc5e535

Please sign in to comment.