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

Redo SPL send implementation following spec update #109

Open
wants to merge 1 commit into
base: fbe/sol_token_swap
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_build.yml@v1
with:
upload_app_binaries_artifact: compiled_app_binaries
flags: "TRUSTED_NAME_TEST=1"
flags: "TRUSTED_NAME_TEST_KEY=1"

ragger_tests:
name: Run ragger tests using the reusable workflow
Expand Down
16 changes: 12 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ APPNAME = "Solana"
APPVERSION_M = 1
APPVERSION_N = 7
APPVERSION_P = 0
APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)-splswapv1"
APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)-splswapv2"

# Application source files
APP_SOURCE_PATH += src
Expand Down Expand Up @@ -95,6 +95,7 @@ DISABLE_STANDARD_APP_FILES = 1
# Allow usage of function from lib_standard_app/crypto_helpers.c
APP_SOURCE_FILES += ${BOLOS_SDK}/lib_standard_app/crypto_helpers.c
APP_SOURCE_FILES += ${BOLOS_SDK}/lib_standard_app/swap_utils.c
APP_SOURCE_FILES += ${BOLOS_SDK}/lib_standard_app/base58.c
CFLAGS += -I${BOLOS_SDK}/lib_standard_app/

WITH_U2F?=0
Expand All @@ -104,6 +105,8 @@ ifneq ($(WITH_U2F),0)
SDK_SOURCE_PATH += lib_u2f
endif

DEFINES += HAVE_SDK_TLV_PARSER

