Skip to content
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

Extended signing support for Off-chain message signing #78

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 0 additions & 1 deletion .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ SortIncludes: false
SpaceAfterCStyleCast: true
AllowShortCaseLabelsOnASingleLine: false
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortFunctionsOnASingleLine: None
BinPackArguments: false
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ __pycache__
*~

tests/python/snapshots-tmp/

.DS_Store

/*.html
/*.css
15 changes: 13 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
ifeq ($(BOLOS_SDK),)
# `THIS_DIR` must be resolved BEFORE any `include` directives
THIS_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
TARGET_SDK := $(shell ./util/read-last-sdk)
BOLOS_SDK := ${$(TARGET_SDK)}
TARGET_SDK := $(shell ./util/read-last-sdk)
BOLOS_SDK := ${$(TARGET_SDK)}
endif

ifeq ($(BOLOS_SDK),)
Expand Down Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Current Features:
### For building the app
* [Install Docker](https://docs.docker.com/get-docker/)
* For Linux hosts, install the Ledger Nano [udev rules](https://github.com/LedgerHQ/udev-rules)
#### Build the [Ledger App Builder](https://developers.ledger.com/docs/nano-app/build/) Docker image
#### Build the [Ledger App Builder](https://github.com/LedgerHQ/ledger-app-builder/blob/master/README.md#build-the-container-image) Docker image
1. Clone the git repository
```
git clone https://github.com/LedgerHQ/ledger-app-builder.git
Expand All @@ -32,8 +32,8 @@ git checkout 73c9e07
```
docker build -t ledger-app-builder:73c9e07 .
```
* If permissions errors are encountered, ensure that your user is in the `docker`
group and that the session has been restarted
* If permissions errors are encountered, ensure that your user is in the `docker`
group and that the session has been restarted

### For working with the device
#### Install Python3 PIP
Expand Down Expand Up @@ -81,7 +81,7 @@ git clone --branch 1.0.3 --depth 1 https://github.com/LedgerHQ/nanosplus-secure-
* Solana [system dependencies](https://github.com/solana-labs/solana/#1-install-rustc-cargo-and-rustfmt)

## Build
It is highly recommended that you read and understand the [Ledger App Builder](https://developers.ledger.com/docs/embedded-app/build-app/#2-build-the-application)
It is highly recommended that you read and understand the [Ledger App Builder](https://github.com/LedgerHQ/ledger-app-builder/blob/master/README.md)
build process before proceeding. A convenience wrapper script (`./docker-make`) has been provided for simplicity

`docker-make` manages the current target SDK for you, automatically setting `BOLOS_SDK` to the
Expand All @@ -104,7 +104,7 @@ from clean and clean must be run _before_ switching
```

## Working with the device
Requires that the `BOLOS_SDK` envvar [be set](https://developers.ledger.com/docs/embedded-app/build-app/#b-build-the-application).
Requires that the `BOLOS_SDK` envvar be set.
This can be achieved by first [building](#build) for the desired target device.
### Load
```bash
Expand Down
7 changes: 6 additions & 1 deletion libsol/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ ifeq ($(COVERAGE),1)
CFLAGS += --coverage
endif

debug_CFLAGS = -g -fsanitize=address -fsanitize=undefined
# Temporary solution to ignore conditional compilation for nanos
ifeq ($(TEST_REDUCE_MEMORY),)
CFLAGS += -DTEST_MEMORY_EXTENDED
endif

debug_CFLAGS = -g -O0 -fsanitize=address -fsanitize=undefined
release_CFLAGS = -O2

libsol_source_files = $(filter-out %_test.c,$(wildcard *.c))
Expand Down
13 changes: 12 additions & 1 deletion libsol/common_byte_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@
0x82, 0xb3, 0x03, 0x1c, 0x5c, 0xce, 0xbc, 0x9b, 0x34, 0xe8, 0xcc, 0xcc, 0x11, 0xf7, 0x04, \
0x7d, 0xc1

#define BYTES32_BS58_9 /* "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

// Program IDs

#define PROGRAM_ID_SPL_TOKEN /* "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" */ \
Expand Down Expand Up @@ -77,9 +82,15 @@
0x7c, 0x7c, 0x35, 0xb5, 0xdd, 0xbc, 0x92, 0xbb, 0x81, 0xe4, 0x1f, 0xa8, 0x40, 0x41, 0x05, \
0x44, 0x8d

