From f01edc08f5df2d72478217c5f16707f2fa8f4687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Palmer?= Date: Thu, 14 Sep 2023 14:23:59 +0200 Subject: [PATCH 01/10] Move format_pkh in keys.h --- app/src/apdu_pubkey.c | 25 ++----------------------- app/src/keys.c | 32 +++++++++++++++++++++++++++++++- app/src/keys.h | 3 +-- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/app/src/apdu_pubkey.c b/app/src/apdu_pubkey.c index 1f0ce0d9f..3cb07b5ee 100644 --- a/app/src/apdu_pubkey.c +++ b/app/src/apdu_pubkey.c @@ -66,29 +66,8 @@ provide_pubkey(void) static void format_pkh(char *buffer, size_t len) { - cx_ecfp_public_key_t pubkey = {0}; - uint8_t hash[21]; - TZ_PREAMBLE(("buffer=%p, len=%u", buffer, len)); - - TZ_CHECK(generate_public_key(&pubkey, - global.path_with_curve.derivation_type, - &global.path_with_curve.bip32_path)); - // clang-format off - TZ_CHECK(public_key_hash(hash+1, 20, NULL, - global.path_with_curve.derivation_type, &pubkey)); - switch (global.path_with_curve.derivation_type) { - case DERIVATION_TYPE_SECP256K1: hash[0] = 1; break; - case DERIVATION_TYPE_SECP256R1: hash[0] = 2; break; - case DERIVATION_TYPE_ED25519: - case DERIVATION_TYPE_BIP32_ED25519: hash[0] = 0; break; - default: CX_CHECK(EXC_WRONG_PARAM); break; - } - // clang-format on - - if (tz_format_pkh(hash, 21, buffer, len)) - TZ_FAIL(EXC_UNKNOWN); - - TZ_POSTAMBLE; + derive_pkh(global.path_with_curve.derivation_type, + &global.path_with_curve.bip32_path, buffer, len); } static void diff --git a/app/src/keys.c b/app/src/keys.c index a6ee51f0e..8654c3cc6 100644 --- a/app/src/keys.c +++ b/app/src/keys.c @@ -33,6 +33,9 @@ #include "keys.h" #include "globals.h" +static void public_key_hash(uint8_t *, size_t, cx_ecfp_public_key_t *, + derivation_type_t, const cx_ecfp_public_key_t *); + static cx_curve_t derivation_type_to_cx_curve(derivation_type_t derivation_type) { @@ -88,9 +91,36 @@ generate_public_key(cx_ecfp_public_key_t *public_key, TZ_POSTAMBLE; } +void +derive_pkh(derivation_type_t derivation_type, const bip32_path_t *bip32_path, + char *buffer, size_t len) +{ + cx_ecfp_public_key_t pubkey = {0}; + uint8_t hash[21]; + TZ_PREAMBLE(("buffer=%p, len=%u", buffer, len)); + + TZ_ASSERT_NOTNULL(buffer); + TZ_CHECK(generate_public_key(&pubkey, derivation_type, bip32_path)); + TZ_CHECK(public_key_hash(hash + 1, 20, NULL, derivation_type, &pubkey)); + // clang-format off + switch (derivation_type) { + case DERIVATION_TYPE_SECP256K1: hash[0] = 1; break; + case DERIVATION_TYPE_SECP256R1: hash[0] = 2; break; + case DERIVATION_TYPE_ED25519: + case DERIVATION_TYPE_BIP32_ED25519: hash[0] = 0; break; + default: TZ_FAIL(EXC_WRONG_PARAM); break; + } + // clang-format on + + if (tz_format_pkh(hash, 21, buffer, len)) + TZ_FAIL(EXC_UNKNOWN); + + TZ_POSTAMBLE; +} + #define HASH_SIZE 20 -void +static void public_key_hash(uint8_t *hash_out, size_t hash_out_size, cx_ecfp_public_key_t *compressed_out, derivation_type_t derivation_type, diff --git a/app/src/keys.h b/app/src/keys.h index 68bd58b60..61c308f62 100644 --- a/app/src/keys.h +++ b/app/src/keys.h @@ -57,8 +57,7 @@ typedef struct { void read_bip32_path(bip32_path_t *, const uint8_t *, size_t); void generate_public_key(cx_ecfp_public_key_t *, derivation_type_t, const bip32_path_t *); -void public_key_hash(uint8_t *, size_t, cx_ecfp_public_key_t *, - derivation_type_t, const cx_ecfp_public_key_t *); +void derive_pkh(derivation_type_t, const bip32_path_t *, char *, size_t); void sign(derivation_type_t, const bip32_path_t *, const uint8_t *, size_t, uint8_t *, size_t *); From 0b23861b513a98960d02c92b6e17fa6215a1a080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Palmer?= Date: Wed, 30 Aug 2023 17:21:08 +0200 Subject: [PATCH 02/10] Swap: handle check_address --- app/src/handle_swap.c | 88 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 app/src/handle_swap.c diff --git a/app/src/handle_swap.c b/app/src/handle_swap.c new file mode 100644 index 000000000..7cf8367a1 --- /dev/null +++ b/app/src/handle_swap.c @@ -0,0 +1,88 @@ +/* Tezos Ledger application - Swap requirement + + Copyright 2023 Nomadic Labs + Copyright 2023 TriliTech + Copyright 2023 Functori + + With code excerpts from: + - Legacy Tezos app, Copyright 2019 Obsidian Systems + - Ledger Blue sample apps, Copyright 2016 Ledger + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + +#ifdef HAVE_SWAP + +#include + +#include "keys.h" +#include "utils.h" + +/* Check check_address_parameters_t.address_to_check against specified + * parameters. + * + * Must set params.result to 0 on error, 1 otherwise */ +void +swap_handle_check_address(check_address_parameters_t *params) +{ + FUNC_ENTER(("params=%p", params)); + + if (params->address_to_check == NULL) { + PRINTF("[ERROR] Address to check is null\n"); + goto error; + } + + if (params->address_parameters_length == 0 + || params->address_parameters == NULL) { + PRINTF("[ERROR] Address parameters is null\n"); + goto error; + } + + if (params->extra_id_to_check == NULL) { + PRINTF("[ERROR] Extra id is null\n"); + goto error; + } + + if (params->extra_id_to_check[0] != '\0') { + PRINTF("[ERROR] Extra id is not empty: %s\n", + params->extra_id_to_check); + goto error; + } + + char address[TZ_CAPTURE_BUFFER_SIZE] = {0}; + + // Always tz1 + derivation_type_t derivation_type = DERIVATION_TYPE_ED25519; + bip32_path_t bip32_path; + + TZ_CHECK(read_bip32_path(&bip32_path, params->address_parameters, + params->address_parameters_length)); + + TZ_CHECK( + derive_pkh(derivation_type, &bip32_path, address, sizeof(address))); + + if (strcmp(params->address_to_check, address) != 0) { + PRINTF("[ERROR] Check address fail: %s != %s\n", + params->address_to_check, address); + goto error; + } + + params->result = 1; + FUNC_LEAVE(); + return; +error: +bail: + params->result = 0; + FUNC_LEAVE(); +} + +#endif // HAVE_SWAP From 4fc0a3398098296ebd1c4fb5c201250518e61a88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Palmer?= Date: Wed, 30 Aug 2023 13:11:05 +0200 Subject: [PATCH 03/10] Swap: handle get_printable_amount --- app/src/handle_swap.c | 48 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/app/src/handle_swap.c b/app/src/handle_swap.c index 7cf8367a1..dfc622a71 100644 --- a/app/src/handle_swap.c +++ b/app/src/handle_swap.c @@ -22,11 +22,16 @@ #ifdef HAVE_SWAP +#include #include #include "keys.h" #include "utils.h" +#include "parser/num_parser.h" + +#define TICKER_MAX_SIZE 9 // based on app-exchange + /* Check check_address_parameters_t.address_to_check against specified * parameters. * @@ -85,4 +90,47 @@ swap_handle_check_address(check_address_parameters_t *params) FUNC_LEAVE(); } +/* Format printable amount including the ticker from specified parameters. + * + * Must set empty printable_amount on error, printable amount otherwise */ +void +swap_handle_get_printable_amount(get_printable_amount_parameters_t *params) +{ + FUNC_ENTER(("params=%p", params)); + + uint64_t amount; + uint8_t decimals; + char ticker[TICKER_MAX_SIZE]; + + if (!swap_parse_config(params->coin_configuration, + params->coin_configuration_length, ticker, + sizeof(ticker), &decimals)) { + PRINTF("[ERROR] Fail to parse config\n"); + goto error; + } + + if (!swap_str_to_u64(params->amount, params->amount_length, &amount)) { + PRINTF("[ERROR] Fail to parse amount\n"); + goto error; + } + + if (!format_fpu64_trimmed(params->printable_amount, + sizeof(params->printable_amount), amount, + decimals)) { + PRINTF("[ERROR] Fail to print amount\n"); + goto error; + } + + strlcat(params->printable_amount, " ", sizeof(params->printable_amount)); + strlcat(params->printable_amount, ticker, + sizeof(params->printable_amount)); + + FUNC_LEAVE(); + return; + +error: + memset(params->printable_amount, '\0', sizeof(params->printable_amount)); + FUNC_LEAVE(); +} + #endif // HAVE_SWAP From b252ce646c625365358a9d92cccc40cbfe2fd35d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Palmer?= Date: Wed, 30 Aug 2023 13:21:04 +0200 Subject: [PATCH 04/10] Swap: copy transaction parameters and finalize sign transaction --- app/src/handle_swap.c | 87 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/app/src/handle_swap.c b/app/src/handle_swap.c index dfc622a71..2ceaebba7 100644 --- a/app/src/handle_swap.c +++ b/app/src/handle_swap.c @@ -30,7 +30,9 @@ #include "parser/num_parser.h" -#define TICKER_MAX_SIZE 9 // based on app-exchange +// based on app-exchange +#define TICKER_MAX_SIZE 9 +#define ADDRESS_MAX_SIZE 63 /* Check check_address_parameters_t.address_to_check against specified * parameters. @@ -133,4 +135,87 @@ swap_handle_get_printable_amount(get_printable_amount_parameters_t *params) FUNC_LEAVE(); } +typedef struct { + uint64_t amount; + uint64_t fee; + char destination_address[ADDRESS_MAX_SIZE]; +} swap_transaction_parameters_t; + +static swap_transaction_parameters_t G_swap_params; + +static uint8_t *G_swap_transaction_result; + +/* Backup up transaction parameters and wipe BSS to avoid collusion with + * app-exchange BSS data. + * + * return false on error, true otherwise */ +bool +swap_copy_transaction_parameters(create_transaction_parameters_t *params) +{ + FUNC_ENTER(("params=%p", params)); + + swap_transaction_parameters_t params_copy; + memset(¶ms_copy, 0, sizeof(params_copy)); + + if (!swap_str_to_u64(params->amount, params->amount_length, + ¶ms_copy.amount)) { + PRINTF("[ERROR] Fail to parse amount\n"); + goto error; + } + + if (!swap_str_to_u64(params->fee_amount, params->fee_amount_length, + ¶ms_copy.fee)) { + PRINTF("[ERROR] Fail to parse fee\n"); + goto error; + } + + if (params->destination_address == NULL) { + PRINTF("[ERROR] Destination address is null\n"); + goto error; + } + + strlcpy(params_copy.destination_address, params->destination_address, + sizeof(params_copy.destination_address)); + if (params_copy + .destination_address[sizeof(params_copy.destination_address) - 1] + != '\0') { + PRINTF("[ERROR] Fail to copy destination address\n"); + goto error; + } + + if (params->destination_address_extra_id == NULL) { + PRINTF("[ERROR] Destination address extra id is null\n"); + goto error; + } + + if (params->destination_address_extra_id[0] != '\0') { + PRINTF("[ERROR] Destination address extra id is not empty: %s\n", + params->destination_address_extra_id); + goto error; + } + + os_explicit_zero_BSS_segment(); + + G_swap_transaction_result = ¶ms->result; + + memcpy(&G_swap_params, ¶ms_copy, sizeof(params_copy)); + + FUNC_LEAVE(); + return true; + +error: + FUNC_LEAVE(); + return false; +} + +/* Set create_transaction.result and call os_lib_end(). + * + * Doesn't return */ +void __attribute__((noreturn)) +swap_finalize_exchange_sign_transaction(bool is_success) +{ + *G_swap_transaction_result = is_success; + os_lib_end(); +} + #endif // HAVE_SWAP From 4e3f54ecef13089c991485083b17040278e622f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Palmer?= Date: Mon, 25 Sep 2023 16:05:56 +0200 Subject: [PATCH 05/10] Enabling swap feature --- app/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Makefile b/app/Makefile index 10035bf70..40f974e6e 100644 --- a/app/Makefile +++ b/app/Makefile @@ -144,6 +144,7 @@ CFLAGS += -Wwrite-strings ######################################## ENABLE_BLUETOOTH = 1 #ENABLE_NFC = 1 +ENABLE_SWAP = 1 ######################################## # NBGL custom features # From 606db2ce6a055d284d0569d09997e5c9337e9e80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Palmer?= Date: Tue, 12 Sep 2023 18:27:18 +0200 Subject: [PATCH 06/10] Allows to extract uint64 from string --- app/src/parser/num_parser.c | 29 +++++++++++++++++++++++++++++ app/src/parser/num_parser.h | 3 +++ 2 files changed, 32 insertions(+) diff --git a/app/src/parser/num_parser.c b/app/src/parser/num_parser.c index 071d65656..18d5cbd2f 100644 --- a/app/src/parser/num_parser.c +++ b/app/src/parser/num_parser.c @@ -1,6 +1,7 @@ /* Tezos Embedded C parser for Ledger - Big num parser Copyright 2023 Nomadic Labs + Copyright 2023 Functori Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -75,3 +76,31 @@ tz_parse_nat_step(tz_num_parser_buffer *buffers, tz_num_parser_regs *regs, { return tz_parse_num_step(buffers, regs, b, 1); } + +bool +tz_string_to_mutez(const char *str, uint64_t *res) +{ + if (str == NULL || res == NULL) { + PRINTF("[ERROR] Null parameter\n"); + goto error; + } + + memset(res, '\0', sizeof(uint64_t)); + int r = 0; + char c; + + while ((c = *str++) != '\0') { + if (c < '0' || c > '9') { + PRINTF("[ERROR] Non-digit character: %c\n", c); + goto error; + } + r = r * 10 + (c - '0'); + *res = *res * 10 + (c - '0'); + } + + return true; + +error: + memset(res, '\0', sizeof(uint64_t)); + return false; +} diff --git a/app/src/parser/num_parser.h b/app/src/parser/num_parser.h index 9565506bc..a5182211a 100644 --- a/app/src/parser/num_parser.h +++ b/app/src/parser/num_parser.h @@ -1,6 +1,7 @@ /* Tezos Embedded C parser for Ledger - Big num parser Copyright 2023 Nomadic Labs + Copyright 2023 Functori Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -25,3 +26,5 @@ tz_parser_result tz_parse_int_step(tz_num_parser_buffer *, tz_num_parser_regs *, uint8_t); tz_parser_result tz_parse_nat_step(tz_num_parser_buffer *, tz_num_parser_regs *, uint8_t); + +bool tz_string_to_mutez(const char *, uint64_t *); From 826d32aa17087df997baf18a85e412dff16c2eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Palmer?= Date: Wed, 13 Sep 2023 11:01:14 +0200 Subject: [PATCH 07/10] Operation parser: differentiate the AMOUNT field from the FEE field --- app/src/parser/operation_parser.c | 26 ++++++++++++++------------ app/src/parser/operation_state.h | 1 + 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/app/src/parser/operation_parser.c b/app/src/parser/operation_parser.c index 456e1b012..a2449a706 100644 --- a/app/src/parser/operation_parser.c +++ b/app/src/parser/operation_parser.c @@ -75,7 +75,7 @@ const tz_operation_field_descriptor failing_noop_fields[] = { const tz_operation_field_descriptor transaction_fields[] = { // Name, Kind, Req, Skip, None {"Source", TZ_OPERATION_FIELD_SOURCE, true, true, false}, - {"Fee", TZ_OPERATION_FIELD_AMOUNT, true, false, false}, + {"Fee", TZ_OPERATION_FIELD_FEE, true, false, false}, {"Counter", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Gas", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Storage limit", TZ_OPERATION_FIELD_NAT, true, false, false}, @@ -88,7 +88,7 @@ const tz_operation_field_descriptor transaction_fields[] = { const tz_operation_field_descriptor reveal_fields[] = { // Name, Kind, Req, Skip, None {"Source", TZ_OPERATION_FIELD_SOURCE, true, true, false}, - {"Fee", TZ_OPERATION_FIELD_AMOUNT, true, false, false}, + {"Fee", TZ_OPERATION_FIELD_FEE, true, false, false}, {"Counter", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Gas", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Storage limit", TZ_OPERATION_FIELD_NAT, true, false, false}, @@ -99,7 +99,7 @@ const tz_operation_field_descriptor reveal_fields[] = { const tz_operation_field_descriptor delegation_fields[] = { // Name, Kind, Req, Skip, None {"Source", TZ_OPERATION_FIELD_SOURCE, true, true, false}, - {"Fee", TZ_OPERATION_FIELD_AMOUNT, true, false, false}, + {"Fee", TZ_OPERATION_FIELD_FEE, true, false, false}, {"Counter", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Gas", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Storage limit", TZ_OPERATION_FIELD_NAT, true, false, false}, @@ -110,7 +110,7 @@ const tz_operation_field_descriptor delegation_fields[] = { const tz_operation_field_descriptor reg_glb_cst_fields[] = { // Name, Kind, Req, Skip, None {"Source", TZ_OPERATION_FIELD_SOURCE, true, true, false}, - {"Fee", TZ_OPERATION_FIELD_AMOUNT, true, false, false}, + {"Fee", TZ_OPERATION_FIELD_FEE, true, false, false}, {"Counter", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Gas", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Storage limit", TZ_OPERATION_FIELD_NAT, true, false, false}, @@ -121,7 +121,7 @@ const tz_operation_field_descriptor reg_glb_cst_fields[] = { const tz_operation_field_descriptor set_deposit_fields[] = { // Name, Kind, Req, Skip, None {"Source", TZ_OPERATION_FIELD_SOURCE, true, true, false}, - {"Fee", TZ_OPERATION_FIELD_AMOUNT, true, false, false}, + {"Fee", TZ_OPERATION_FIELD_FEE, true, false, false}, {"Counter", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Gas", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Storage limit", TZ_OPERATION_FIELD_NAT, true, false, false}, @@ -132,7 +132,7 @@ const tz_operation_field_descriptor set_deposit_fields[] = { const tz_operation_field_descriptor inc_paid_stg_fields[] = { // Name, Kind, Req, Skip, None {"Source", TZ_OPERATION_FIELD_SOURCE, true, true, false}, - {"Fee", TZ_OPERATION_FIELD_AMOUNT, true, false, false}, + {"Fee", TZ_OPERATION_FIELD_FEE, true, false, false}, {"Counter", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Gas", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Storage limit", TZ_OPERATION_FIELD_NAT, true, false, false}, @@ -144,7 +144,7 @@ const tz_operation_field_descriptor inc_paid_stg_fields[] = { const tz_operation_field_descriptor update_ck_fields[] = { // Name, Kind, Req, Skip, None {"Source", TZ_OPERATION_FIELD_SOURCE, true, true, false}, - {"Fee", TZ_OPERATION_FIELD_AMOUNT, true, false, false}, + {"Fee", TZ_OPERATION_FIELD_FEE, true, false, false}, {"Counter", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Gas", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Storage limit", TZ_OPERATION_FIELD_NAT, true, false, false}, @@ -155,7 +155,7 @@ const tz_operation_field_descriptor update_ck_fields[] = { const tz_operation_field_descriptor origination_fields[] = { // Name, Kind, Req, Skip, None {"Source", TZ_OPERATION_FIELD_SOURCE, true, true, false}, - {"Fee", TZ_OPERATION_FIELD_AMOUNT, true, false, false}, + {"Fee", TZ_OPERATION_FIELD_FEE, true, false, false}, {"Counter", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Gas", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Storage limit", TZ_OPERATION_FIELD_NAT, true, false, false}, @@ -169,7 +169,7 @@ const tz_operation_field_descriptor origination_fields[] = { const tz_operation_field_descriptor transfer_tck_fields[] = { // Name, Kind, Req, Skip, None {"Source", TZ_OPERATION_FIELD_SOURCE, true, true, false}, - {"Fee", TZ_OPERATION_FIELD_AMOUNT, true, false, false}, + {"Fee", TZ_OPERATION_FIELD_FEE, true, false, false}, {"Counter", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Gas", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Storage limit", TZ_OPERATION_FIELD_NAT, true, false, false}, @@ -185,7 +185,7 @@ const tz_operation_field_descriptor transfer_tck_fields[] = { const tz_operation_field_descriptor soru_add_msg_fields[] = { // Name, Kind, Req, Skip, None {"Source", TZ_OPERATION_FIELD_SOURCE, true, true, false}, - {"Fee", TZ_OPERATION_FIELD_AMOUNT, true, false, false}, + {"Fee", TZ_OPERATION_FIELD_FEE, true, false, false}, {"Counter", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Gas", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Storage limit", TZ_OPERATION_FIELD_NAT, true, false, false}, @@ -196,7 +196,7 @@ const tz_operation_field_descriptor soru_add_msg_fields[] = { const tz_operation_field_descriptor soru_exe_msg_fields[] = { // Name, Kind, Req, Skip, None {"Source", TZ_OPERATION_FIELD_SOURCE, true, true, false}, - {"Fee", TZ_OPERATION_FIELD_AMOUNT, true, false, false}, + {"Fee", TZ_OPERATION_FIELD_FEE, true, false, false}, {"Counter", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Gas", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Storage limit", TZ_OPERATION_FIELD_NAT, true, false, false}, @@ -209,7 +209,7 @@ const tz_operation_field_descriptor soru_exe_msg_fields[] = { const tz_operation_field_descriptor soru_origin_fields[] = { // Name, Kind, Req, Skip, None {"Source", TZ_OPERATION_FIELD_SOURCE, true, true, false}, - {"Fee", TZ_OPERATION_FIELD_AMOUNT, true, false, false}, + {"Fee", TZ_OPERATION_FIELD_FEE, true, false, false}, {"Counter", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Gas", TZ_OPERATION_FIELD_NAT, true, true, false}, {"Storage limit", TZ_OPERATION_FIELD_NAT, true, false, false}, @@ -447,6 +447,7 @@ tz_operation_parser_step(tz_parser_state *state) case TZ_OPERATION_FIELD_INT: case TZ_OPERATION_FIELD_NAT: break; + case TZ_OPERATION_FIELD_FEE: case TZ_OPERATION_FIELD_AMOUNT: { int len = 0; while (str[len]) @@ -759,6 +760,7 @@ tz_operation_parser_step(tz_parser_state *state) break; } case TZ_OPERATION_FIELD_NAT: + case TZ_OPERATION_FIELD_FEE: case TZ_OPERATION_FIELD_AMOUNT: { tz_must(push_frame(state, TZ_OPERATION_STEP_READ_NUM)); tz_parse_num_state_init(&state->buffers.num, diff --git a/app/src/parser/operation_state.h b/app/src/parser/operation_state.h index 96a4e3661..4a73ab2a5 100644 --- a/app/src/parser/operation_state.h +++ b/app/src/parser/operation_state.h @@ -67,6 +67,7 @@ typedef enum { TZ_OPERATION_FIELD_INT, TZ_OPERATION_FIELD_NAT, TZ_OPERATION_FIELD_AMOUNT, + TZ_OPERATION_FIELD_FEE, TZ_OPERATION_FIELD_INT32, TZ_OPERATION_FIELD_STRING, TZ_OPERATION_FIELD_SOURCE, From 7f6ed3200587edd3d0938e8d17528b32db028407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Palmer?= Date: Tue, 12 Sep 2023 14:47:37 +0200 Subject: [PATCH 08/10] Operation parser: remind data in order to check swap transaction --- app/src/apdu_sign.c | 2 ++ app/src/parser/operation_parser.c | 25 ++++++++++++++++++++++++- app/src/parser/operation_state.h | 5 +++++ app/src/parser/parser_state.c | 1 + app/src/parser/parser_state.h | 1 + 5 files changed, 33 insertions(+), 1 deletion(-) diff --git a/app/src/apdu_sign.c b/app/src/apdu_sign.c index d10c59962..6272eb2ce 100644 --- a/app/src/apdu_sign.c +++ b/app/src/apdu_sign.c @@ -193,6 +193,7 @@ refill() break; case TZ_ERR_INVALID_TAG: case TZ_ERR_INVALID_OP: + case TZ_ERR_INVALID_DATA: case TZ_ERR_UNSUPPORTED: case TZ_ERR_TOO_LARGE: case TZ_ERR_TOO_DEEP: @@ -222,6 +223,7 @@ send_cancel(void) break; case TZ_ERR_INVALID_TAG: case TZ_ERR_INVALID_OP: + case TZ_ERR_INVALID_DATA: case TZ_ERR_UNSUPPORTED: case TZ_ERR_TOO_LARGE: case TZ_ERR_TOO_DEEP: diff --git a/app/src/parser/operation_parser.c b/app/src/parser/operation_parser.c index a2449a706..083715872 100644 --- a/app/src/parser/operation_parser.c +++ b/app/src/parser/operation_parser.c @@ -286,7 +286,12 @@ tz_operation_parser_init(tz_parser_state *state, uint16_t size, state->operation.seen_reveal = 0; memset(&state->operation.source, 0, 22); memset(&state->operation.destination, 0, 22); - op->batch_index = 0; + op->batch_index = 0; +#ifdef HAVE_SWAP + op->last_tag = TZ_OPERATION_TAG_END; + op->last_fee = 0; + op->last_amount = 0; +#endif // HAVE_SWAP op->frame = op->stack; op->stack[0].stop = size; if (!skip_magic) { @@ -397,6 +402,9 @@ tz_operation_parser_step(tz_parser_state *state) const tz_operation_descriptor *d; uint8_t t; tz_must(tz_parser_read(state, &t)); +#ifdef HAVE_SWAP + op->last_tag = t; +#endif // HAVE_SWAP for (d = tz_operation_descriptors; d->tag != TZ_OPERATION_TAG_END; d++) { if (d->tag == t) { @@ -437,6 +445,21 @@ tz_operation_parser_step(tz_parser_state *state) &op->frame->step_read_num.state, b, op->frame->step_read_num.natural)); if (op->frame->step_read_num.state.stop) { +#ifdef HAVE_SWAP + uint64_t value; + if (!tz_string_to_mutez(state->buffers.num.decimal, &value)) + tz_raise(INVALID_DATA); + switch (op->frame->step_read_num.kind) { + case TZ_OPERATION_FIELD_AMOUNT: + op->last_amount = value; + break; + case TZ_OPERATION_FIELD_FEE: + op->last_fee = value; + break; + default: + break; + } +#endif // HAVE_SWAP if (op->frame->step_read_num.skip) { tz_must(pop_frame(state)); tz_continue; diff --git a/app/src/parser/operation_state.h b/app/src/parser/operation_state.h index 4a73ab2a5..d0b2e9733 100644 --- a/app/src/parser/operation_state.h +++ b/app/src/parser/operation_state.h @@ -151,4 +151,9 @@ typedef struct { uint8_t source[22]; // check consistent source in batch uint8_t destination[22]; // saved for entrypoint dispatch uint16_t batch_index; // to print a sequence number +#ifdef HAVE_SWAP + tz_operation_tag last_tag; + uint64_t last_fee; + uint64_t last_amount; +#endif // HAVE_SWAP } tz_operation_state; diff --git a/app/src/parser/parser_state.c b/app/src/parser/parser_state.c index ba9874569..38131604d 100644 --- a/app/src/parser/parser_state.c +++ b/app/src/parser/parser_state.c @@ -37,6 +37,7 @@ tz_parser_result_name(tz_parser_result code) BLO_LABEL(IM_FULL); TZ_LABEL(ERR_INVALID_TAG); TZ_LABEL(ERR_INVALID_OP); + TZ_LABEL(ERR_INVALID_DATA); TZ_LABEL(ERR_UNSUPPORTED); TZ_LABEL(ERR_TOO_LARGE); TZ_LABEL(ERR_TOO_DEEP); diff --git a/app/src/parser/parser_state.h b/app/src/parser/parser_state.h index 2ff16d4cc..ab2625bd3 100644 --- a/app/src/parser/parser_state.h +++ b/app/src/parser/parser_state.h @@ -55,6 +55,7 @@ typedef enum { // everything below is an error TZ_ERR_INVALID_TAG = 200, TZ_ERR_INVALID_OP, + TZ_ERR_INVALID_DATA, TZ_ERR_UNSUPPORTED, TZ_ERR_TOO_LARGE, TZ_ERR_TOO_DEEP, From a2fd76a9201f27cf2e646ebbf5c64a3098d76efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Palmer?= Date: Mon, 25 Sep 2023 12:06:59 +0200 Subject: [PATCH 09/10] Swap: check swap transaction validity --- app/src/apdu_sign.c | 19 ++++++++++++++++++- app/src/handle_swap.c | 26 ++++++++++++++++++++++++++ app/src/handle_swap.h | 25 +++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 app/src/handle_swap.h diff --git a/app/src/apdu_sign.c b/app/src/apdu_sign.c index 6272eb2ce..491c5bd98 100644 --- a/app/src/apdu_sign.c +++ b/app/src/apdu_sign.c @@ -40,6 +40,12 @@ #include "parser/parser_state.h" #include "parser/operation_parser.h" +#ifdef HAVE_SWAP +#include + +#include "handle_swap.h" +#endif // HAVE_SWAP + #ifdef HAVE_NBGL #include "nbgl_use_case.h" #endif @@ -243,7 +249,18 @@ stream_cb(tz_ui_cb_type_t type) // clang-format off switch (type) { - case TZ_UI_STREAM_CB_ACCEPT: return sign_packet(); + case TZ_UI_STREAM_CB_ACCEPT: +#ifdef HAVE_SWAP + if (G_called_from_swap) { + if (G_swap_response_ready) + os_sched_exit(-1); + else + G_swap_response_ready = true; + + TZ_CHECK(swap_check_validity()); + } +#endif // HAVE_SWAP + return sign_packet(); case TZ_UI_STREAM_CB_REFILL: return refill(); case TZ_UI_STREAM_CB_REJECT: return send_reject(); case TZ_UI_STREAM_CB_CANCEL: return send_cancel(); diff --git a/app/src/handle_swap.c b/app/src/handle_swap.c index 2ceaebba7..d5a0d8361 100644 --- a/app/src/handle_swap.c +++ b/app/src/handle_swap.c @@ -25,6 +25,8 @@ #include #include +#include "compat.h" +#include "handle_swap.h" #include "keys.h" #include "utils.h" @@ -208,6 +210,30 @@ swap_copy_transaction_parameters(create_transaction_parameters_t *params) return false; } +void +swap_check_validity(void) +{ + tz_operation_state *op = &global.apdu.sign.u.clear.parser_state.operation; + char dstaddr[ADDRESS_MAX_SIZE]; + TZ_PREAMBLE(("")); + + PRINTF("[DEBUG] batch_index = %u, tag=%d\n", op->batch_index, + op->last_tag); + TZ_ASSERT(EXC_REJECT, op->batch_index == 1); + TZ_ASSERT(EXC_REJECT, op->last_tag == TZ_OPERATION_TAG_TRANSACTION); + TZ_ASSERT(EXC_REJECT, op->last_amount == G_swap_params.amount); + TZ_ASSERT(EXC_REJECT, op->last_fee == G_swap_params.fee); + + tz_format_address(op->destination, 22, dstaddr, sizeof(dstaddr)); + + PRINTF("[DEBUG] dstaddr=\"%s\"\n", dstaddr); + PRINTF("[DEBUG] G...dstaddr=\"%s\"\n", G_swap_params.destination_address); + TZ_ASSERT(EXC_REJECT, + !strcmp(dstaddr, G_swap_params.destination_address)); + + TZ_POSTAMBLE; +} + /* Set create_transaction.result and call os_lib_end(). * * Doesn't return */ diff --git a/app/src/handle_swap.h b/app/src/handle_swap.h new file mode 100644 index 000000000..f3974c657 --- /dev/null +++ b/app/src/handle_swap.h @@ -0,0 +1,25 @@ +/* Tezos Ledger application - Swap requirement + + Copyright 2023 Nomadic Labs + Copyright 2023 TriliTech + Copyright 2023 Functori + + With code excerpts from: + - Legacy Tezos app, Copyright 2019 Obsidian Systems + - Ledger Blue sample apps, Copyright 2016 Ledger + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + +#ifdef HAVE_SWAP +void swap_check_validity(void); +#endif // HAVE_SWAP From 4496155d73e6e1354af5f4f5db410cc5364d858b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Palmer?= Date: Tue, 26 Sep 2023 10:58:38 +0200 Subject: [PATCH 10/10] Run the CI app-exchange tests --- .github/workflows/swap.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/swap.yml diff --git a/.github/workflows/swap.yml b/.github/workflows/swap.yml new file mode 100644 index 000000000..61d448df7 --- /dev/null +++ b/.github/workflows/swap.yml @@ -0,0 +1,18 @@ +name: Swap feature tests + +on: + workflow_dispatch: + push: + branches: + - main + pull_request: + +jobs: + job_functional_tests: + uses: spalmer25/app-exchange/.github/workflows/reusable_swap_functional_tests.yml@palmer/functori/add-new-tezos-app-swap-tests + with: + repo_for_exchange: 'spalmer25/app-exchange' + branch_for_exchange: 'palmer/functori/add-new-tezos-app-swap-tests' + branch_for_tezos: ${{ github.ref }} + run_for_devices: '["nanos", "nanosp"]' + test_filter: '"tezos_new"'