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

Implement swap with calldata #627

Merged
merged 2 commits into from
Aug 23, 2024
Merged
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [1.12.0](https://github.com/ledgerhq/app-ethereum/compare/1.11.0...1.11.1) - 2024-??-??

### Added

- Added support for swap with calldata (Thorswap / LiFi / ...)

## [1.11.2](https://github.com/ledgerhq/app-ethereum/compare/1.11.1...1.11.2) - 2024-08-13

### Added
Expand Down
14 changes: 11 additions & 3 deletions src/eth_plugin_handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "plugin_utils.h"
#include "shared_context.h"
#include "network.h"
#include "cmd_setPlugin.h"

void eth_plugin_prepare_init(ethPluginInitContract_t *init,
const uint8_t *selector,
Expand Down Expand Up @@ -153,10 +154,13 @@ eth_plugin_result_t eth_plugin_perform_init(uint8_t *contractAddress,
break;
}

// Do not handle a plugin if running in swap mode
if (G_called_from_swap && (contractAddress != NULL)) {
PRINTF("eth_plug_init aborted in swap mode\n");
return 0;
PRINTF("contractAddress == %.*H\n", 20, contractAddress);
PRINTF("selector == %.*H\n", 20, contractAddress);
PRINTF("Fallback on swap_with_calldata plugin\n");
set_swap_with_calldata_plugin_type();
dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_OK;
contractAddress = NULL;
}

eth_plugin_result_t status = ETH_PLUGIN_RESULT_UNAVAILABLE;
Expand Down Expand Up @@ -271,6 +275,10 @@ eth_plugin_result_t eth_plugin_call(int method, void *parameter) {
END_TRY;
break;
}
case SWAP_WITH_CALLDATA: {
swap_with_calldata_plugin_call(method, parameter);
break;
}
#ifdef HAVE_NFT_SUPPORT
case ERC721: {
erc721_plugin_call(method, parameter);
Expand Down
1 change: 1 addition & 0 deletions src/eth_plugin_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

void erc721_plugin_call(int message, void* parameters);
void erc1155_plugin_call(int message, void* parameters);
void swap_with_calldata_plugin_call(int message, void* parameters);

typedef bool (*const PluginAvailableCheck)(void);
typedef void (*PluginCall)(int, void*);
Expand Down
53 changes: 53 additions & 0 deletions src/handle_swap_sign_transaction.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,37 @@
#include "nbgl_use_case.h"
#endif // HAVE_NBGL

// Remember if we have been started by the Exchange application or not
bool G_called_from_swap;
cedelavergne-ledger marked this conversation as resolved.
Show resolved Hide resolved

// Set this boolean when a transaction is signed in Swap mode. Safety against double sign
bool G_swap_response_ready;

// Save the BSS address where we will write the return value when finished
static uint8_t* G_swap_sign_return_value_address;

// Standard or crosschain swap type
swap_mode_t G_swap_mode;

// On crosschain swap, save the hash promised by the partner
uint8_t G_swap_crosschain_hash[CX_SHA256_SIZE];

typedef enum extra_id_type_e {
EXTRA_ID_TYPE_NATIVE,
EXTRA_ID_TYPE_EVM_CALLDATA,
// There are others but they are not relevant for the Ethereum application
} extra_id_type_t;

bool copy_transaction_parameters(create_transaction_parameters_t* sign_transaction_params,
const chain_config_t* config) {
// first copy parameters to stack, and then to global data.
// We need this "trick" as the input data position can overlap with app-ethereum globals
txStringProperties_t stack_data;
uint8_t destination_address_extra_data[CX_SHA256_SIZE + 1];

memset(&stack_data, 0, sizeof(stack_data));
memset(destination_address_extra_data, 0, sizeof(destination_address_extra_data));

strlcpy(stack_data.toAddress,
sign_transaction_params->destination_address,
sizeof(stack_data.toAddress));
Expand All @@ -28,6 +50,12 @@ bool copy_transaction_parameters(create_transaction_parameters_t* sign_transacti
return false;
}

if (sign_transaction_params->destination_address_extra_id != NULL) {
memcpy(destination_address_extra_data,
sign_transaction_params->destination_address_extra_id,
sizeof(destination_address_extra_data));
}

char ticker[MAX_TICKER_LEN];
uint8_t decimals;
uint64_t chain_id = 0;
Expand Down Expand Up @@ -71,6 +99,31 @@ bool copy_transaction_parameters(create_transaction_parameters_t* sign_transacti
G_swap_sign_return_value_address = &sign_transaction_params->result;
// Commit the values read from exchange to the clean global space

// if destination_address_extra_id is given, we use the first byte to determine if we use the
// normal swap protocol, or the one for cross-chain swaps
switch (destination_address_extra_data[0]) {
case EXTRA_ID_TYPE_NATIVE:
G_swap_mode = SWAP_MODE_STANDARD;
PRINTF("Standard swap\n");

// we don't use the payin_extra_id field in this mode
explicit_bzero(G_swap_crosschain_hash, sizeof(G_swap_crosschain_hash));
break;
case EXTRA_ID_TYPE_EVM_CALLDATA:
G_swap_mode = SWAP_MODE_CROSSCHAIN_PENDING_CHECK;

memcpy(G_swap_crosschain_hash,
destination_address_extra_data + 1,
sizeof(G_swap_crosschain_hash));

PRINTF("Crosschain swap with hash: %.*H\n", CX_SHA256_SIZE, G_swap_crosschain_hash);
break;
default:
// We can't return errors from here, we remember that we have an issue to report later
PRINTF("Invalid or unknown swap protocol\n");
G_swap_mode = SWAP_MODE_ERROR;
}

memcpy(&strings.common, &stack_data, sizeof(stack_data));
return true;
}
Expand Down
2 changes: 0 additions & 2 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ cx_sha3_t global_sha3;

uint8_t appState;
uint16_t apdu_response_code;
bool G_called_from_swap;
bool G_swap_response_ready;
pluginType_t pluginType;

#ifdef HAVE_ETH2
Expand Down
24 changes: 20 additions & 4 deletions src/shared_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,14 +166,30 @@ extern strings_t strings;
extern cx_sha3_t global_sha3;
extern const internalStorage_t N_storage_real;

typedef enum swap_mode_e {
SWAP_MODE_STANDARD,
SWAP_MODE_CROSSCHAIN_PENDING_CHECK,
SWAP_MODE_CROSSCHAIN_SUCCESS,
SWAP_MODE_ERROR,
} swap_mode_t;

extern bool G_called_from_swap;
extern bool G_swap_response_ready;
extern swap_mode_t G_swap_mode;
extern uint8_t G_swap_crosschain_hash[CX_SHA256_SIZE];

typedef enum {
EXTERNAL, // External plugin, set by setExternalPlugin.
ERC721, // Specific ERC721 internal plugin, set by setPlugin.
ERC1155, // Specific ERC1155 internal plugin, set by setPlugin
OLD_INTERNAL // Old internal plugin, not set by any command.
// External plugin, set by setExternalPlugin
EXTERNAL,
// Specific SWAP_WITH_CALLDATA internal plugin
// set as fallback when started if calldata is provided in swap mode
SWAP_WITH_CALLDATA,
// Specific ERC721 internal plugin, set by setPlugin
ERC721,
// Specific ERC1155 internal plugin, set by setPlugin
ERC1155,
// Old internal plugin, not set by any command
OLD_INTERNAL
} pluginType_t;

extern pluginType_t pluginType;
Expand Down
8 changes: 8 additions & 0 deletions src_features/setPlugin/cmd_setPlugin.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,23 @@ typedef bool verificationAlgo(const cx_ecfp_public_key_t *,
static pluginType_t getPluginType(char *pluginName, uint8_t pluginNameLength) {
if (pluginNameLength == sizeof(ERC721_STR) - 1 &&
strncmp(pluginName, ERC721_STR, pluginNameLength) == 0) {
PRINTF("Using internal plugin ERC721\n");
return ERC721;
} else if (pluginNameLength == sizeof(ERC1155_STR) - 1 &&
strncmp(pluginName, ERC1155_STR, pluginNameLength) == 0) {
PRINTF("Using internal plugin ERC1155\n");
return ERC1155;
} else {
PRINTF("Using external plugin\n");
return EXTERNAL;
}
}

void set_swap_with_calldata_plugin_type(void) {
PRINTF("Using internal plugin SWAP_WITH_CALLDATA\n");
pluginType = SWAP_WITH_CALLDATA;
}

void handleSetPlugin(uint8_t p1,
uint8_t p2,
const uint8_t *workBuffer,
Expand Down
3 changes: 3 additions & 0 deletions src_features/setPlugin/cmd_setPlugin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#pragma once

void set_swap_with_calldata_plugin_type(void);
16 changes: 16 additions & 0 deletions src_features/signTx/logic_signTx.c
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ __attribute__((noinline)) static bool finalize_parsing_helper(void) {
pluginFinalize.result = pluginProvideInfo.result;
}
if (pluginFinalize.result != ETH_PLUGIN_RESULT_FALLBACK) {
PRINTF("pluginFinalize.result %d successful\n", pluginFinalize.result);
// Handle the right interface
switch (pluginFinalize.uiType) {
case ETH_UI_TYPE_GENERIC:
Expand Down Expand Up @@ -423,6 +424,10 @@ __attribute__((noinline)) static bool finalize_parsing_helper(void) {
report_finalize_error();
return false;
}
} else if (G_called_from_swap && G_swap_mode == SWAP_MODE_CROSSCHAIN_SUCCESS) {
PRINTF("Plugin swap_with_calldata fell back for UI with success\n");
// We are not bling signing, the data has been validated by the plugin
tmpContent.txContent.dataPresent = false;
}
}

Expand All @@ -441,7 +446,18 @@ __attribute__((noinline)) static bool finalize_parsing_helper(void) {
THROW(ERR_SILENT_MODE_CHECK_FAILED);
}

// Specific calldata check when in swap mode
if (G_called_from_swap) {
// Two success cases: we are in standard mode and no calldata was received
// We are in crosschain mode and the correct calldata has been received
if (G_swap_mode != SWAP_MODE_STANDARD && G_swap_mode != SWAP_MODE_CROSSCHAIN_SUCCESS) {
PRINTF("Error: G_swap_mode %d refused\n", G_swap_mode);
THROW(ERR_SILENT_MODE_CHECK_FAILED);
}
}

if (tmpContent.txContent.dataPresent && !N_storage.dataAllowed) {
PRINTF("Data is present but not allowed\n");
report_finalize_error();
ui_error_blind_signing();
return false;
Expand Down
137 changes: 137 additions & 0 deletions src_plugins/swap_with_calldata_plugin.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#include <string.h>
#include "lib_cxng/src/cx_sha256.h"
#include "plugin_utils.h"
#include "eth_plugin_internal.h"
#include "eth_plugin_interface.h"
#include "eth_plugin_handler.h"

/* SWAP_WITH_CALLDATA 'internal plugin'
*
* This internal plugin is used to parse calldata in the SWAP_WITH_CALLDATA context.
* Used for example by Thorswap and LiFi.
* Difference with main swap mode:
* - The swap transaction promise made by the partner contains a 32 bytes hash additional field
* - This hash is the hash of the calldata of the final signing transaction
* - During the final TX signature in ETH, we check the destination and amount as usual
* - We also check the hash of the calldata against the promised hash.
*
* This minimalist internal plugin is used to do this calldata hashing and check.
*/

typedef struct swap_with_calldata_context_s {
// Hash of the calldata received.
// Computed on (Selector + N * 32bytes parameters)
// We don't care at all about the content, we'll just compare the hash with the promised hash.
cx_sha256_t update_hash;
} swap_with_calldata_context_t;

void handle_init_contract_swap_with_calldata(ethPluginInitContract_t *msg) {
PRINTF("handle_init_contract_swap_with_calldata\n");
if (!G_called_from_swap) {
// Can't happen in theory, but let's double check.
PRINTF("swap_with_calldata plugin can't be used outside of SWAP context");
msg->result = ETH_PLUGIN_RESULT_ERROR;
return;
}

swap_with_calldata_context_t *context = (swap_with_calldata_context_t *) msg->pluginContext;

PRINTF("swap_with_calldata plugin init contract %.*H\n", SELECTOR_SIZE, msg->selector);

// Can't fail
cx_sha256_init_no_throw(&context->update_hash);

if (cx_sha256_update(&context->update_hash, msg->selector, SELECTOR_SIZE) != CX_OK) {
PRINTF("ERROR: cx_sha256_update on selector\n");
msg->result = ETH_PLUGIN_RESULT_ERROR;
} else {
msg->result = ETH_PLUGIN_RESULT_OK;
}
}

void handle_provide_parameter_swap_with_calldata(ethPluginProvideParameter_t *msg) {
PRINTF("handle_provide_parameter_swap_with_calldata\n");
if (!G_called_from_swap) {
// Can't happen in theory, but let's double check.
PRINTF("swap_with_calldata plugin can't be used outside of SWAP context");
msg->result = ETH_PLUGIN_RESULT_ERROR;
return;
}

swap_with_calldata_context_t *context = (swap_with_calldata_context_t *) msg->pluginContext;

PRINTF("swap_with_calldata plugin provide parameter %d %.*H\n",
msg->parameterOffset,
PARAMETER_LENGTH,
msg->parameter);

if (cx_sha256_update(&context->update_hash, msg->parameter, PARAMETER_LENGTH) != CX_OK) {
PRINTF("ERROR: cx_sha256_update on parameter %d\n", msg->parameterOffset);
msg->result = ETH_PLUGIN_RESULT_ERROR;
} else {
msg->result = ETH_PLUGIN_RESULT_OK;
}
}

void handle_finalize_swap_with_calldata(ethPluginFinalize_t *msg) {
PRINTF("handle_finalize_swap_with_calldata\n");
if (!G_called_from_swap) {
// Can't happen in theory, but let's double check.
PRINTF("swap_with_calldata plugin can't be used outside of SWAP context");
msg->result = ETH_PLUGIN_RESULT_ERROR;
return;
}

swap_with_calldata_context_t *context = (swap_with_calldata_context_t *) msg->pluginContext;

// Can't fail
uint8_t hash_digest[CX_SHA256_SIZE];
cx_sha256_final(&context->update_hash, hash_digest);

// Compare computed hash with promise made in Exchange by the partner
if (memcmp(hash_digest, G_swap_crosschain_hash, CX_SHA256_SIZE) != 0) {
PRINTF("ERROR: Wrong hash, promised %.*H, received %.*H\n",
CX_SHA256_SIZE,
G_swap_crosschain_hash,
CX_SHA256_SIZE,
hash_digest);
G_swap_mode = SWAP_MODE_ERROR;
} else {
PRINTF("Finalize successful, hashes match\n");
if (G_swap_mode == SWAP_MODE_CROSSCHAIN_PENDING_CHECK) {
// Remember that a calldata exists and is valid
// This differentiates the case were there was no calldata is provided
G_swap_mode = SWAP_MODE_CROSSCHAIN_SUCCESS;
} else {
PRINTF("G_swap_mode %d wrong, refusing to validate the calldata", G_swap_mode);
G_swap_mode = SWAP_MODE_ERROR;
}
}

// We use fallback so that the Eth UI takes back the display
// This is simpler than forwarding the values in txContent
// We return this even in case of errors because the error handling is done with G_swap_mode
msg->result = ETH_PLUGIN_RESULT_FALLBACK;
msg->tokenLookup1 = NULL;
msg->tokenLookup2 = NULL;
}

void swap_with_calldata_plugin_call(int message, void *parameters) {
switch (message) {
case ETH_PLUGIN_INIT_CONTRACT:
handle_init_contract_swap_with_calldata((ethPluginInitContract_t *) parameters);
break;
case ETH_PLUGIN_PROVIDE_PARAMETER:
handle_provide_parameter_swap_with_calldata((ethPluginProvideParameter_t *) parameters);
break;
case ETH_PLUGIN_FINALIZE:
handle_finalize_swap_with_calldata((ethPluginFinalize_t *) parameters);
break;
case ETH_PLUGIN_PROVIDE_INFO:
case ETH_PLUGIN_QUERY_CONTRACT_ID:
case ETH_PLUGIN_QUERY_CONTRACT_UI:
default:
PRINTF("Unhandled message %d\n", message);
break;
}
}
Loading