// Sysvars
#define PROGRAM_ID_COMPUTE_BUDGET BYTES32_BS58_9

// 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
68 changes: 68 additions & 0 deletions libsol/compute_budget_instruction.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include "common_byte_strings.h"
#include "sol/parser.h"
#include "compute_budget_instruction.h"
#include "util.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 ComputeBudgetRequestUnits:
case ComputeBudgetRequestHeapFrame:
case ComputeBudgetChangeUnitLimit:
case ComputeBudgetChangeUnitPrice:
*kind = (enum ComputeBudgetInstructionKind) maybe_kind;
return 0;
default:
return 1;
}
}

static int parse_request_units_instruction(Parser* parser, ComputeBudgetRequestUnitsInfo* info) {
BAIL_IF(parse_u32(parser, &info->units));
BAIL_IF(parse_u32(parser, &info->additional_fee));

return 0;
}

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_u32(parse, &info->units));

return 0;
}

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 ComputeBudgetRequestUnits:
return parse_request_units_instruction(&parser, &info->request_units);
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);
default:
return 1;
}
}

41 changes: 41 additions & 0 deletions libsol/compute_budget_instruction.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once

#include "sol/parser.h"

extern const Pubkey compute_budget_program_id;

enum ComputeBudgetInstructionKind {
ComputeBudgetRequestUnits = 0,
ComputeBudgetRequestHeapFrame,
ComputeBudgetChangeUnitLimit,
ComputeBudgetChangeUnitPrice
};

typedef struct ComputeBudgetRequestUnitsInfo {
uint32_t units;
uint32_t additional_fee;
} ComputeBudgetRequestUnitsInfo;

typedef struct ComputeBudgetRequestHeapFrameInfo {
uint32_t bytes;
} ComputeBudgetRequestHeapFrameInfo;

typedef struct ComputeBudgetChangeUnitLimitInfo {
uint32_t units;
} ComputeBudgetChangeUnitLimitInfo;

typedef struct ComputeBudgetChangeUnitPriceInfo {
uint32_t units;
} ComputeBudgetChangeUnitPriceInfo;

typedef struct ComputeBudgetInfo {
enum ComputeBudgetInstructionKind kind;
union {
ComputeBudgetRequestUnitsInfo request_units;
ComputeBudgetRequestHeapFrameInfo request_heap_frame;
ComputeBudgetChangeUnitLimitInfo change_unit_limit;
ComputeBudgetChangeUnitPriceInfo change_unit_price;
};
} ComputeBudgetInfo;

int parse_compute_budget_instructions(const Instruction* instruction, ComputeBudgetInfo* info);
72 changes: 72 additions & 0 deletions libsol/compute_budget_instruction_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#include "compute_budget_instruction.c"
#include "include/sol/parser.h"
#include <assert.h>
#include <stdio.h>

void test_parse_compute_budget_instruction_kind() {
uint8_t message[] = {0, 1, 2, 3};

enum ComputeBudgetInstructionKind instruction_kind_target;

Parser parser = {message, sizeof(message)};

// ComputeBudgetRequestUnits
assert(parse_compute_budget_instruction_kind(&parser, &instruction_kind_target) == 0);
assert(instruction_kind_target == ComputeBudgetRequestUnits);

// 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);

// Buffer empty, should fail
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;
}
20 changes: 20 additions & 0 deletions libsol/include/sol/offchain_message_signing.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#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;

19 changes: 18 additions & 1 deletion libsol/include/sol/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,28 @@ 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;

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);
Expand All @@ -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);
Expand Down
7 changes: 7 additions & 0 deletions libsol/include/sol/string_utils.h
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);
Loading