Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
fbeutin-ledger committed Jan 21, 2025
1 parent c5cdd16 commit 26bbe8b
Show file tree
Hide file tree
Showing 48 changed files with 1,222 additions and 868 deletions.
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

0 comments on commit 26bbe8b

Please sign in to comment.