Skip to content

Commit

Permalink
Merge pull request #203 from LedgerHQ/fbe/thorswap
Browse files Browse the repository at this point in the history
Thorswap
  • Loading branch information
fbeutin-ledger authored Jul 22, 2024
2 parents 34469a7 + 925739d commit 8943ded
Show file tree
Hide file tree
Showing 183 changed files with 445 additions and 165 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ jobs:
uses: ./.github/workflows/reusable_swap_functional_tests.yml
with:
branch_for_exchange: ${{ github.ref }}
test_filter: "'not polkadot'"
2 changes: 1 addition & 1 deletion .github/workflows/reusable_swap_functional_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ on:

branch_for_bitcoin:
required: false
default: 'develop'
default: 'swap'
type: string
branch_for_bitcoin_nanos:
required: false
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ APPNAME = "Exchange"

# Application version
APPVERSION_M = 3
APPVERSION_N = 3
APPVERSION_P = 4
APPVERSION_N = 4
APPVERSION_P = 0
APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)"

# Application source files
Expand Down
45 changes: 23 additions & 22 deletions protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,25 +299,26 @@ For all other TYPES, the format of this nonce is a 32 bytes array.

Return code can be one of the following values:

| Bytes | Name | Description |
| ------ | ------------------------- | --------------------------------------------------------------------------------------- |
| 0x6A80 | INCORRECT_COMMAND_DATA | The DATA sent does not match the correct format for the COMMAND specified |
| 0x6A81 | DESERIALIZATION_FAILED | Can't parse partner transaction proposal |
| 0x6A82 | WRONG_TRANSACTION_ID | Transaction ID is not equal to one generated on the START_NEW_TRANSACTION step |
| 0x6A83 | INVALID_ADDRESS | Refund or payout address doesn't belong to us |
| 0x6A84 | USER_REFUSED | User refused the transaction proposal |
| 0x6A85 | INTERNAL_ERROR | Internal error of the application |
| 0x6A86 | WRONG_P1 | The P1 value is not a valid RATE |
| 0x6A87 | WRONG_P2_SUBCOMMAND | The P2 lower 4 bits of the P2 byte is not a valid SUBCOMMAND |
| 0x6A88 | WRONG_P2_EXTENSION | The P2 upper 4 bits of the P2 byte is not a valid EXTENSION |
| 0x6A89 | INVALID_P2_EXTENSION | The extension is a valid value but is refused in the current context |
| 0x6A8A | MEMORY_CORRUPTION | A child application started by Exchange has corrupted the Exchange application memory |
| 0x6A8B | AMOUNT_FORMATTING_FAILED | A child application failed to format an amount provided by the partner |
| 0x6A8C | APPLICATION_NOT_INSTALLED | The requested child application is not installed on the device |
| 0x6E00 | CLASS_NOT_SUPPORTED | The CLASS is not 0xE0 |
| 0x6E01 | MALFORMED_APDU | The APDU header is malformed |
| 0x6E02 | INVALID_DATA_LENGTH | The length of the DATA is refused for this COMMAND |
| 0x6D00 | INVALID_INSTRUCTION | COMMAND is not in the "Possible commands" table |
| 0x6D01 | UNEXPECTED_INSTRUCTION | COMMAND is in the "Possible commands" table but is refused in the current context |
| 0x9D1A | SIGN_VERIFICATION_FAIL | The signature sent by this command does not match the data or the associated public key |
| 0x9000 | SUCCESS | Success code |
| Bytes | Name | Description |
| ------ | ---------------------------- | --------------------------------------------------------------------------------------- |
| 0x6A80 | INCORRECT_COMMAND_DATA | The DATA sent does not match the correct format for the COMMAND specified |
| 0x6A81 | DESERIALIZATION_FAILED | Can't parse partner transaction proposal |
| 0x6A82 | WRONG_TRANSACTION_ID | Transaction ID is not equal to one generated on the START_NEW_TRANSACTION step |
| 0x6A83 | INVALID_ADDRESS | Refund or payout address doesn't belong to us |
| 0x6A84 | USER_REFUSED | User refused the transaction proposal |
| 0x6A85 | INTERNAL_ERROR | Internal error of the application |
| 0x6A86 | WRONG_P1 | The P1 value is not a valid RATE |
| 0x6A87 | WRONG_P2_SUBCOMMAND | The P2 lower 4 bits of the P2 byte is not a valid SUBCOMMAND |
| 0x6A88 | WRONG_P2_EXTENSION | The P2 upper 4 bits of the P2 byte is not a valid EXTENSION |
| 0x6A89 | INVALID_P2_EXTENSION | The extension is a valid value but is refused in the current context |
| 0x6A8A | MEMORY_CORRUPTION | A child application started by Exchange has corrupted the Exchange application memory |
| 0x6A8B | AMOUNT_FORMATTING_FAILED | A child application failed to format an amount provided by the partner |
| 0x6A8C | APPLICATION_NOT_INSTALLED | The requested child application is not installed on the device |
| 0x6A8D | WRONG_EXTRA_ID_OR_EXTRA_DATA | The values given for extra_id (memo) and / or extra_data (Thorswap like) are incorrect |
| 0x6E00 | CLASS_NOT_SUPPORTED | The CLASS is not 0xE0 |
| 0x6E01 | MALFORMED_APDU | The APDU header is malformed |
| 0x6E02 | INVALID_DATA_LENGTH | The length of the DATA is refused for this COMMAND |
| 0x6D00 | INVALID_INSTRUCTION | COMMAND is not in the "Possible commands" table |
| 0x6D01 | UNEXPECTED_INSTRUCTION | COMMAND is in the "Possible commands" table but is refused in the current context |
| 0x9D1A | SIGN_VERIFICATION_FAIL | The signature sent by this command does not match the data or the associated public key |
| 0x9000 | SUCCESS | Success code |
1 change: 1 addition & 0 deletions src/pb_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@

