Skip to content

Commit

Permalink
Merge pull request #275 from LedgerHQ/swap-release
Browse files Browse the repository at this point in the history
Swap release
  • Loading branch information
bigspider authored Aug 19, 2024
2 parents 4c60d06 + 6fde678 commit 0d4106d
Show file tree
Hide file tree
Showing 13 changed files with 144 additions and 6 deletions.
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.

0 comments on commit 0d4106d

Please sign in to comment.