WITH_LIBSOL?=1
ifneq ($(WITH_LIBSOL),0)
SOURCE_FILES += $(filter-out %_test.c,$(wildcard libsol/*.c))
Expand All @@ -116,9 +119,14 @@ endif
#######################################
# Trusted Name Test Mode #
#######################################
TRUSTED_NAME_TEST ?= 0
ifneq ($(TRUSTED_NAME_TEST),0)
DEFINES += HAVE_TRUSTED_NAME_TEST
TRUSTED_NAME_TEST_KEY ?= 0
ifneq ($(TRUSTED_NAME_TEST_KEY),0)
DEFINES += TRUSTED_NAME_TEST_KEY
endif

FIXED_TLV_CHALLENGE ?= 0
ifneq ($(FIXED_TLV_CHALLENGE),0)
DEFINES += FIXED_TLV_CHALLENGE
endif

include $(BOLOS_SDK)/Makefile.standard_app
4 changes: 2 additions & 2 deletions ledger_app.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ sdk = "C"
devices = ["nanox", "nanos+", "stax", "flex"]

[use_cases]
trusted_name_test = "TRUSTED_NAME_TEST=1"
dbg_trusted_name_test = "DEBUG=1 TRUSTED_NAME_TEST=1"
trusted_name_test = "TRUSTED_NAME_TEST_KEY=1"
dbg_trusted_name_test = "DEBUG=1 TRUSTED_NAME_TEST_KEY=1"

[tests]
pytest_directory = "./tests/python"
155 changes: 155 additions & 0 deletions libsol/ed25519_helpers.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#include <cx.h>

#include "common_byte_strings.h"

#include "ed25519_helpers.h"

static bool is_on_curve_internal(const uint8_t compressed_point[PUBKEY_LENGTH]) {
cx_ecpoint_t point;
cx_err_t result;
bool is_on_curve;
uint8_t compressed_point_tmp[PUBKEY_LENGTH];

memset(&point, 0, sizeof(point));
result = cx_ecpoint_alloc(&point, CX_CURVE_Ed25519);
if (result != CX_OK) {
PRINTF("cx_ecpoint_alloc failed %x\n", result);
return false;
}

// cx_decode_coord may hide the flag sign byte from the compressed point, we make a local copy
memcpy(compressed_point_tmp, compressed_point, PUBKEY_LENGTH);
int sign = cx_decode_coord(compressed_point_tmp, PUBKEY_LENGTH);
result = cx_ecpoint_decompress(&point, compressed_point_tmp, PUBKEY_LENGTH, sign);
if (result != CX_OK) {
PRINTF("cx_ecpoint_decompress failed %x\n", result);
return false;
}

result = cx_ecpoint_is_on_curve(&point, &is_on_curve);
if (result != CX_OK) {
PRINTF("cx_ecpoint_is_on_curve failed %x\n", result);
return false;
}
return is_on_curve;
}

bool is_on_curve(const uint8_t compressed_point[PUBKEY_LENGTH]) {
CX_ASSERT(cx_bn_lock(PUBKEY_LENGTH, 0));
bool is_on_curve = is_on_curve_internal(compressed_point);
if (cx_bn_is_locked()) {
CX_ASSERT(cx_bn_unlock());
}

return is_on_curve;
}

static int derivate_ata_candidate(const uint8_t *owner_account,
const uint8_t *mint_account,
uint8_t nonce,
uint8_t (*derived_ata_candidate)[PUBKEY_LENGTH]) {
cx_sha256_t hash_ctx;
uint8_t program_id_spl_token[32] = {PROGRAM_ID_SPL_TOKEN};
uint8_t program_id_spl_associated_token_account[32] = {PROGRAM_ID_SPL_ASSOCIATED_TOKEN_ACCOUNT};
const char program_derived_address[] = "ProgramDerivedAddress";

cx_sha256_init(&hash_ctx);

if (cx_hash_no_throw((cx_hash_t *) &hash_ctx, 0, owner_account, PUBKEY_LENGTH, NULL, 0) !=
CX_OK) {
PRINTF("ERROR: Failed to hash owner account\n");
return -1;
}

if (cx_hash_no_throw((cx_hash_t *) &hash_ctx,
0,
program_id_spl_token,
PUBKEY_LENGTH,
NULL,
0) != CX_OK) {
PRINTF("ERROR: Failed to hash program ID\n");
return -1;
}

if (cx_hash_no_throw((cx_hash_t *) &hash_ctx, 0, mint_account, PUBKEY_LENGTH, NULL, 0) !=
CX_OK) {
PRINTF("ERROR: Failed to hash mint account\n");
return -1;
}

if (cx_hash_no_throw((cx_hash_t *) &hash_ctx, 0, &nonce, sizeof(nonce), NULL, 0) != CX_OK) {
PRINTF("ERROR: Failed to hash nonce\n");
return -1;
}

if (cx_hash_no_throw((cx_hash_t *) &hash_ctx,
0,
program_id_spl_associated_token_account,
PUBKEY_LENGTH,
NULL,
0) != CX_OK) {
PRINTF("ERROR: Failed to hash program ID\n");
return -1;
}

if (cx_hash_no_throw((cx_hash_t *) &hash_ctx,
0,
(const uint8_t *) program_derived_address,
strlen(program_derived_address),
NULL,
0) != CX_OK) {
PRINTF("ERROR: Failed to hash ProgramDerivedAddress string\n");
return -1;
}

if (cx_hash_no_throw((cx_hash_t *) &hash_ctx,
CX_LAST,
NULL,
0,
*derived_ata_candidate,
PUBKEY_LENGTH) != CX_OK) {
PRINTF("ERROR: Failed to finalize hash\n");
return -1;
}

return 0;
}

bool validate_associated_token_address(const uint8_t owner_account[PUBKEY_LENGTH],
const uint8_t mint_account[PUBKEY_LENGTH],
const uint8_t provided_ata[PUBKEY_LENGTH]) {
uint8_t derived_ata[PUBKEY_LENGTH];

// Start with the maximum nonce value
// As this is how official libraries do, we minimize the number of checks of valid case
uint8_t nonce = 255;

PRINTF("Trying to validate provided_ata %.*H\n", PUBKEY_LENGTH, provided_ata);
while (nonce > 0) {
// Worst case scenario is 255 hash + 255 memcmp. The performance hit is not noticeable.
if (derivate_ata_candidate(owner_account, mint_account, nonce, &derived_ata) != 0) {
PRINTF("Error derivate_ata_candidate for nonce %d\n", nonce);
return false;
}
// Compare the derived ATA with the provided ATA
PRINTF("derived_ata %.*H with nonce%d\n", PUBKEY_LENGTH, derived_ata, nonce);
nonce--;

if (memcmp(derived_ata, provided_ata, PUBKEY_LENGTH) == 0) {
PRINTF("Successful ATA match\n");
// A valid ATA cannot be on the curve, check that the one we received is valid
// Official online libraries do it before the memcmp
// we do it after to do it at most once
if (is_on_curve(derived_ata)) {
PRINTF("Error, derived ATA is on the curve\n");
return false;
} else {
return true;
}
}
}

// We exhausted all nonces without matching the provided ATA
PRINTF("ERROR: Unable to find a valid nonce for ATA derivation\n");
return false;
}
9 changes: 9 additions & 0 deletions libsol/ed25519_helpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

#include "globals.h"

bool is_on_curve(const uint8_t compressed_point[PUBKEY_LENGTH]);

bool validate_associated_token_address(const uint8_t owner_account[PUBKEY_LENGTH],
const uint8_t mint_account[PUBKEY_LENGTH],
const uint8_t provided_ata[PUBKEY_LENGTH]);
18 changes: 18 additions & 0 deletions libsol/include/sol/trusted_info.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once

#include "sol/printer.h"

// libsol cannot include src.
// The libsol / src split should be reevaluated, in the meantime this lives here

typedef struct trusted_info_s {
bool received;
char encoded_owner_address[BASE58_PUBKEY_LENGTH];
uint8_t owner_address[PUBKEY_LENGTH];
char encoded_token_address[BASE58_PUBKEY_LENGTH];
uint8_t token_address[PUBKEY_LENGTH];
char encoded_mint_address[BASE58_PUBKEY_LENGTH];
uint8_t mint_address[PUBKEY_LENGTH];
} trusted_info_t;

extern trusted_info_t g_trusted_info;
61 changes: 54 additions & 7 deletions libsol/spl_token_instruction.c
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
#include "os.h"
#include "common_byte_strings.h"
#include "instruction.h"
#include "sol/parser.h"
#include "sol/transaction_summary.h"
#include "spl_token_instruction.h"
#include "token_info.h"
#include "util.h"
#include "base58.h"
#include "globals.h"
#include "ed25519_helpers.h"
#include "sol/trusted_info.h"

#include "spl_token_instruction.h"

const Pubkey spl_token_program_id = {{PROGRAM_ID_SPL_TOKEN}};

Expand Down Expand Up @@ -148,6 +154,46 @@ static int parse_transfer_spl_token_instruction(Parser* parser,

BAIL_IF(parse_spl_token_sign(&it, &info->sign));

// Here we will check the content of the SPL transaction against the received descriptor
if (!g_trusted_info.received) {
PRINTF("Descriptor info is required for a SPL transfer\n");
return -1;
}

// We have received a destination ATA, we will validate it by comparing it against the
// derivation of the owner address + mint address
// We must have received the owner address from the descriptor for this

PRINTF("=== TX INFO ===\n");
PRINTF("src_account = %.*H\n", PUBKEY_LENGTH, info->src_account->data);
PRINTF("mint_account = %.*H\n", PUBKEY_LENGTH, info->mint_account->data);
PRINTF("dest_account = %.*H\n", PUBKEY_LENGTH, info->dest_account->data);

PRINTF("=== TRUSTED INFO ===\n");
PRINTF("encoded_owner_address = %s\n", g_trusted_info.encoded_owner_address);
PRINTF("owner_address = %.*H\n", PUBKEY_LENGTH, g_trusted_info.owner_address);
PRINTF("encoded_token_address = %s\n", g_trusted_info.encoded_token_address);
PRINTF("token_address = %.*H\n", PUBKEY_LENGTH, g_trusted_info.token_address);
PRINTF("encoded_mint_address = %s\n", g_trusted_info.encoded_mint_address);
PRINTF("mint_address = %.*H\n", PUBKEY_LENGTH, g_trusted_info.mint_address);

if (memcmp(g_trusted_info.mint_address, info->mint_account->data, PUBKEY_LENGTH) != 0) {
PRINTF("Mint address does not match with mint address in descriptor\n");
return -1;
}

if (memcmp(g_trusted_info.token_address, info->dest_account->data, PUBKEY_LENGTH) != 0) {
PRINTF("Token address does not match with token address in descriptor\n");
return -1;
}

if (!validate_associated_token_address(g_trusted_info.owner_address,
info->mint_account->data,
info->dest_account->data)) {
PRINTF("Failed to validate ATA\n");
return -1;
}

return 0;
}

Expand Down Expand Up @@ -484,9 +530,6 @@ static int print_spl_token_initialize_multisig_info(const char* primary_title,
return 0;
}

uint8_t g_trusted_token_account_owner_pubkey[BASE58_PUBKEY_LENGTH];
bool g_trusted_token_account_owner_pubkey_set;

int print_spl_token_transfer_info(const SplTokenTransferInfo* info,
const PrintConfig* print_config,
bool primary) {
Expand All @@ -505,11 +548,15 @@ int print_spl_token_transfer_info(const SplTokenTransferInfo* info,
symbol,
info->body.decimals);

if (g_trusted_token_account_owner_pubkey_set) {
item = transaction_summary_general_item();
summary_item_set_string(item, "To", (char*) g_trusted_token_account_owner_pubkey);
// Already checked in parsing step but let's be secure
if (!g_trusted_info.received) {
PRINTF("Descriptor info is required for a SPL transfer\n");
return -1;
}

item = transaction_summary_general_item();
summary_item_set_string(item, "To", g_trusted_info.encoded_owner_address);

item = transaction_summary_general_item();
summary_item_set_pubkey(item, "Token address", info->mint_account);

Expand Down
Loading
Loading