typedef PB_BYTES_ARRAY_T(16) pb_bytes_array_16_t;
typedef PB_BYTES_ARRAY_T(32) pb_bytes_array_32_t;
typedef PB_BYTES_ARRAY_T(33) pb_bytes_array_33_t;
34 changes: 34 additions & 0 deletions src/process_transaction.c
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,36 @@ static bool deserialize_protobuf_payload(buf_t payload,
return true;
}

static bool check_extra_id_extra_data(subcommand_e subcommand) {
if (subcommand == SWAP || subcommand == SWAP_NG) {
pb_bytes_array_33_t *extra =
(pb_bytes_array_33_t *) &G_swap_ctx.swap_transaction.payin_extra_data;
// has_extra_id == extra id string is not 0 sized
bool has_extra_id = (G_swap_ctx.swap_transaction.payin_extra_id[0] != '\0');
// has_extra_data == extra data is not empty and does not have only one byte NATIVE id (0)
bool has_extra_data = (extra->size != 0 && !(extra->size == 1 && extra->bytes[0] == 0));
if (has_extra_id && has_extra_data) {
PRINTF("Error: both payin_extra_id '%s' and payin_extra_data '%.*H' received\n",
G_swap_ctx.swap_transaction.payin_extra_id,
extra->size,
extra->bytes);
return false;
}

if (has_extra_data) {
// Size has to be header + 32 bytes hash
if (extra->size != 33) {
PRINTF("Error: incorrect payin_extra_data size %d != 33; payin_extra_data = %.*H\n",
extra->size,
extra->size,
extra->bytes);
return false;
}
}
}
return true;
}

