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

Swap release #275

Merged
merged 7 commits into from
Aug 19, 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ Dates are in `dd-mm-yyyy` format.

## [2.2.5] - TBD

### Added

- Support for crosschain swap protocol.

### Fixed

- `signMessage` would fail since version 2.2.2 for certain message lengths.
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ PATH_SLIP21_APP_LOAD_PARAMS = "LEDGER-Wallet policy"
# Application version
APPVERSION_M = 2
APPVERSION_N = 2
APPVERSION_P = 4
APPVERSION_P = 5
APPVERSION_SUFFIX = # if not empty, appended at the end. Do not add a dash.

ifeq ($(APPVERSION_SUFFIX),)
Expand Down
104 changes: 99 additions & 5 deletions src/handler/sign_psbt.c
Original file line number Diff line number Diff line change
Expand Up @@ -1202,13 +1202,97 @@ execute_swap_checks(dispatcher_context_t *dc, sign_psbt_state_t *st) {

uint64_t fee = st->inputs_total_amount - st->outputs.total_amount;

// There must be only one external output
if (st->n_external_outputs != 1) {
PRINTF("Swap transaction must have exactly 1 external output\n");
// The index of the swap destination address in the cache of external outputs.
// NB: this is _not_ the output index in the transaction, as change outputs are skipped.
int swap_dest_idx = -1;

if (G_swap_state.mode == SWAP_MODE_STANDARD) {
swap_dest_idx = 0;

// There must be only one external output
if (st->n_external_outputs != 1) {
PRINTF("Standard swap transaction must have exactly 1 external output\n");
SEND_SW(dc, SW_FAIL_SWAP);
finalize_exchange_sign_transaction(false);
}
} else if (G_swap_state.mode == SWAP_MODE_CROSSCHAIN) {
// There must be exactly 2 external outputs; the first is the OP_RETURN

swap_dest_idx = 1;

if (st->n_external_outputs != 2) {
PRINTF("Cross-chain swap transaction must have exactly 2 external outputs\n");
SEND_SW(dc, SW_FAIL_SWAP);
finalize_exchange_sign_transaction(false);
}

uint8_t *opreturn_script = st->outputs.output_scripts[0];
size_t opreturn_script_len = st->outputs.output_script_lengths[0];
size_t opreturn_amount = st->outputs.output_amounts[0];
if (opreturn_script_len < 4 || opreturn_script[0] != OP_RETURN) {
PRINTF("The first output must be OP_RETURN <data> for a cross-chain swap\n");
SEND_SW(dc, SW_FAIL_SWAP);
finalize_exchange_sign_transaction(false);
}

uint8_t second_byte = opreturn_script[1];
size_t push_opcode_size; // the length of the push opcode (1 or 2 bytes)
size_t data_size; // the length of the actual data embedded in the OP_RETURN output
if (2 <= second_byte && second_byte <= 75) {
push_opcode_size = 1;
data_size = second_byte;
} else if (second_byte == OP_PUSHDATA1) {
// pushing more than 75 bytes requires using OP_PUSHDATA1 <len>
// insted of a single-byte opcode
push_opcode_size = 2;
data_size = opreturn_script[2];
} else {
// there are other valid OP_RETURN Scripts that we never expect here,
// so we don't bother parsing.
PRINTF("Unsupported or invalid OP_RETURN Script in cross-chain swap\n");
SEND_SW(dc, SW_FAIL_SWAP);
finalize_exchange_sign_transaction(false);
}

// Make sure there is a singla data push
if (opreturn_script_len != 1 + push_opcode_size + data_size) {
PRINTF("Invalid OP_RETURN Script length in cross-chain swap\n");
SEND_SW(dc, SW_FAIL_SWAP);
finalize_exchange_sign_transaction(false);
}

// Make sure the output's value is 0
if (opreturn_amount != 0) {
PRINTF("OP_RETURN with non-zero value during cross-chain swap\n");
SEND_SW(dc, SW_FAIL_SWAP);
finalize_exchange_sign_transaction(false);
}

// verify the hash in the data payload is the expected one
uint8_t expected_payin_hash[32];
cx_hash_sha256(&opreturn_script[1 + push_opcode_size], data_size, expected_payin_hash, 32);
if (memcmp(G_swap_state.payin_extra_id + 1,
expected_payin_hash,
sizeof(expected_payin_hash)) != 0) {
PRINTF("Mismatching payin hash in cross-chain swap\n");
SEND_SW(dc, SW_FAIL_SWAP);
finalize_exchange_sign_transaction(false);
}
} else if (G_swap_state.mode == SWAP_MODE_ERROR) {
// an error was detected in handle_swap_sign_transaction.c::copy_transaction_parameters
// special case only to improve error reporting in debug mode
PRINTF("Invalid parameters for swap feature\n");
SEND_SW(dc, SW_FAIL_SWAP);
finalize_exchange_sign_transaction(false);
} else {
PRINTF("Unknown swap mode: %d\n", G_swap_state.mode);
SEND_SW(dc, SW_FAIL_SWAP);
finalize_exchange_sign_transaction(false);
}

LEDGER_ASSERT(0 <= swap_dest_idx && swap_dest_idx < N_CACHED_EXTERNAL_OUTPUTS,
"External output index out of range for swap\n");

// Check that total amount and fees are as expected
if (fee != G_swap_state.fees) {
PRINTF("Mismatching fee for swap\n");
Expand All @@ -1226,8 +1310,8 @@ execute_swap_checks(dispatcher_context_t *dc, sign_psbt_state_t *st) {
// Compute this output's address
char output_description[MAX_OUTPUT_SCRIPT_DESC_SIZE];

if (!format_script(st->outputs.output_scripts[0],
st->outputs.output_script_lengths[0],
if (!format_script(st->outputs.output_scripts[swap_dest_idx],
st->outputs.output_script_lengths[swap_dest_idx],
output_description)) {
PRINTF("Invalid or unsupported script for external output\n");
SEND_SW(dc, SW_FAIL_SWAP);
Expand All @@ -1243,6 +1327,16 @@ execute_swap_checks(dispatcher_context_t *dc, sign_psbt_state_t *st) {
strncmp(G_swap_state.destination_address, output_description, output_description_len)) {
// address did not match
PRINTF("Mismatching address for swap\n");
PRINTF("Expected: ");
for (int i = 0; i < swap_addr_len; i++) {
PRINTF("%c", G_swap_state.destination_address[i]);
}
PRINTF("\n");
PRINTF("Found: ");
for (int i = 0; i < output_description_len; i++) {
PRINTF("%c", output_description[i]);
}
PRINTF("\n");
SEND_SW(dc, SW_FAIL_SWAP);
finalize_exchange_sign_transaction(false);
}
Expand Down
32 changes: 32 additions & 0 deletions src/swap/handle_swap_sign_transaction.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,26 @@ static uint8_t* G_swap_sign_return_value_address;

bool copy_transaction_parameters(create_transaction_parameters_t* sign_transaction_params) {
char destination_address[65];
uint8_t destination_address_extra_data[33];
uint8_t amount[8];
uint8_t fees[8];

// first copy parameters to stack, and then to global data.
// We need this "trick" as the input data position can overlap with btc-app globals
memset(destination_address, 0, sizeof(destination_address));
memset(destination_address_extra_data, 0, sizeof(destination_address_extra_data));
memset(amount, 0, sizeof(amount));
memset(fees, 0, sizeof(fees));
strncpy(destination_address,
sign_transaction_params->destination_address,
sizeof(destination_address) - 1);

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

// sanity checks
if ((destination_address[sizeof(destination_address) - 1] != '\0') ||
(sign_transaction_params->amount_length > 8) ||
Expand All @@ -53,6 +61,30 @@ bool copy_transaction_parameters(create_transaction_parameters_t* sign_transacti
memcpy(G_swap_state.destination_address,
destination_address,
sizeof(G_swap_state.destination_address));

// 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
if (destination_address_extra_data[0] == 0) {
G_swap_state.mode = SWAP_MODE_STANDARD;

// we don't use the payin_extra_id field in this mode
explicit_bzero(G_swap_state.payin_extra_id, sizeof(G_swap_state.payin_extra_id));
} else if (destination_address_extra_data[0] == 2) {
G_swap_state.mode = SWAP_MODE_CROSSCHAIN;

// we expect exactly 33 bytes. Guard against future protocol changes, as the following
// code might need to be revised in that case
LEDGER_ASSERT(sizeof(G_swap_state.payin_extra_id) == 33, "Unexpected payin_extra_id size");

memcpy(G_swap_state.payin_extra_id,
destination_address_extra_data,
sizeof(G_swap_state.payin_extra_id));
} else {
// Since we cannot return an error status word here, we mark the swap state as invalid
// and will return an error later, once an attempt is made to sign.
G_swap_state.mode = SWAP_MODE_ERROR;
}

return true;
}

Expand Down
8 changes: 8 additions & 0 deletions src/swap/swap_globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@

#include <stdint.h>

enum {
SWAP_MODE_STANDARD = 0,
SWAP_MODE_CROSSCHAIN = 1,
SWAP_MODE_ERROR = 0xFF,
};

typedef struct swap_globals_s {
uint64_t amount;
uint64_t fees;
char destination_address[65];
/*Is swap mode*/
unsigned char called_from_swap;
unsigned char should_exit;
unsigned char mode;
uint8_t payin_extra_id[1 + 32];
} swap_globals_t;

extern swap_globals_t G_swap_state;
Binary file modified tests/snapshots/flex/test_dashboard/00001.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanos/test_dashboard/00000.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanos/test_dashboard/00001.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanos/test_dashboard/00002.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanos/test_dashboard/00003.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/snapshots/nanosp/test_dashboard/00001.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/snapshots/nanox/test_dashboard/00001.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/snapshots/stax/test_dashboard/00001.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading