From c5cdd16627705827cb1c801717a29cfea8040721 Mon Sep 17 00:00:00 2001 From: Francois Beutin Date: Fri, 20 Dec 2024 18:24:12 +0100 Subject: [PATCH] Implement SPL swap --- Makefile | 15 +++-- libsol/printer.c | 1 - libsol/util.h | 5 ++ src/handle_sign_message.c | 31 ++++++++-- src/handle_sign_offchain_message.c | 2 +- src/{io.c => io_utils.c} | 2 +- src/{io.h => io_utils.h} | 0 src/main.c | 2 - src/swap/handle_check_address.c | 8 +-- src/swap/handle_get_printable_amount.c | 35 +++++++---- src/swap/handle_swap_sign_transaction.c | 53 ++++++++++++---- src/swap/handle_swap_sign_transaction.h | 2 + src/swap/swap_lib_calls.h | 80 ------------------------- src/swap/swap_utils.c | 16 ----- src/swap/swap_utils.h | 7 --- src/ui/get_pubkey_bagl.c | 2 +- src/ui/get_pubkey_nbgl.c | 2 +- src/ui/sign_message_bagl.c | 2 +- src/ui/sign_message_nbgl.c | 2 +- tests/python/apps/solana.py | 14 ++--- tests/python/test_solana.py | 12 +--- 21 files changed, 131 insertions(+), 162 deletions(-) rename src/{io.c => io_utils.c} (99%) rename src/{io.h => io_utils.h} (100%) delete mode 100644 src/swap/swap_lib_calls.h delete mode 100644 src/swap/swap_utils.c delete mode 100644 src/swap/swap_utils.h diff --git a/Makefile b/Makefile index cbb3d8c0..509f669c 100644 --- a/Makefile +++ b/Makefile @@ -36,9 +36,9 @@ APPNAME = "Solana" # Application version APPVERSION_M = 1 -APPVERSION_N = 6 -APPVERSION_P = 1 -APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)" +APPVERSION_N = 7 +APPVERSION_P = 0 +APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)-splswapv1" # Application source files APP_SOURCE_PATH += src @@ -66,11 +66,15 @@ VARIANT_VALUES = solana ######################################## # Application custom permissions # ######################################## -HAVE_APPLICATION_FLAG_LIBRARY = 1 ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_NANOX TARGET_STAX TARGET_FLEX)) HAVE_APPLICATION_FLAG_BOLOS_SETTINGS = 1 endif +######################################## +# Swap features # +######################################## +ENABLE_SWAP = 1 + ######################################## # Application communication interfaces # ######################################## @@ -90,6 +94,8 @@ DISABLE_STANDARD_APP_FILES = 1 # Allow usage of function from lib_standard_app/crypto_helpers.c APP_SOURCE_FILES += ${BOLOS_SDK}/lib_standard_app/crypto_helpers.c +APP_SOURCE_FILES += ${BOLOS_SDK}/lib_standard_app/swap_utils.c +CFLAGS += -I${BOLOS_SDK}/lib_standard_app/ WITH_U2F?=0 ifneq ($(WITH_U2F),0) @@ -102,6 +108,7 @@ WITH_LIBSOL?=1 ifneq ($(WITH_LIBSOL),0) SOURCE_FILES += $(filter-out %_test.c,$(wildcard libsol/*.c)) CFLAGS += -Ilibsol/include + CFLAGS += -Ilibsol DEFINES += HAVE_SNPRINTF_FORMAT_U DEFINES += NDEBUG endif diff --git a/libsol/printer.c b/libsol/printer.c index 27b7489f..10ed392c 100644 --- a/libsol/printer.c +++ b/libsol/printer.c @@ -61,7 +61,6 @@ int print_token_amount(uint64_t amount, return 0; } -#define SOL_DECIMALS 9 int print_amount(uint64_t amount, char *out, size_t out_length) { return print_token_amount(amount, "SOL", SOL_DECIMALS, out, out_length); } diff --git a/libsol/util.h b/libsol/util.h index 3e041767..a3795313 100644 --- a/libsol/util.h +++ b/libsol/util.h @@ -7,12 +7,17 @@ int err = x; \ if (err) return err; \ } while (0) + +#ifndef MIN #define MIN(a, b) ((a) < (b) ? (a) : (b)); +#endif #define assert_string_equal(actual, expected) assert(strcmp(actual, expected) == 0) #define assert_pubkey_equal(actual, expected) assert(memcmp(actual, expected, 32) == 0) +#define SOL_DECIMALS 9 + #ifndef UNUSED #define UNUSED(x) (void) x #endif diff --git a/src/handle_sign_message.c b/src/handle_sign_message.c index 5276c553..5e535587 100644 --- a/src/handle_sign_message.c +++ b/src/handle_sign_message.c @@ -1,4 +1,4 @@ -#include "io.h" +#include "io_utils.h" #include "utils.h" #include "handle_swap_sign_transaction.h" @@ -93,24 +93,47 @@ void handle_sign_message_parse_message(volatile unsigned int *tx) { static bool check_swap_validity(const SummaryItemKind_t kinds[MAX_TRANSACTION_SUMMARY_ITEMS], size_t num_summary_steps) { + bool is_token_swap = is_token_transaction(); bool amount_ok = false; bool recipient_ok = false; - if (num_summary_steps != 2 && num_summary_steps != 3) { - PRINTF("2 or 3 steps expected for transaction in swap context, not %u\n", + uint8_t expected_steps; + if (is_token_swap) { + expected_steps = 4; + } else { + expected_steps = 2; + } + // Accept base step number + optional fee step + if (num_summary_steps != expected_steps && num_summary_steps != expected_steps + 1) { + PRINTF("%d steps expected for token transaction in swap context, not %u\n", + expected_steps, num_summary_steps); return false; } + for (size_t i = 0; i < num_summary_steps; ++i) { transaction_summary_display_item(i, DisplayFlagNone | DisplayFlagLongPubkeys); + PRINTF("Item (%d) '%s', '%s'\n", kinds[i], G_transaction_summary_title, G_transaction_summary_text); switch (kinds[i]) { + case SummaryItemTokenAmount: + amount_ok = + check_swap_amount(G_transaction_summary_title, G_transaction_summary_text); + break; case SummaryItemAmount: if (strcmp(G_transaction_summary_title, "Max fees") == 0) { - break; // Should we check the fees ? + break; } amount_ok = check_swap_amount(G_transaction_summary_title, G_transaction_summary_text); break; case SummaryItemPubkey: + if (is_token_swap && strcmp(G_transaction_summary_title, "Token address") == 0) { + PRINTF("Skip %s field\n", G_transaction_summary_title); + break; + } + if (is_token_swap && strcmp(G_transaction_summary_title, "From (token account)") == 0) { + PRINTF("Skip %s field\n", G_transaction_summary_title); + break; + } recipient_ok = check_swap_recipient(G_transaction_summary_title, G_transaction_summary_text); break; diff --git a/src/handle_sign_offchain_message.c b/src/handle_sign_offchain_message.c index 1b5a725d..dc0a743b 100644 --- a/src/handle_sign_offchain_message.c +++ b/src/handle_sign_offchain_message.c @@ -1,4 +1,4 @@ -#include "io.h" +#include "io_utils.h" #include "os.h" #include "ux.h" #include "cx.h" diff --git a/src/io.c b/src/io_utils.c similarity index 99% rename from src/io.c rename to src/io_utils.c index d320de33..c236e305 100644 --- a/src/io.c +++ b/src/io_utils.c @@ -29,7 +29,7 @@ #include "apdu.h" #include "ui_api.h" #include "handle_swap_sign_transaction.h" -#include "io.h" +#include "io_utils.h" #ifdef HAVE_BAGL // override point, but nothing more to do diff --git a/src/io.h b/src/io_utils.h similarity index 100% rename from src/io.h rename to src/io_utils.h diff --git a/src/main.c b/src/main.c index b991c528..76d5751d 100644 --- a/src/main.c +++ b/src/main.c @@ -36,8 +36,6 @@ ApduCommand G_command; unsigned char G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B]; -volatile bool G_called_from_swap; -volatile bool G_swap_response_ready; static void reset_main_globals(void) { MEMCLEAR(G_command); diff --git a/src/swap/handle_check_address.c b/src/swap/handle_check_address.c index c777dcd1..4f964191 100644 --- a/src/swap/handle_check_address.c +++ b/src/swap/handle_check_address.c @@ -27,10 +27,10 @@ int handle_check_address(const check_address_parameters_t *params) { PRINTF("Inside Solana handle_check_address\n"); PRINTF("Params on the address %d\n", (unsigned int) params); - if (params->coin_configuration != NULL || params->coin_configuration_length != 0) { - PRINTF("No coin_configuration expected\n"); - return 0; - } + // if (params->coin_configuration != NULL || params->coin_configuration_length != 0) { + // PRINTF("No coin_configuration expected\n"); + // return 0; + // } if (params->address_parameters == NULL) { PRINTF("derivation path expected\n"); diff --git a/src/swap/handle_get_printable_amount.c b/src/swap/handle_get_printable_amount.c index c721081b..8d0301d7 100644 --- a/src/swap/handle_get_printable_amount.c +++ b/src/swap/handle_get_printable_amount.c @@ -4,28 +4,41 @@ #include "utils.h" #include "sol/printer.h" +#define MAX_SWAP_TOKEN_LENGTH 15 + /* return 0 on error, 1 otherwise */ int handle_get_printable_amount(get_printable_amount_parameters_t* params) { PRINTF("Inside Solana handle_get_printable_amount\n"); MEMCLEAR(params->printable_amount); - // Fees are displayed normally - // params->is_fee - - if (params->coin_configuration != NULL || params->coin_configuration_length != 0) { - PRINTF("No coin_configuration expected\n"); - return 0; - } - uint64_t amount; if (!swap_str_to_u64(params->amount, params->amount_length, &amount)) { PRINTF("Amount is too big"); return 0; } - if (print_amount(amount, params->printable_amount, sizeof(params->printable_amount)) != 0) { - PRINTF("print_amount failed"); - return 0; + // Fees are displayed normally + if (params->is_fee || params->coin_configuration == NULL) { + PRINTF("Defaulting to native SOL amount\n"); + if (print_amount(amount, params->printable_amount, sizeof(params->printable_amount)) != 0) { + PRINTF("print_amount failed"); + return 0; + } + } else { + uint8_t decimals; + char ticker[MAX_SWAP_TOKEN_LENGTH] = {0}; + if (!swap_parse_config(params->coin_configuration, + params->coin_configuration_length, + ticker, + sizeof(ticker), + &decimals)) { + PRINTF("Fail to parse coin_configuration\n"); + return 0; + } + if (print_token_amount(amount, ticker, decimals, params->printable_amount, sizeof(params->printable_amount)) != 0) { + PRINTF("print_amount failed"); + return 0; + } } PRINTF("Amount %s\n", params->printable_amount); diff --git a/src/swap/handle_swap_sign_transaction.c b/src/swap/handle_swap_sign_transaction.c index 7575ee13..b5f4fb93 100644 --- a/src/swap/handle_swap_sign_transaction.c +++ b/src/swap/handle_swap_sign_transaction.c @@ -4,9 +4,14 @@ #include "swap_lib_calls.h" #include "swap_utils.h" #include "sol/printer.h" +#include "util.h" + +#define MAX_SWAP_TOKEN_LENGTH 15 typedef struct swap_validated_s { bool initialized; + uint8_t decimals; + char ticker[MAX_SWAP_TOKEN_LENGTH]; uint64_t amount; char recipient[BASE58_PUBKEY_LENGTH]; } swap_validated_t; @@ -18,12 +23,6 @@ static uint8_t *G_swap_sign_return_value_address; // Save the data validated during the Exchange app flow bool copy_transaction_parameters(create_transaction_parameters_t *params) { - // Ensure no subcoin configuration - if (params->coin_configuration != NULL || params->coin_configuration_length != 0) { - PRINTF("No coin_configuration expected\n"); - return false; - } - // Ensure no extraid if (params->destination_address_extra_id == NULL) { PRINTF("destination_address_extra_id expected\n"); @@ -39,6 +38,22 @@ bool copy_transaction_parameters(create_transaction_parameters_t *params) { swap_validated_t swap_validated; memset(&swap_validated, 0, sizeof(swap_validated)); + // Parse config and save decimals and ticker + // If there is no coin_configuration, consider that we are doing a SOL swap + if (params->coin_configuration == NULL) { + memcpy(swap_validated.ticker, "SOL", sizeof("SOL")); + swap_validated.decimals = SOL_DECIMALS; + } else { + if (!swap_parse_config(params->coin_configuration, + params->coin_configuration_length, + swap_validated.ticker, + sizeof(swap_validated.ticker), + &swap_validated.decimals)) { + PRINTF("Fail to parse coin_configuration\n"); + return false; + } + } + // Save recipient strlcpy(swap_validated.recipient, params->destination_address, @@ -72,13 +87,19 @@ bool check_swap_amount(const char *title, const char *text) { return false; } - if (strcmp(title, "Transfer") != 0) { - PRINTF("Refused field '%s', expecting 'Transfer'\n", title); + char expected_title[MAX(sizeof("Transfer tokens"), sizeof("Transfer"))] = {'\0'}; + if (is_token_transaction()) { + strcpy(expected_title, "Transfer tokens"); + } else { + strcpy(expected_title, "Transfer"); + } + if (strcmp(title, expected_title) != 0) { + PRINTF("Refused title '%s', expecting '%s'\n", title, expected_title); return false; } char validated_amount[MAX_PRINTABLE_AMOUNT_SIZE]; - if (print_amount(G_swap_validated.amount, validated_amount, sizeof(validated_amount)) != 0) { + if (print_token_amount(G_swap_validated.amount, G_swap_validated.ticker, G_swap_validated.decimals, validated_amount, sizeof(validated_amount)) != 0) { PRINTF("Conversion failed\n"); return false; } @@ -98,8 +119,14 @@ bool check_swap_recipient(const char *title, const char *text) { return false; } - if (strcmp(title, "Recipient") != 0) { - PRINTF("Refused field '%s', expecting 'Recipient'\n", title); + char expected_title[MAX(sizeof("To (token account)"), sizeof("Recipient"))] = {'\0'}; + if (is_token_transaction()) { + strcpy(expected_title, "To (token account)"); + } else { + strcpy(expected_title, "Recipient"); + } + if (strcmp(title, expected_title) != 0) { + PRINTF("Refused title '%s', expecting '%s'\n", title, expected_title); return false; } @@ -116,3 +143,7 @@ void __attribute__((noreturn)) finalize_exchange_sign_transaction(bool is_succes *G_swap_sign_return_value_address = is_success; os_lib_end(); } + +bool is_token_transaction() { + return (memcmp(G_swap_validated.ticker, "SOL", sizeof("SOL")) != 0); +} diff --git a/src/swap/handle_swap_sign_transaction.h b/src/swap/handle_swap_sign_transaction.h index 06fbbfe4..bc43aa07 100644 --- a/src/swap/handle_swap_sign_transaction.h +++ b/src/swap/handle_swap_sign_transaction.h @@ -8,4 +8,6 @@ bool check_swap_amount(const char *title, const char *text); bool check_swap_recipient(const char *title, const char *text); +bool is_token_transaction(); + void __attribute__((noreturn)) finalize_exchange_sign_transaction(bool is_success); diff --git a/src/swap/swap_lib_calls.h b/src/swap/swap_lib_calls.h deleted file mode 100644 index 213d8b21..00000000 --- a/src/swap/swap_lib_calls.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -/* This file is the shared API between Exchange and the apps started in Library mode for Exchange - * - * DO NOT MODIFY THIS FILE IN APPLICATIONS OTHER THAN EXCHANGE - * On modification in Exchange, forward the changes to all applications supporting Exchange - */ - -#include "stdbool.h" -#include "stdint.h" - -#define RUN_APPLICATION 1 - -#define SIGN_TRANSACTION 2 - -#define CHECK_ADDRESS 3 - -#define GET_PRINTABLE_AMOUNT 4 - -/* - * Amounts are stored as bytes, with a max size of 16 (see protobuf - * specifications). Max 16B integer is 340282366920938463463374607431768211455 - * in decimal, which is a 32-long char string. - * The printable amount also contains spaces, the ticker symbol (with variable - * size, up to 12 in Ethereum for instance) and a terminating null byte, so 50 - * bytes total should be a fair maximum. - */ -#define MAX_PRINTABLE_AMOUNT_SIZE 50 - -// structure that should be send to specific coin application to get address -typedef struct check_address_parameters_s { - // IN - uint8_t *coin_configuration; - uint8_t coin_configuration_length; - // serialized path, segwit, version prefix, hash used, dictionary etc. - // fields and serialization format depends on specific coin app - uint8_t *address_parameters; - uint8_t address_parameters_length; - char *address_to_check; - char *extra_id_to_check; - // OUT - int result; -} check_address_parameters_t; - -// structure that should be send to specific coin application to get printable amount -typedef struct get_printable_amount_parameters_s { - // IN - uint8_t *coin_configuration; - uint8_t coin_configuration_length; - uint8_t *amount; - uint8_t amount_length; - bool is_fee; - // OUT - char printable_amount[MAX_PRINTABLE_AMOUNT_SIZE]; -} get_printable_amount_parameters_t; - -typedef struct create_transaction_parameters_s { - // IN - uint8_t *coin_configuration; - uint8_t coin_configuration_length; - uint8_t *amount; - uint8_t amount_length; - uint8_t *fee_amount; - uint8_t fee_amount_length; - char *destination_address; - char *destination_address_extra_id; - // OUT - uint8_t result; -} create_transaction_parameters_t; - -typedef struct libargs_s { - unsigned int id; - unsigned int command; - unsigned int unused; - union { - check_address_parameters_t *check_address; - create_transaction_parameters_t *create_transaction; - get_printable_amount_parameters_t *get_printable_amount; - }; -} libargs_t; diff --git a/src/swap/swap_utils.c b/src/swap/swap_utils.c deleted file mode 100644 index ac2f6cd0..00000000 --- a/src/swap/swap_utils.c +++ /dev/null @@ -1,16 +0,0 @@ -#include - -#include "swap_utils.h" - -bool swap_str_to_u64(const uint8_t* src, size_t length, uint64_t* result) { - if (length > sizeof(uint64_t)) { - return false; - } - uint64_t value = 0; - for (size_t i = 0; i < length; i++) { - value <<= 8; - value |= src[i]; - } - *result = value; - return true; -} diff --git a/src/swap/swap_utils.h b/src/swap/swap_utils.h deleted file mode 100644 index 5f25201e..00000000 --- a/src/swap/swap_utils.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "stdint.h" -#include "stddef.h" -#include "stdbool.h" - -bool swap_str_to_u64(const uint8_t* src, size_t length, uint64_t* result); diff --git a/src/ui/get_pubkey_bagl.c b/src/ui/get_pubkey_bagl.c index 9e2d978e..0dfb148d 100644 --- a/src/ui/get_pubkey_bagl.c +++ b/src/ui/get_pubkey_bagl.c @@ -20,7 +20,7 @@ #include "handle_get_pubkey.h" #include "apdu.h" -#include "io.h" +#include "io_utils.h" #include "ux.h" UX_STEP_NOCB(ux_display_public_flow_5_step, diff --git a/src/ui/get_pubkey_nbgl.c b/src/ui/get_pubkey_nbgl.c index 4d077146..b8976c0b 100644 --- a/src/ui/get_pubkey_nbgl.c +++ b/src/ui/get_pubkey_nbgl.c @@ -18,7 +18,7 @@ #ifdef HAVE_NBGL #include "handle_get_pubkey.h" -#include "io.h" +#include "io_utils.h" #include "sol/printer.h" #include "nbgl_use_case.h" #include "ui_api.h" diff --git a/src/ui/sign_message_bagl.c b/src/ui/sign_message_bagl.c index 22e81a39..0fb4e126 100644 --- a/src/ui/sign_message_bagl.c +++ b/src/ui/sign_message_bagl.c @@ -1,6 +1,6 @@ #ifdef HAVE_BAGL -#include "io.h" +#include "io_utils.h" #include "utils.h" #include "sol/parser.h" #include "sol/printer.h" diff --git a/src/ui/sign_message_nbgl.c b/src/ui/sign_message_nbgl.c index 80388e4a..002c59e1 100644 --- a/src/ui/sign_message_nbgl.c +++ b/src/ui/sign_message_nbgl.c @@ -1,6 +1,6 @@ #ifdef HAVE_NBGL -#include "io.h" +#include "io_utils.h" #include "sol/parser.h" #include "sol/printer.h" #include "sol/print_config.h" diff --git a/tests/python/apps/solana.py b/tests/python/apps/solana.py index 1980bf23..2ef0d9d2 100644 --- a/tests/python/apps/solana.py +++ b/tests/python/apps/solana.py @@ -97,7 +97,7 @@ def __init__(self, client: BackendInterface) -> None: def send_certificate(self, payload: bytes) -> RAPDU: response = self.send_raw(payload) assert response.status == StatusWord.OK - + def send_raw(self, payload: bytes) -> RAPDU: header = bytearray() @@ -125,7 +125,7 @@ def provide_trusted_name(self, address: bytes, chain_id: int, challenge: Optional[int] = None): - + payload = format_tlv(FieldTag.TAG_STRUCTURE_TYPE, 3) payload += format_tlv(FieldTag.TAG_VERSION, 2) payload += format_tlv(FieldTag.TAG_TRUSTED_NAME_TYPE, 0x06) @@ -140,7 +140,7 @@ def provide_trusted_name(self, payload += format_tlv(FieldTag.TAG_SIGNER_ALGO, 1) # secp256k1 payload += format_tlv(FieldTag.TAG_DER_SIGNATURE, sign_data(Key.TRUSTED_NAME, payload)) - + # send PKI certificate if self._pki_client is None: print(f"Ledger-PKI Not supported on '{self._client.firmware.name}'") @@ -156,16 +156,16 @@ def provide_trusted_name(self, cert_apdu = "01010102010211040000000212010013020002140101160400000000200C547275737465645F4E616D6530020004310104320121332102B91FBEC173E3BA4A714E014EBC827B6F899A9FA7F4AC769CDE284317A00F4F6534010135010515473045022100CEF28780DCAFA3A485D83406D519F9AC12FD9B9C3AA7AE798896013F07DD178D022020F01B1AB1D2AAEDA70357F615EAC55E17FE94EC36DF9DE850CEFACBC98D16C8" # noqa: E501 # pylint: enable=line-too-long - self._pki_client.send_certificate(bytes.fromhex(cert_apdu)) - + self._pki_client.send_certificate(bytes.fromhex(cert_apdu)) + # send TLV trusted info res: RAPDU = self._client.exchange(CLA, INS.INS_TRUSTED_INFO, P1_NON_CONFIRM, P2_NONE, payload) assert res.status == StatusWord.OK def get_challenge(self) -> bytes: challenge: RAPDU = self._client.exchange(CLA, INS.INS_GET_CHALLENGE,P1_NON_CONFIRM, P2_NONE) - - assert challenge.status == StatusWord.OK + + assert challenge.status == StatusWord.OK return challenge.data def get_public_key(self, derivation_path: bytes) -> bytes: diff --git a/tests/python/test_solana.py b/tests/python/test_solana.py index 2bc64530..94db62b2 100644 --- a/tests/python/test_solana.py +++ b/tests/python/test_solana.py @@ -219,24 +219,18 @@ def test_solana_trusted_name(self, backend, scenario_navigator): challenge = sol.get_challenge() sol.provide_trusted_name(bytes(SOURCE_CONTRACT, 'utf-8'), - bytes(TRUSTED_NAME, 'utf-8'), + bytes(TRUSTED_NAME, 'utf-8'), bytes(ADDRESS, 'utf-8'), CHAIN_ID, challenge=challenge) - + from_public_key = sol.get_public_key(SOL_PACKED_DERIVATION_PATH) # Create message (SPL Token transfer) message: bytes = bytes.fromhex("0100030621a36fe74e1234c35e62bfd700fd247b92c4d4e0e538401ac51f5c4ae97657a7276497ba0bb8659172b72edd8c66e18f561764d9c86a610a3a7e0f79c0baf9dbc71573813ea96479a79e579af14646413602b9b3dcbdc51cbf8e064b5685ed120479d9c7cc1035de7211f99eb48c09d70b2bdf5bdf9e2e56b8a1fbb5a2ea332706ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a938b19525b109c0e2517df8786389e33365afe2dc6bfabeb65458fd24a1ab5b13000000000000000000000000000000000000000000000000000000000000000001040501030205000a0c020000000000000006") - + with sol.send_async_sign_message(SOL_PACKED_DERIVATION_PATH, message): scenario_navigator.review_approve(path=ROOT_SCREENSHOT_PATH) signature: bytes = sol.get_async_response().data verify_signature(from_public_key, message, signature) - - - - - -