static bool check_transaction_id(subcommand_e subcommand) {
if (subcommand == SWAP) {
if (G_swap_ctx.swap_transaction.device_transaction_id[10] != '\0') {
Expand Down Expand Up @@ -332,6 +362,10 @@ int process_transaction(const command_t *cmd) {
return reply_error(DESERIALIZATION_FAILED);
}

if (!check_extra_id_extra_data(cmd->subcommand)) {
return reply_error(WRONG_EXTRA_ID_OR_EXTRA_DATA);
}

if (!check_transaction_id(cmd->subcommand)) {
return reply_error(WRONG_TRANSACTION_ID);
}
Expand Down
3 changes: 2 additions & 1 deletion src/proto/protocol.options
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
ledger_swap.NewTransactionResponse.payin_address max_size:63;
ledger_swap.NewTransactionResponse.payin_extra_id max_size:33;
ledger_swap.NewTransactionResponse.payin_extra_id max_size:20;
ledger_swap.NewTransactionResponse.payin_extra_data max_size:33;
ledger_swap.NewTransactionResponse.refund_address max_size:63;
ledger_swap.NewTransactionResponse.refund_extra_id max_size:20;
ledger_swap.NewTransactionResponse.payout_address max_size:63;
Expand Down
5 changes: 3 additions & 2 deletions src/proto/protocol.pb.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.3.9 at Thu Jun 20 13:57:59 2024. */
/* Generated by nanopb-0.3.9 at Mon Jun 24 15:05:18 2024. */

#include "protocol.pb.h"

Expand All @@ -10,7 +10,7 @@



const pb_field_t ledger_swap_NewTransactionResponse_fields[13] = {
const pb_field_t ledger_swap_NewTransactionResponse_fields[14] = {
PB_FIELD( 1, STRING , SINGULAR, STATIC , FIRST, ledger_swap_NewTransactionResponse, payin_address, payin_address, 0),
PB_FIELD( 2, STRING , SINGULAR, STATIC , OTHER, ledger_swap_NewTransactionResponse, payin_extra_id, payin_address, 0),
PB_FIELD( 3, STRING , SINGULAR, STATIC , OTHER, ledger_swap_NewTransactionResponse, refund_address, payin_extra_id, 0),
Expand All @@ -23,6 +23,7 @@ const pb_field_t ledger_swap_NewTransactionResponse_fields[13] = {
PB_FIELD( 10, BYTES , SINGULAR, STATIC , OTHER, ledger_swap_NewTransactionResponse, amount_to_wallet, amount_to_provider, 0),
PB_FIELD( 11, STRING , SINGULAR, STATIC , OTHER, ledger_swap_NewTransactionResponse, device_transaction_id, amount_to_wallet, 0),
PB_FIELD( 12, BYTES , SINGULAR, STATIC , OTHER, ledger_swap_NewTransactionResponse, device_transaction_id_ng, device_transaction_id, 0),
PB_FIELD( 13, BYTES , SINGULAR, STATIC , OTHER, ledger_swap_NewTransactionResponse, payin_extra_data, device_transaction_id_ng, 0),
PB_LAST_FIELD
};

Expand Down
15 changes: 9 additions & 6 deletions src/proto/protocol.pb.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.3.9 at Thu Jun 20 13:57:59 2024. */
/* Generated by nanopb-0.3.9 at Mon Jun 24 15:05:18 2024. */

#ifndef PB_LEDGER_SWAP_PROTOCOL_PB_H_INCLUDED
#define PB_LEDGER_SWAP_PROTOCOL_PB_H_INCLUDED
Expand Down Expand Up @@ -27,12 +27,13 @@ typedef struct _ledger_swap_NewFundResponse {
/* @@protoc_insertion_point(struct:ledger_swap_NewFundResponse) */
} ledger_swap_NewFundResponse;

typedef PB_BYTES_ARRAY_T(33) ledger_swap_NewTransactionResponse_payin_extra_data_t;
typedef PB_BYTES_ARRAY_T(16) ledger_swap_NewTransactionResponse_amount_to_provider_t;
typedef PB_BYTES_ARRAY_T(16) ledger_swap_NewTransactionResponse_amount_to_wallet_t;
typedef PB_BYTES_ARRAY_T(32) ledger_swap_NewTransactionResponse_device_transaction_id_ng_t;
typedef struct _ledger_swap_NewTransactionResponse {
char payin_address[63];
char payin_extra_id[33];
char payin_extra_id[20];
char refund_address[63];
char refund_extra_id[20];
char payout_address[63];
Expand All @@ -43,6 +44,7 @@ typedef struct _ledger_swap_NewTransactionResponse {
ledger_swap_NewTransactionResponse_amount_to_wallet_t amount_to_wallet;
char device_transaction_id[11];
ledger_swap_NewTransactionResponse_device_transaction_id_ng_t device_transaction_id_ng;
ledger_swap_NewTransactionResponse_payin_extra_data_t payin_extra_data;
/* @@protoc_insertion_point(struct:ledger_swap_NewTransactionResponse) */
} ledger_swap_NewTransactionResponse;

Expand All @@ -69,11 +71,11 @@ typedef struct _ledger_swap_NewSellResponse {
/* Default values for struct fields */

/* Initializer values for message structs */
#define ledger_swap_NewTransactionResponse_init_default {"", "", "", "", "", "", "", "", {0, {0}}, {0, {0}}, "", {0, {0}}}
#define ledger_swap_NewTransactionResponse_init_default {"", "", "", "", "", "", "", "", {0, {0}}, {0, {0}}, "", {0, {0}}, {0, {0}}}
#define ledger_swap_UDecimal_init_default {{0, {0}}, 0}
#define ledger_swap_NewSellResponse_init_default {"", "", {0, {0}}, "", "", ledger_swap_UDecimal_init_default, {0, {0}}}
#define ledger_swap_NewFundResponse_init_default {"", "", "", {0, {0}}, "", {0, {0}}}
#define ledger_swap_NewTransactionResponse_init_zero {"", "", "", "", "", "", "", "", {0, {0}}, {0, {0}}, "", {0, {0}}}
#define ledger_swap_NewTransactionResponse_init_zero {"", "", "", "", "", "", "", "", {0, {0}}, {0, {0}}, "", {0, {0}}, {0, {0}}}
#define ledger_swap_UDecimal_init_zero {{0, {0}}, 0}
#define ledger_swap_NewSellResponse_init_zero {"", "", {0, {0}}, "", "", ledger_swap_UDecimal_init_zero, {0, {0}}}
#define ledger_swap_NewFundResponse_init_zero {"", "", "", {0, {0}}, "", {0, {0}}}
Expand All @@ -87,6 +89,7 @@ typedef struct _ledger_swap_NewSellResponse {
#define ledger_swap_NewFundResponse_device_transaction_id_tag 6
#define ledger_swap_NewTransactionResponse_payin_address_tag 1
#define ledger_swap_NewTransactionResponse_payin_extra_id_tag 2
#define ledger_swap_NewTransactionResponse_payin_extra_data_tag 13
#define ledger_swap_NewTransactionResponse_refund_address_tag 3
#define ledger_swap_NewTransactionResponse_refund_extra_id_tag 4
#define ledger_swap_NewTransactionResponse_payout_address_tag 5
Expand All @@ -108,13 +111,13 @@ typedef struct _ledger_swap_NewSellResponse {
#define ledger_swap_NewSellResponse_device_transaction_id_tag 7

/* Struct field encoding specification for nanopb */
extern const pb_field_t ledger_swap_NewTransactionResponse_fields[13];
extern const pb_field_t ledger_swap_NewTransactionResponse_fields[14];
extern const pb_field_t ledger_swap_UDecimal_fields[3];
extern const pb_field_t ledger_swap_NewSellResponse_fields[8];
extern const pb_field_t ledger_swap_NewFundResponse_fields[7];

/* Maximum encoded size of messages (where known) */
#define ledger_swap_NewTransactionResponse_size 381
#define ledger_swap_NewTransactionResponse_size 403
#define ledger_swap_UDecimal_size 24
#define ledger_swap_NewSellResponse_size 219
#define ledger_swap_NewFundResponse_size 233
Expand Down
1 change: 1 addition & 0 deletions src/proto/protocol.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package ledger_swap;
message NewTransactionResponse {
string payin_address = 1;
string payin_extra_id = 2;
bytes payin_extra_data = 13;
string refund_address = 3;
string refund_extra_id = 4;
string payout_address = 5;
Expand Down
12 changes: 11 additions & 1 deletion src/start_signing_transaction.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,17 @@ int start_signing_transaction(const command_t *cmd) {
lib_in_out_params.amount = G_swap_ctx.swap_transaction.amount_to_provider.bytes;
lib_in_out_params.amount_length = G_swap_ctx.swap_transaction.amount_to_provider.size;
lib_in_out_params.destination_address = G_swap_ctx.swap_transaction.payin_address;
lib_in_out_params.destination_address_extra_id = G_swap_ctx.swap_transaction.payin_extra_id;
if (G_swap_ctx.swap_transaction.payin_extra_data.size == 33) {
PRINTF("Using extra data %.*H\n",
G_swap_ctx.swap_transaction.payin_extra_data.size,
G_swap_ctx.swap_transaction.payin_extra_data.bytes);
lib_in_out_params.destination_address_extra_id =
(char *) G_swap_ctx.swap_transaction.payin_extra_data.bytes;
} else {
PRINTF("Using native payin_extra_id %s\n", G_swap_ctx.swap_transaction.payin_extra_id);
lib_in_out_params.destination_address_extra_id =
G_swap_ctx.swap_transaction.payin_extra_id;
}
}

if (cmd->subcommand == SELL || cmd->subcommand == SELL_NG) {
Expand Down
1 change: 1 addition & 0 deletions src/swap_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ typedef enum {
MEMORY_CORRUPTION = 0x6A8A,
AMOUNT_FORMATTING_FAILED = 0x6A8B,
APPLICATION_NOT_INSTALLED = 0x6A8C,
WRONG_EXTRA_ID_OR_EXTRA_DATA = 0x6A8D,
CLASS_NOT_SUPPORTED = 0x6E00,
MALFORMED_APDU = 0x6E01,
INVALID_DATA_LENGTH = 0x6E02,
Expand Down
19 changes: 16 additions & 3 deletions test/python/apps/bitcoin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import sys
from typing import Optional
from enum import IntEnum
from pathlib import Path
from ragger.utils import create_currency_config
Expand Down Expand Up @@ -30,10 +31,22 @@ def __init__(self, backend: BackendInterface):
self._backend.whitelisted_status = [0x9000, 0xE000]
self.client = createClient(backend, chain=CHAIN, debug=True)

def send_simple_sign_tx(self, in_wallet: WalletPolicy, fees: int, destination: WalletPolicy, send_amount: int) -> RAPDU:
def send_simple_sign_tx(self, in_wallet: WalletPolicy, fees: int, destination: WalletPolicy, send_amount: int, *, opreturn_data: Optional[bytes] = None) -> RAPDU:
in_amounts = [send_amount + fees]
out_amounts = [send_amount]
psbt = createPsbt(in_wallet, in_amounts, out_amounts, [False], [destination])

# Prepend one opreturn data if needed with amount 0
if opreturn_data is not None:
out_amounts = [0, send_amount]
output_is_change = [False, False]
output_wallet = [None, destination]
output_opreturn_data = [opreturn_data, None]
else:
out_amounts = [send_amount]
output_is_change = [False]
output_wallet = [destination]
output_opreturn_data = [None]

psbt = createPsbt(in_wallet, in_amounts, out_amounts, output_is_change, output_wallet=output_wallet, output_opreturn_data=output_opreturn_data)
self.client.sign_psbt(psbt, in_wallet, None)

def get_address_from_wallet(wallet: WalletPolicy):
Expand Down
Loading

0 comments on commit 8943ded

Please sign in to comment.