From be078c11d748f2315ef2e3d61a2472ba2bcbbf78 Mon Sep 17 00:00:00 2001 From: Robert Lukotka Date: Tue, 16 Aug 2022 22:43:01 +0200 Subject: [PATCH] Message signing --- app/src/apdu_handler.c | 12 +++ app/src/coin.h | 2 + app/src/common/actions.c | 16 ++++ app/src/common/actions.h | 25 ++++- app/src/common/app_main.c | 16 +++- app/src/common/app_main.h | 1 + app/src/common/tx.c | 20 ---- app/src/crypto.c | 25 +++-- app/src/crypto.h | 1 + app/src/message.c | 90 ++++++++++++++++++ app/src/message.h | 29 ++++++ deps/ledger-zxlib/dockerized_build.mk | 1 + docs/APDUSPEC.md | 31 ++++-- js/src/index.js | 10 +- js/src/signTransaction.js | 15 ++- tests_speculos/test-messages.js | 111 ++++++++++++++++++++++ tests_speculos/test-messages/nanos.01.png | Bin 0 -> 380 bytes tests_speculos/test-messages/nanos.02.png | Bin 0 -> 529 bytes tests_speculos/test-messages/nanos.03.png | Bin 0 -> 587 bytes tests_speculos/test-messages/nanos.04.png | Bin 0 -> 542 bytes tests_speculos/test-messages/nanos.05.png | Bin 0 -> 249 bytes tests_speculos/test-messages/nanos.06.png | Bin 0 -> 380 bytes tests_speculos/test-messages/nanos.07.png | Bin 0 -> 657 bytes tests_speculos/test-messages/nanos.08.png | Bin 0 -> 639 bytes tests_speculos/test-messages/nanos.09.png | Bin 0 -> 249 bytes tests_speculos/test-messages/nanos.10.png | Bin 0 -> 380 bytes tests_speculos/test-messages/nanos.11.png | Bin 0 -> 483 bytes tests_speculos/test-messages/nanos.12.png | Bin 0 -> 468 bytes tests_speculos/test-messages/nanos.13.png | Bin 0 -> 380 bytes tests_speculos/test-messages/nanos.14.png | Bin 0 -> 642 bytes tests_speculos/test-messages/nanos.15.png | Bin 0 -> 641 bytes tests_speculos/test-messages/nanos.16.png | Bin 0 -> 546 bytes tests_speculos/test-messages/nanos.17.png | Bin 0 -> 249 bytes tests_speculos/test-messages/nanos.18.png | Bin 0 -> 380 bytes tests_speculos/test-messages/nanos.19.png | Bin 0 -> 468 bytes tests_speculos/test-messages/nanos.20.png | Bin 0 -> 483 bytes tests_speculos/test-messages/nanos.21.png | Bin 0 -> 380 bytes tests_speculos/test-messages/nanox.01.png | Bin 0 -> 409 bytes tests_speculos/test-messages/nanox.02.png | Bin 0 -> 420 bytes tests_speculos/test-messages/nanox.03.png | Bin 0 -> 791 bytes tests_speculos/test-messages/nanox.04.png | Bin 0 -> 780 bytes tests_speculos/test-messages/nanox.05.png | Bin 0 -> 355 bytes tests_speculos/test-messages/nanox.06.png | Bin 0 -> 409 bytes tests_speculos/test-messages/nanox.07.png | Bin 0 -> 420 bytes tests_speculos/test-messages/nanox.08.png | Bin 0 -> 933 bytes tests_speculos/test-messages/nanox.09.png | Bin 0 -> 551 bytes tests_speculos/test-messages/nanox.10.png | Bin 0 -> 355 bytes tests_speculos/test-messages/nanox.11.png | Bin 0 -> 409 bytes tests_speculos/test-messages/nanox.12.png | Bin 0 -> 442 bytes tests_speculos/test-messages/nanox.13.png | Bin 0 -> 428 bytes tests_speculos/test-messages/nanox.14.png | Bin 0 -> 409 bytes tests_speculos/test-messages/nanox.15.png | Bin 0 -> 420 bytes tests_speculos/test-messages/nanox.16.png | Bin 0 -> 942 bytes tests_speculos/test-messages/nanox.17.png | Bin 0 -> 540 bytes tests_speculos/test-messages/nanox.18.png | Bin 0 -> 608 bytes tests_speculos/test-messages/nanox.19.png | Bin 0 -> 355 bytes tests_speculos/test-messages/nanox.20.png | Bin 0 -> 409 bytes tests_speculos/test-messages/nanox.21.png | Bin 0 -> 428 bytes tests_speculos/test-messages/nanox.22.png | Bin 0 -> 442 bytes tests_speculos/test-messages/nanox.23.png | Bin 0 -> 409 bytes 60 files changed, 356 insertions(+), 49 deletions(-) create mode 100644 app/src/message.c create mode 100644 app/src/message.h create mode 100644 tests_speculos/test-messages.js create mode 100644 tests_speculos/test-messages/nanos.01.png create mode 100644 tests_speculos/test-messages/nanos.02.png create mode 100644 tests_speculos/test-messages/nanos.03.png create mode 100644 tests_speculos/test-messages/nanos.04.png create mode 100644 tests_speculos/test-messages/nanos.05.png create mode 100644 tests_speculos/test-messages/nanos.06.png create mode 100644 tests_speculos/test-messages/nanos.07.png create mode 100644 tests_speculos/test-messages/nanos.08.png create mode 100644 tests_speculos/test-messages/nanos.09.png create mode 100644 tests_speculos/test-messages/nanos.10.png create mode 100644 tests_speculos/test-messages/nanos.11.png create mode 100644 tests_speculos/test-messages/nanos.12.png create mode 100644 tests_speculos/test-messages/nanos.13.png create mode 100644 tests_speculos/test-messages/nanos.14.png create mode 100644 tests_speculos/test-messages/nanos.15.png create mode 100644 tests_speculos/test-messages/nanos.16.png create mode 100644 tests_speculos/test-messages/nanos.17.png create mode 100644 tests_speculos/test-messages/nanos.18.png create mode 100644 tests_speculos/test-messages/nanos.19.png create mode 100644 tests_speculos/test-messages/nanos.20.png create mode 100644 tests_speculos/test-messages/nanos.21.png create mode 100644 tests_speculos/test-messages/nanox.01.png create mode 100644 tests_speculos/test-messages/nanox.02.png create mode 100644 tests_speculos/test-messages/nanox.03.png create mode 100644 tests_speculos/test-messages/nanox.04.png create mode 100644 tests_speculos/test-messages/nanox.05.png create mode 100644 tests_speculos/test-messages/nanox.06.png create mode 100644 tests_speculos/test-messages/nanox.07.png create mode 100644 tests_speculos/test-messages/nanox.08.png create mode 100644 tests_speculos/test-messages/nanox.09.png create mode 100644 tests_speculos/test-messages/nanox.10.png create mode 100644 tests_speculos/test-messages/nanox.11.png create mode 100644 tests_speculos/test-messages/nanox.12.png create mode 100644 tests_speculos/test-messages/nanox.13.png create mode 100644 tests_speculos/test-messages/nanox.14.png create mode 100644 tests_speculos/test-messages/nanox.15.png create mode 100644 tests_speculos/test-messages/nanox.16.png create mode 100644 tests_speculos/test-messages/nanox.17.png create mode 100644 tests_speculos/test-messages/nanox.18.png create mode 100644 tests_speculos/test-messages/nanox.19.png create mode 100644 tests_speculos/test-messages/nanox.20.png create mode 100644 tests_speculos/test-messages/nanox.21.png create mode 100644 tests_speculos/test-messages/nanox.22.png create mode 100644 tests_speculos/test-messages/nanox.23.png diff --git a/app/src/apdu_handler.c b/app/src/apdu_handler.c index 5cbf67a0..476e71d3 100644 --- a/app/src/apdu_handler.c +++ b/app/src/apdu_handler.c @@ -32,6 +32,7 @@ #include "zxformat.h" #include "hdpath.h" #include "parser_impl.h" +#include "message.h" __Z_INLINE void handleGetPubkey(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { hasPubkey = false; @@ -86,6 +87,17 @@ __Z_INLINE void handleSign(volatile uint32_t *flags, volatile uint32_t *tx, uint THROW(APDU_CODE_OK); } + if (callType == PROCESS_CHUNK_FINISHED_MESSAGE) { + zxerr_t err = message_parse(); + if (err != zxerr_ok) { + THROW(APDU_CODE_DATA_INVALID); + } + CHECK_APP_CANARY() + view_review_init(message_getItem, message_getNumItems, app_sign_message); + view_review_show(); + *flags |= IO_ASYNCH_REPLY; + } + const char *error_msg = tx_parse(callType); if (error_msg != NULL) { diff --git a/app/src/coin.h b/app/src/coin.h index a6d0c21f..9bbce45c 100644 --- a/app/src/coin.h +++ b/app/src/coin.h @@ -55,6 +55,8 @@ typedef struct { #define MAIN_SLOT 0 +#define DOMAIN_TAG_LENGTH 32 + #ifdef __cplusplus } #endif diff --git a/app/src/common/actions.c b/app/src/common/actions.c index 079a300d..93e04d9d 100644 --- a/app/src/common/actions.c +++ b/app/src/common/actions.c @@ -18,3 +18,19 @@ #include "actions.h" uint16_t action_addr_len; + +// UTF-8 encoding of "FLOW-V0.0-transaction" padded with zeros to 32 bytes +const uint8_t TX_DOMAIN_TAG_TRANSACTION[DOMAIN_TAG_LENGTH] = {\ + 0x46, 0x4C, 0x4F, 0x57, 0x2D, 0x56, 0x30, 0x2E, + 0x30, 0x2D, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6F, 0x6E, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +const uint8_t TX_DOMAIN_TAG_MESSAGE[DOMAIN_TAG_LENGTH] = {\ + 0x46, 0x4C, 0x4F, 0x57, 0x2D, 0x56, 0x30, 0x2E, + 0x30, 0x2D, 0x75, 0x73, 0x65, 0x72, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + diff --git a/app/src/common/actions.h b/app/src/common/actions.h index a012a96c..0e673135 100644 --- a/app/src/common/actions.h +++ b/app/src/common/actions.h @@ -25,12 +25,31 @@ #define GET_PUB_KEY_RESPONSE_LENGTH (3 * SECP256_PK_LEN) +extern const uint8_t TX_DOMAIN_TAG_TRANSACTION[DOMAIN_TAG_LENGTH]; +extern const uint8_t TX_DOMAIN_TAG_MESSAGE[DOMAIN_TAG_LENGTH]; + __Z_INLINE void app_sign() { - const uint8_t *message = get_signable(); - const uint16_t messageLength = get_signable_length(); + const uint8_t *message = tx_get_buffer(); + const uint16_t messageLength = tx_get_buffer_length(); + + uint16_t replyLen = 0; + zxerr_t err = crypto_sign(hdPath, cryptoOptions, message, messageLength, TX_DOMAIN_TAG_TRANSACTION, G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 3, &replyLen); + + if (err != zxerr_ok || replyLen == 0) { + set_code(G_io_apdu_buffer, 0, APDU_CODE_SIGN_VERIFY_ERROR); + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); + } else { + set_code(G_io_apdu_buffer, replyLen, APDU_CODE_OK); + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, replyLen + 2); + } +} + +__Z_INLINE void app_sign_message() { + const uint8_t *message = tx_get_buffer(); + const uint16_t messageLength = tx_get_buffer_length(); uint16_t replyLen = 0; - zxerr_t err = crypto_sign(hdPath, cryptoOptions, message, messageLength, G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 3, &replyLen); + zxerr_t err = crypto_sign(hdPath, cryptoOptions, message, messageLength, TX_DOMAIN_TAG_MESSAGE, G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 3, &replyLen); if (err != zxerr_ok || replyLen == 0) { set_code(G_io_apdu_buffer, 0, APDU_CODE_SIGN_VERIFY_ERROR); diff --git a/app/src/common/app_main.c b/app/src/common/app_main.c index c85e438d..de7d1852 100644 --- a/app/src/common/app_main.c +++ b/app/src/common/app_main.c @@ -133,33 +133,39 @@ process_chunk_response_t process_chunk(__Z_UNUSED volatile uint32_t *tx, uint32_ extractHDPathAndCryptoOptions(rx, OFFSET_DATA); initStoredTxMetadata(); return PROCESS_CHUNK_NOT_FINISHED; - case 1: + case 0x01: added = tx_append(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA); if (added != rx - OFFSET_DATA) { THROW(APDU_CODE_OUTPUT_BUFFER_TOO_SMALL); } return PROCESS_CHUNK_NOT_FINISHED; - case 2: + case 0x02: added = tx_append(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA); if (added != rx - OFFSET_DATA) { THROW(APDU_CODE_OUTPUT_BUFFER_TOO_SMALL); } return PROCESS_CHUNK_FINISHED_NO_METADATA; - case 3: + case 0x03: if (storeTxMetadata(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA) != PARSER_OK) { THROW(APDU_CODE_DATA_INVALID); } return PROCESS_CHUNK_NOT_FINISHED; - case 4: + case 0x04: if (validateStoredTxMetadataMerkleTreeLevel(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA) != PARSER_OK) { THROW(APDU_CODE_DATA_INVALID); } return PROCESS_CHUNK_NOT_FINISHED; - case 5: + case 0x05: if (validateStoredTxMetadataMerkleTreeLevel(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA) != PARSER_OK) { THROW(APDU_CODE_DATA_INVALID); } return PROCESS_CHUNK_FINISHED_WITH_METADATA; + case 0x10: + added = tx_append(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA); + if (added != rx - OFFSET_DATA) { + THROW(APDU_CODE_OUTPUT_BUFFER_TOO_SMALL); + } + return PROCESS_CHUNK_FINISHED_MESSAGE; } THROW(APDU_CODE_INVALIDP1P2); diff --git a/app/src/common/app_main.h b/app/src/common/app_main.h index 4a20680d..9f9fcbb0 100644 --- a/app/src/common/app_main.h +++ b/app/src/common/app_main.h @@ -49,6 +49,7 @@ typedef enum { PROCESS_CHUNK_NOT_FINISHED = 0, PROCESS_CHUNK_FINISHED_WITH_METADATA, PROCESS_CHUNK_FINISHED_NO_METADATA, + PROCESS_CHUNK_FINISHED_MESSAGE, } process_chunk_response_t; process_chunk_response_t process_chunk(volatile uint32_t *tx, uint32_t rx); diff --git a/app/src/common/tx.c b/app/src/common/tx.c index e2b0249e..9305c93f 100644 --- a/app/src/common/tx.c +++ b/app/src/common/tx.c @@ -46,14 +46,6 @@ storage_t NV_CONST N_appdata_impl __attribute__ ((aligned(64))); parser_context_t ctx_parsed_tx; -// UTF-8 encoding of "FLOW-V0.0-transaction" padded with zeros to 32 bytes -#define DOMAIN_TAG_LENGTH 32 -const uint8_t TX_DOMAIN_TAG[DOMAIN_TAG_LENGTH] = {\ - 0x46, 0x4C, 0x4F, 0x57, 0x2D, 0x56, 0x30, 0x2E, - 0x30, 0x2D, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6F, 0x6E, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, -}; #define TX_BUFFER_OFFSET DOMAIN_TAG_LENGTH @@ -68,7 +60,6 @@ void tx_initialize() { void tx_reset() { buffering_reset(); - buffering_append((uint8_t *)TX_DOMAIN_TAG, DOMAIN_TAG_LENGTH); } uint32_t tx_append(unsigned char *buffer, uint32_t length) { @@ -76,21 +67,10 @@ uint32_t tx_append(unsigned char *buffer, uint32_t length) { } uint32_t tx_get_buffer_length() { - if (buffering_get_buffer()->pos >= TX_BUFFER_OFFSET) { - return buffering_get_buffer()->pos - TX_BUFFER_OFFSET; - } - return 0; -} - -uint32_t get_signable_length() { return buffering_get_buffer()->pos; } uint8_t *tx_get_buffer() { - return buffering_get_buffer()->data + TX_BUFFER_OFFSET; -} - -uint8_t *get_signable() { return buffering_get_buffer()->data; } diff --git a/app/src/crypto.c b/app/src/crypto.c index 4e9b4f43..dbe20489 100644 --- a/app/src/crypto.c +++ b/app/src/crypto.c @@ -24,7 +24,6 @@ #if defined(TARGET_NANOS) || defined(TARGET_NANOX) || defined(TARGET_NANOS2) #include "cx.h" - __Z_INLINE digest_type_e get_hash_type(const uint16_t options) { const uint8_t hash_type = (uint8_t) (options & 0xFF); switch(hash_type) { @@ -118,7 +117,8 @@ void sha256(const uint8_t *message, uint16_t messageLen, uint8_t message_digest[ cx_hash_sha256(message, messageLen, message_digest, CX_SHA256_SIZE); } -zxerr_t digest_message(const uint8_t *message, uint16_t messageLen, digest_type_e hash_kind, uint8_t *digest, uint16_t digestMax, uint16_t* digest_size) { +zxerr_t digest_message(const uint8_t *message, uint16_t messageLen, const uint8_t domainTag[DOMAIN_TAG_LENGTH], + digest_type_e hash_kind, uint8_t *digest, uint16_t digestMax, uint16_t* digest_size) { switch(hash_kind) { case HASH_SHA2_256: { zemu_log_stack("sha2_256"); @@ -126,20 +126,23 @@ zxerr_t digest_message(const uint8_t *message, uint16_t messageLen, digest_type_ zemu_log_stack("digest_message: zxerr_buffer_too_small"); return zxerr_buffer_too_small; } - sha256(message, messageLen, digest); + cx_sha256_t sha2; + cx_sha256_init(&sha2); + cx_hash((cx_hash_t*) &sha2, 0, domainTag, DOMAIN_TAG_LENGTH, NULL, 0); + cx_hash((cx_hash_t*) &sha2, CX_LAST, message, messageLen, digest, CX_SHA256_SIZE); *digest_size = CX_SHA256_SIZE; return zxerr_ok; } case HASH_SHA3_256: { - if (digestMax < 32) { + zemu_log_stack("sha3_256"); + if (digestMax < CX_SHA256_SIZE) { return zxerr_buffer_too_small; } - zemu_log_stack("sha3_256"); cx_sha3_t sha3; cx_sha3_init(&sha3, 256); - cx_hash((cx_hash_t*)&sha3, CX_LAST, message, messageLen, digest, 32); - zemu_log_stack("sha3_256 ready"); - *digest_size = 32; + cx_hash((cx_hash_t*) &sha3, 0, domainTag, DOMAIN_TAG_LENGTH, NULL, 0); + cx_hash((cx_hash_t*) &sha3, CX_LAST, message, messageLen, digest, CX_SHA256_SIZE); + *digest_size = CX_SHA256_SIZE; return zxerr_ok; } default: { @@ -149,7 +152,9 @@ zxerr_t digest_message(const uint8_t *message, uint16_t messageLen, digest_type_ } } -zxerr_t crypto_sign(const hd_path_t path, const uint16_t options, const uint8_t *message, uint16_t messageLen, uint8_t *buffer, uint16_t bufferSize, uint16_t *sigSize) { +zxerr_t crypto_sign(const hd_path_t path, const uint16_t options, + const uint8_t *message, uint16_t messageLen, const uint8_t domainTag[DOMAIN_TAG_LENGTH], + uint8_t *buffer, uint16_t bufferSize, uint16_t *sigSize) { zemu_log_stack("crypto_sign"); cx_curve_t curve = get_cx_curve(options); @@ -163,7 +168,7 @@ zxerr_t crypto_sign(const hd_path_t path, const uint16_t options, const uint8_t uint8_t messageDigest[32]; uint16_t messageDigestSize = 0; - CHECK_ZXERR(digest_message(message, messageLen, cx_hash_kind, messageDigest, sizeof(messageDigest), &messageDigestSize)); + CHECK_ZXERR(digest_message(message, messageLen, domainTag, cx_hash_kind, messageDigest, sizeof(messageDigest), &messageDigestSize)); if (messageDigestSize != 32) { zemu_log_stack("crypto_sign: zxerr_out_of_bounds"); diff --git a/app/src/crypto.h b/app/src/crypto.h index c02c2ec7..917d5848 100644 --- a/app/src/crypto.h +++ b/app/src/crypto.h @@ -52,6 +52,7 @@ zxerr_t crypto_sign( const uint16_t options, const uint8_t *message, uint16_t messageLen, + const uint8_t domainTag[DOMAIN_TAG_LENGTH], uint8_t *signature, uint16_t signatureMaxlen, uint16_t *sigSize diff --git a/app/src/message.c b/app/src/message.c new file mode 100644 index 00000000..403a841f --- /dev/null +++ b/app/src/message.c @@ -0,0 +1,90 @@ +/******************************************************************************* +* (c) 2022 Vacuumlabs +* +* 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. +********************************************************************************/ +#include "message.h" +#include "zxformat.h" +#include "app_mode.h" +#include "hdpath.h" +#include "crypto.h" +#include "buffering.h" + +#define MAX_MESSAGE_SHOW_LENGTH 34*5 +#define MAX_MESSAGE_LENGTH 0x7FFF + +struct { + uint8_t hash[CX_SHA256_SIZE]; + uint16_t length; + uint8_t *message; + bool canBeDisplayed; + +} messageData; + +zxerr_t message_parse() { + messageData.message = buffering_get_buffer()->data; + messageData.length = buffering_get_buffer()->pos; + + if (messageData.length > MAX_MESSAGE_LENGTH) { + return zxerr_out_of_bounds; + } + + sha256(messageData.message, messageData.length, messageData.hash); + messageData.canBeDisplayed = false; + + if (messageData.length <= MAX_MESSAGE_SHOW_LENGTH) { + messageData.canBeDisplayed = true; + for(size_t j=0; j127) { + messageData.canBeDisplayed = false; + break; + } + } + } + + return zxerr_ok; +} + +zxerr_t message_getNumItems(uint8_t *num_items){ + *num_items = app_mode_expert() ? 2: 1; + return zxerr_ok; +} + +// retrieves a readable output for each field / page +zxerr_t message_getItem(int8_t displayIdx, + char *outKey, uint16_t outKeyLen, + char *outVal, uint16_t outValLen, + uint8_t pageIdx, uint8_t *pageCount) { + switch(displayIdx) { + case 0: + if (messageData.canBeDisplayed) { + snprintf(outKey, outKeyLen, "Message"); + pageStringExt(outVal, outValLen, (char *)messageData.message, messageData.length, pageIdx, pageCount); + } + else { + snprintf(outKey, outKeyLen, "Message hash"); + pageHexString(outVal, outValLen, messageData.hash, sizeof(messageData.hash), pageIdx, pageCount); + } + return zxerr_ok; + case 1: + if (app_mode_expert()) { + snprintf(outKey, outKeyLen, "Your Path"); + char buffer[100]; + path_options_to_string(buffer, sizeof(buffer), hdPath.data, HDPATH_LEN_DEFAULT, cryptoOptions); + pageString(outVal, outValLen, buffer, pageIdx, pageCount); + return zxerr_ok; + } + return zxerr_no_data; + } + return zxerr_no_data; +} diff --git a/app/src/message.h b/app/src/message.h new file mode 100644 index 00000000..4587d912 --- /dev/null +++ b/app/src/message.h @@ -0,0 +1,29 @@ +/******************************************************************************* +* (c) 2022 Vacuumlabs +* +* 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. +********************************************************************************/ +#pragma once + +#include "zxerror.h" + +zxerr_t message_parse(); + +zxerr_t message_getNumItems(uint8_t *num_items); + +zxerr_t message_getItem(int8_t displayIdx, + char *outKey, uint16_t outKeyLen, + char *outVal, uint16_t outValLen, + uint8_t pageIdx, uint8_t *pageCount); + + diff --git a/deps/ledger-zxlib/dockerized_build.mk b/deps/ledger-zxlib/dockerized_build.mk index 037fdb59..9b078299 100644 --- a/deps/ledger-zxlib/dockerized_build.mk +++ b/deps/ledger-zxlib/dockerized_build.mk @@ -348,6 +348,7 @@ speculos_port_5002_test_internal: $(call run_nodejs_test,5002,40002,test-slot-transaction-interaction.js) $(call run_nodejs_test,5002,40002,test-transaction-expert-mode.js) $(call run_nodejs_test,5002,40002,test-transaction-arbitrary.js) + $(call run_nodejs_test,5002,40002,test-messages.js) $(call run_nodejs_test,5002,40002,test-transactions.js) @echo "# ALL TESTS COMPLETED!" | tee -a $(TESTS_SPECULOS_DIR)/speculos-port-5002.log diff --git a/docs/APDUSPEC.md b/docs/APDUSPEC.md index 586355bf..845ebab5 100644 --- a/docs/APDUSPEC.md +++ b/docs/APDUSPEC.md @@ -164,10 +164,11 @@ Each slot has the following structure The first packet/chunk includes only the derivation path -All other packets/chunks contain data chunks that are described below. There are two workflows as of now (typical sequences here, the app allows other combination of commands, too): +All other packets/chunks contain data chunks that are described below. There are three workflows as of now (typical sequences here, the app allows other combination of commands, too): Merkle tree workflow - Init packet, several add packets, metadata packet, four Merkle tree packets (3x 0x04 and finaly 0x05). -Arbitrary transaction signing - Init packer, several add packets, final packet. +Arbitrary transaction signing - Init packet, several add packets, final packet. +Message signing workflow - Init packet, several add packets, final message packet (P1=0x10). ##### Init Packet P1 = 0x00 @@ -180,7 +181,7 @@ Arbitrary transaction signing - Init packer, several add packets, final packet. | Path[4] | byte (4) | Derivation Path Data | ? | | Options | byte (2) | Crypto options (LE) | ? | -This clears tx data and sets detivation path and crypto options variable +This clears data and sets detivation path and crypto options variable ##### Add Packet P1 = 0x01 @@ -190,11 +191,11 @@ This clears tx data and sets detivation path and crypto options variable Data is defined as: -| Field | Type | Content | Expected | -| ------- | ------- | ---------------- | -------- | -| Message | bytes.. | RLP data to sign | | +| Field | Type | Content | Expected | +| ------- | ------- | ------------------------ | -------- | +| Message | bytes.. | RLP data/message to sign | | -Appends to transaction data +Appends to data (transaction or message) ##### Fimal Packet P1 = 0x02 @@ -208,7 +209,7 @@ Data is defined as: | ------- | ------- | ---------------- | -------- | | Message | bytes.. | RLP data to sign | | -Appends to transaction data and initiates signing without metadata (requires expert mode). +Appends to transaction data and initiates transaction signing without metadata (requires expert mode). ##### Metadata Packet P1 = 0x03 @@ -272,6 +273,20 @@ After three calls there is call with P1=0x05, which works the same as P1=0x04 ca Validates merkle tree node. Validates that previous hash (metadata hash or merkle tree node hash) is in the list of hashes. Computes new hash and increments merkle tree counter. Call with P1 = 0x05 starts the signing process with metadata. This requires that we are at the root of the merkle tree and that the hash value matches the one stored in the app. +##### Final message signing Packet P1 = 0x10 + +| Field | Type | Content | Expected | +| ----- | -------- | ------- | -------- | +| Data | bytes... | Message | | + +Data is defined as: + +| Field | Type | Content | Expected | +| ------- | ------- | ------------------- | -------- | +| Message | bytes.. | Mesage data to sign | | + +Appends to data to message and initiates message signing. + #### Response | Field | Type | Content | Note | diff --git a/js/src/index.js b/js/src/index.js index 323a217f..8bdc89b5 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -142,9 +142,17 @@ export default class FlowApp { } async sign(path, message, cryptoOptions, scriptHash) { + return this._signImplementation(path, message, cryptoOptions, scriptHash) + } + + async signMessage(path, message, cryptoOptions) { + return this._signImplementation(path, message, cryptoOptions, "Sign message") + } + + async _signImplementation(path, message, cryptoOptions, extraInfo) { validateCryptoOptions(cryptoOptions); const getVersionResponse = await this.getVersion(); - const chunks = signGetChunks(path, cryptoOptions, getVersionResponse, message, scriptHash) + const chunks = signGetChunks(path, cryptoOptions, getVersionResponse, message, extraInfo) if (typeof chunks === "string") { return { diff --git a/js/src/signTransaction.js b/js/src/signTransaction.js index 85b7aa8b..1fdd3e8a 100644 --- a/js/src/signTransaction.js +++ b/js/src/signTransaction.js @@ -9,10 +9,11 @@ const PAYLOAD_TYPE = { TX_METADATA: 0x03, MERKLE_TREE: 0x04, MERKLE_TREE_LAST: 0x05, + MESSAGE_LAST: 0x10, } export function signIsLastAPDU(type) { - return (type === PAYLOAD_TYPE.LAST || type === PAYLOAD_TYPE.MERKLE_TREE_LAST) + return (type === PAYLOAD_TYPE.LAST || type === PAYLOAD_TYPE.MERKLE_TREE_LAST || PAYLOAD_TYPE.MESSAGE_LAST) } /* @@ -45,11 +46,21 @@ function signGetChunksv1(path, options, getVersionResponse, message) { return chunks; } -function signGetChunksv2(path, options, getVersionResponse, message, scriptHash) { +//ExtraInfo is either +// - script hash from merkleIndex - initiates transaction signing with metadata +// - "Sign message" - initiates message signing +// - anything else - initiates transaction sining without metadata +function signGetChunksv2(path, options, getVersionResponse, message, extraInfo) { const serializedPath = serializePath(path, getVersionResponse, options); const basicChunks = prepareBasicChunks(serializedPath, message) + if (extraInfo == "Sign message") { + basicChunks[basicChunks.length-1].type = PAYLOAD_TYPE.MESSAGE_LAST + return basicChunks; + } + // We try to find hash in the merkle tree. If it is not there, we send the tx without metadata (arbitrary tx signing in expert mode) + const scriptHash = extraInfo const merkleI = merkleIndex[scriptHash.slice(0, 16)] if (merkleI === undefined) { basicChunks[basicChunks.length-1].type = PAYLOAD_TYPE.LAST diff --git a/tests_speculos/test-messages.js b/tests_speculos/test-messages.js new file mode 100644 index 00000000..16b1df3e --- /dev/null +++ b/tests_speculos/test-messages.js @@ -0,0 +1,111 @@ +'use strict'; + +import { testStart, testEnd, testStep, compareInAPDU, compareOutAPDU, noMoreAPDUs, compareGetVersionAPDUs, getScriptName, getSpeculosDefaultConf, humanTime } from "./speculos-common.js"; +import { getSpyTransport } from "./speculos-transport.js"; +import { getButtonsAndSnapshots } from "./speculos-buttons-and-snapshots.js"; +import { default as OnflowLedgerMod } from "@onflow/ledger"; +import { fileURLToPath, pathToFileURL } from 'url'; +import assert from 'assert/strict'; +import pkg from 'elliptic'; +const {ec: EC} = pkg; +import jsSHA from "jssha"; + +const scriptName = getScriptName(fileURLToPath(import.meta.url)); +testStart(scriptName); + +const speculosConf = getSpeculosDefaultConf(); +const transport = await getSpyTransport(speculosConf); +const FlowApp = OnflowLedgerMod.default; +const app = new FlowApp(transport); +const device = getButtonsAndSnapshots(scriptName, speculosConf); + +const ECDSA_SECP256K1 = { name: "secp256k1", code: FlowApp.Signature.SECP256K1, pathCode: 0x200 }; +const ECDSA_P256 = { name: "p256", code: FlowApp.Signature.P256, pathCode: 0x300}; + +const SHA2_256 = { name: "SHA-256", code: FlowApp.Hash.SHA2_256, pathCode: 0x01}; +const SHA3_256 = { name: "SHA3-256", code: FlowApp.Hash.SHA3_256, pathCode: 0x03}; + +const path = `m/44'/539'/0'/0/0`; +const options = ECDSA_P256.code | SHA3_256.code + +await device.makeStartingScreenshot(); + + +// We get pubkey so we can verify signature +testStep(" - - -", "await app.getAddressAndPubKey() // path=" + path); +const getPubkeyResponse = await app.getAddressAndPubKey(path, options); +assert.equal(getPubkeyResponse.returnCode, 0x9000); +assert.equal(getPubkeyResponse.errorMessage, "No errors"); +const pubkeyHex = getPubkeyResponse.publicKey.toString("hex") + + +{ + const message = Buffer.from("This is a nice message that has only displayable characters and is short enough to be displayed") + + testStep(" - - -", "Message with displayable characters"); + const signPromise = app.signMessage(path, message, options); + await device.review("Review message"); + const signResponse = await signPromise; + console.log(signResponse) + assert.equal(signResponse.returnCode, 0x9000); + assert.equal(signResponse.errorMessage, "No errors"); + + let tag = Buffer.alloc(32); + tag.write("FLOW-V0.0-user"); + const hasher = new jsSHA(SHA3_256.name, "UINT8ARRAY"); + hasher.update(tag); + hasher.update(message); + const digestHex = hasher.getHash("HEX"); + const ec = new EC(ECDSA_P256.name); + assert.ok(ec.verify(digestHex, signResponse.signatureDER.toString("hex"), pubkeyHex, 'hex')); +} + +{ + const message = Buffer.alloc(1000, 0x40); + + testStep(" - - -", "Message too long to display"); + const signPromise = app.signMessage(path, message, options); + await device.review("Review message"); + const signResponse = await signPromise; + console.log(signResponse) + assert.equal(signResponse.returnCode, 0x9000); + assert.equal(signResponse.errorMessage, "No errors"); + + let tag = Buffer.alloc(32); + tag.write("FLOW-V0.0-user"); + const hasher = new jsSHA(SHA3_256.name, "UINT8ARRAY"); + hasher.update(tag); + hasher.update(message); + const digestHex = hasher.getHash("HEX"); + const ec = new EC(ECDSA_P256.name); + assert.ok(ec.verify(digestHex, signResponse.signatureDER.toString("hex"), pubkeyHex, 'hex')); +} + +await device.toggleExpertMode("ON"); + +{ + const message = Buffer.concat([Buffer.from("This is a short message with a non-displayable character"), Buffer.from("ee", "hex")]); + + testStep(" - - -", "A message with non-displayable characte"); + const signPromise = app.signMessage(path, message, options); + await device.review("Review message"); + const signResponse = await signPromise; + console.log(signResponse) + assert.equal(signResponse.returnCode, 0x9000); + assert.equal(signResponse.errorMessage, "No errors"); + + let tag = Buffer.alloc(32); + tag.write("FLOW-V0.0-user"); + const hasher = new jsSHA(SHA3_256.name, "UINT8ARRAY"); + hasher.update(tag); + hasher.update(message); + const digestHex = hasher.getHash("HEX"); + const ec = new EC(ECDSA_P256.name); + assert.ok(ec.verify(digestHex, signResponse.signatureDER.toString("hex"), pubkeyHex, 'hex')); +} + +await device.toggleExpertMode("OFF"); + +await transport.close() +testEnd(scriptName); +process.stdin.pause() diff --git a/tests_speculos/test-messages/nanos.01.png b/tests_speculos/test-messages/nanos.01.png new file mode 100644 index 0000000000000000000000000000000000000000..7f6ab5e6010052a6c826d8a33994eba6cf7bbeed GIT binary patch literal 380 zcmV-?0fYXDP)uRr;yTsQ43OPlRlX!mA#zXLNU2R?VwJ6)EF1I*s6nq^p&4PTTOSmPEMbE{jtppwY z^J4|+t@vhuNa?L)$#$MM!@0Ht2hgw^YzuAo0F>?KHP7%21er<@_~X;u1W_f`a9 z&)8tifC~e#OpaQ*=)Jj^nQ=S=L>0_r)pnQRorNTQ`GlD2lQ9F#qKh$G{-E9;9g9Bp zfE@Dtsj?-U1BU#LN1Ji&^64|f)*teIloz|6`X;Q;vRtl7O23!MRe)b>R26NZxKMI1Ti_fPP_|S)BUgn(pc|>G-)u}q>$H$;KLOdT!H=n0s?Ua_0CyfJ zId*%I;Qze-1Zsk#j6+}*63;*4C4g1MZ9&a|54uxkAJ0akh#KL(*DFDedo33Zy-sq_ zi<(8xp-$Ktx?^C}!DZoAI0NVW%Z(^yQ`F_UeG9A)FHeNA4?rUsK4%~y$dLSnp60r` zVB)d4q8k2iPZcO3ip{F4M`7NgE8)?P=u`y1f=~jp#H0r!KpxIEh4xWqv z(WwYN(;y+ZIpQp!>BkrX-IP@BRF}7aauyaA78YzV;)mz<;n7hL1I572J=Q3;A#S^O z49uk%dOt(apla~CF+mr@9s{>`3{<%(WRRC)=VkU__#6=Rj$wq6;xQ|9^0c-oo>o61XCv*^GBpihzoWx0Z0kzJ=u-ys&Q}gp)#A(hbpcw0?Zf zWN57rLQ+~9Y7E_}!;Wam##fql6L8teEc$G!k z;d@RbJN(W7aED4IQj;RDvt~F@GB%KSSTcPVGW$&ACkO!iQ z8vs27yhSbjP;`J=@&04wvG;&J|8PiF{H(jKfT~?c`YR&-X`*N6^$t+G8gHL>2v1Br Z2!EWZyV}X2qm2Ln002ovPDHLkV1kO~3eo@o literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanos.04.png b/tests_speculos/test-messages/nanos.04.png new file mode 100644 index 0000000000000000000000000000000000000000..f9c1e1e8c0bf14c28045600dc4bb581e9df5eaa0 GIT binary patch literal 542 zcmV+(0^$9MP)x`xmLI!_AZhK2Y0u*VQ#DZ4Z35XhiUo2AJ>WtD{niz^{cI7h|g|2)vHPQ98C-q za(sZIS+HEb%Q(HJDTk~`KN_K+eMvZfSvuu$kjtyHMlJX%70?1FqwO>z$S z@1q0EN3L$ispddMK03gh>l`>uJ3x{Nps^{;AJo+Fx+3g}IF8>~K zzhRaAS`H?-=QM1i=Tu6{1-oD!M<5AJP1 literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanos.06.png b/tests_speculos/test-messages/nanos.06.png new file mode 100644 index 0000000000000000000000000000000000000000..7f6ab5e6010052a6c826d8a33994eba6cf7bbeed GIT binary patch literal 380 zcmV-?0fYXDP)uRr;yTsQ43OPlRlX!mA#zXLNU2R?VwJ6)EF1I*s6nq^p&4PTTOSmPEMbE{jtppwY z^J4|+t@vhuNa?L)$#$MM!@0Ht2hgw^YzuAo0F>?KHP7%21er<@_~X;u1W_f`a9 z&)8tifC~e#OpaQ*=)Jj^nQ=S=L>0_r)pnQRorNTQ`GlD2lQ9F#qKh$G{-E9;9g9Bp zfE@Dtsj?-U1BU#LN1Ji&^64|f)*teIloz|6`X;Q;-(XN&`1qW;=fUzejC5Pixj+Y@>hMZkK>U^B54}7{) z()8>x8o+;;faY30Z_bT-w_n$o1THg=XP^ls+9+a<>y&+@LTP;z9?x>Tx^*^-0yg0V zY)ZLbRY9wj>kN(3Bv78)RKDI>*kCA3Of&2dWxQq=Bjhw1f096piSCHt{`w; zG|hq{kJKD}cUrg`8&aY=@11`JyTWlz7;MVU!{3x?G=Yw{UuK1%`XN1saG@df+BD8H zQ)++;(}3~Esd>hd8V~1*z0!wiSQ~==7Cu)qrMAVRu<0=O zhwF#l&d9ZH+M5e5=aO7SW0rE;;EIC|5Ytm5I9UP1Gu_r+ z{|;!h9fErF+tLhiB_UqH=^@dtV;U7vg9pSYA(1xJ14t2$!2C3{lQ!uwlpdHPt3Gf^&kef5hbbj8}Y@L~F)_0-9ggqpXqoy3Q8yg!Ns}Z3h zJmom4;}d6qr^u?Ukh6i43MN0HS*#_C=m&l`$17+`LY#z?gb10-Q5O;PrJ<%tNLQG7 z5w{iE7K;&gAjJjA9S6(t^P6I`F=4Rsm=1k3KB|w;T4pSBN?wkcAqYBPAu<7;7w6#> zv{*rSHz@H$-g@Tb4*=Q)1R>8mu!IY0V6x}@FkFLGrS%tllKci1;d6+mp<19IzdP`M r2s}#2D090c(oo`dG@p;$Gl5!jR>|&`LPUb0p zOAW16w^3IZzpw$A*xC=L-Th154oaTHX(p>BA5}_A3Yfz>CC}X0F}6dz?T!pOzdrD& z{=gO)4LJ3{D)50!=P6&nm3dD|-IHOc{*@-m#|Z_LV;%{!a=6mt(yTN0g;$n-kfbUd zr+Ia#0QXcjGx1&N?0UqwGQYk*^LG%6$^^E!9U-N;BTSs#W4XUymS+WA2FV=43Fctt zkvdJZ^0Xt&QdX@$q~<$M`+kgk;6Xlw;bpV@EdH^xS-1@w#f2Ls&?RLerbt`3P|EA9 z>4qw6I!s0ly26$lZn&C`R(B?sNL$rv$K6zwHN9e@Hv2UTyUo%KX7kl#X=QOvx4yb; z%9iQkO52gkm16_I#j&Ee`~ahyWHq0>8(6ly=9>3QNojdotv0}Ifcer(qPH;_gsVEY zWK~WwYDTb)2V6s$j`yq&@?0@7A{&p$GhiZ^dJ-WU4{iha10cbwcOHZ4JrbDX74ots zCmCbkt5{fASa<-qQ3M0rf0Ofr)UKqwe%F{pTI6RH&jM~T@%*4~)X~zhON62XddnwM@J+Pum5=yh!gWRVD9`t9a>CukMUtm6c!f3 Z@dtW5MU$fSh7$k)002ovPDHLkV1iyzDHs3% literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanos.09.png b/tests_speculos/test-messages/nanos.09.png new file mode 100644 index 0000000000000000000000000000000000000000..006c26abaac6c76b2e871b194165596c0b416694 GIT binary patch literal 249 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!2~2j9iA5fsRN!ajv*Cu-d?-N*JQxc`taO| z-}VOL1%{U&gdOO)vH0QtN3CIpH=On}yd9NxI9#@L#=GYozkR+NZ@s(h_s<*5szDx| zmTO$v85!1fT#rxFzI@yL`PcK^*L-*W3fIF8>~K zzhRaAS`H?-=QM1i=Tu6{1-oD!M<5AJP1 literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanos.10.png b/tests_speculos/test-messages/nanos.10.png new file mode 100644 index 0000000000000000000000000000000000000000..7f6ab5e6010052a6c826d8a33994eba6cf7bbeed GIT binary patch literal 380 zcmV-?0fYXDP)uRr;yTsQ43OPlRlX!mA#zXLNU2R?VwJ6)EF1I*s6nq^p&4PTTOSmPEMbE{jtppwY z^J4|+t@vhuNa?L)$#$MM!@0Ht2hgw^YzuAo0F>?KHP7%21er<@_~X;u1W_f`a9 z&)8tifC~e#OpaQ*=)Jj^nQ=S=L>0_r)pnQRorNTQ`GlD2lQ9F#qKh$G{-E9;9g9Bp zfE@Dtsj?-U1BU#LN1Ji&^64|f)*teIloz|6`X;Q;N2PZibzUHk|asKmB*xc&I8T)nlx!ARp-Il z{tO+RGHx+bj5UI}4glalJYMoQv=Dh{JP$#hy8(-lxws}=JyB<@L+SPUv!`=2=b7d_c{t+~xhV|KW2$tHVdc z;vM4q03GIrAdGnY31Ue36k-?@r!x*{cPv_T2iV77Jn6Ns;i}SZ5%De+_k>4>5WaW7 zv=DULY%)yuC48B^B_>h`^4sy7wrU(R-m((_FBiXYKe+M#rALW9-);MT2WWDh{P&L4 zo|z~+`$;7L{?Lc^B!CiE+Or^@x^X`lPU0P#JZjeoV%?Xrs2Eq-$q=iEIv`*>Eg9g) zNNeFk`ER3LQ{?uSuvh_h_PTT%E(X+Z<6UY`ggfpRd4SLfXx&YIc~60e$crz*%QEUV zEWHCTIO!r0CSbw)yw9f;G)@u1A53~AKr&ym5+>|0gn zvW5LiL&|L@q&w=vhL<*jGs|g>G7NKF3d;VdJ5jk$xaSe?lM~j|DosAV`Wu@8S`9uU z7q5`tC&=KNf=XccGhj+N3z!Dsa!P;>=c0?w06qL+r021UsZzT};8_ZN!XiT#wtIkE z3c77I+0F1Fd|15&Dkufn_4rG!+K-xVMFh~z#je~F9sesW%JliE+s`wg*3VM^-to1^ zlZt0Qc@IKf^r=1QppsYGs~}mrNjsfxB>j1yZ}=KqcM%IxO8sSMhc_L*Xv%nkE?-+6 z4qU!6ZjS)?0&E-wUo@FEb06h8YM+1M%tLfVK&xBTUK6_~XJ&)*FYo)A#lDDHUg(VT z-^2hehw$l1KxQq6Md#r4Q9h})tm62Gjml@sT4N9dK@bE%a5eu4`0Nl|44q&A0000< KMNUMnLSTY>a@(5# literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanos.13.png b/tests_speculos/test-messages/nanos.13.png new file mode 100644 index 0000000000000000000000000000000000000000..7f6ab5e6010052a6c826d8a33994eba6cf7bbeed GIT binary patch literal 380 zcmV-?0fYXDP)uRr;yTsQ43OPlRlX!mA#zXLNU2R?VwJ6)EF1I*s6nq^p&4PTTOSmPEMbE{jtppwY z^J4|+t@vhuNa?L)$#$MM!@0Ht2hgw^YzuAo0F>?KHP7%21er<@_~X;u1W_f`a9 z&)8tifC~e#OpaQ*=)Jj^nQ=S=L>0_r)pnQRorNTQ`GlD2lQ9F#qKh$G{-E9;9g9Bp zfE@Dtsj?-U1BU#LN1Ji&^64|f)*teIloz|6`X;Q;NklfC7o(dmV!Vp5NwEYI9_zToStRK*|kTV%eiF4)tx<^ zUX%Dyy*Ly(Q(;j=WN zNs2sNb9C>ta5p!kM0MVUza~$`#&uv~lM7GZDc5KM1G`UEA*fv>@(7a|QZJ_g-%PGS zRG5Yse?ZL@S86`M7klL#%CI&B{Vlw)vf20}aO+`SrW+Li!VTmEw3JjEeodRNg%2?b zlv-#dlD%4FFQUT}`tfVwez;0ZM6w|Ft&b)rZAR2({YYNn${5?)=2YSYa+sB9S*mk1 zgDC83Fd{(cQoBZ7zY$k$gdo@(QXUXBlPEs%IdT;m~H@~)M!1;%0M?+&${_QuA>#%JRZ z9?=HAHppr{-4HTUNs>aUriWO}jfPmF;I{D6DXfxcsN-EAYO>A zDcS36O-WQl*O~rbn`PMUbc=Z8SA=XhJkAoKN%)j(D^uL%7sZ8G2w+8hn1AIb2#HP@ zu`&1u1W`p+VER cv9XcI50{gGV8O*LT>t<807*qoM6N<$g4u{9c>n+a literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanos.15.png b/tests_speculos/test-messages/nanos.15.png new file mode 100644 index 0000000000000000000000000000000000000000..5c1baf5eacb379216ec8018e3409771a61da1cca GIT binary patch literal 641 zcmV-{0)G98P)afh3$Jcj^?Wn$XM+Bwa2ZrJU z*2qY}p$Faq8^~~W*#TGPJp^%0h9TOQtH_5A36x_VakDVEkYkc8Gxxcdr+yHmA{~c$ zai{?EM7Cz&o5I;N#JDoQzK?MqOhusrYut`0sky^V9NlBFzh7Q^Ih;Gm9Kr#{VC1P~ zHlf97hnx9aG5?T~?>z1MF|xrY*)R>SHp}1Qhn3C3^HASO_^VDcj+%9Kx)d?jb8(_c z9cdD_#1qU-E>ZBM5D2%{T#N*hmcECkc@TS><;<#Fe2B;E0Tb^LgI9u|Cuq?TFYjbc z#jqLdWCW4>M#}(9ZnUav^62It?USM>|0)!8FxmJ3%YTJTSiy%P}u- zG=XAw#MH(i`zyWhTL9*ADW)T)%T!EBUxevR0b*S_hEN8(mb;QAWAIx5@DCrR5H%J3 zj@^qsh<{${!o4YofhiIJwC>2A5(qqR0lYBY9l52y;@1n}>wC<`q*w9`z;m0#8QK>X b7B2Vz@=T?*ivaf?00000NkvXXu0mjf$}A=O literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanos.16.png b/tests_speculos/test-messages/nanos.16.png new file mode 100644 index 0000000000000000000000000000000000000000..d4fe3ec9f027d4681d2aadaff529572401be1ce0 GIT binary patch literal 546 zcmV+-0^R+IP)$Owo{*66F0#=TJ3d((e1Uea7540jb54 zy(!IEwF~VCHkE3}v2S$Uq|4L6n~LIGaRxe8jH&-_MWjf_Xd!7!>8} z?y~O;fDr&anG6f=dEoM6@4m(aK@h0m7H&BW$K?dFx}e-Z^y+A?-UH5cu2VE+2|?NF zpw}e}9&MFgxcvl{TcrdVJ6BCV*0xF)`ra2TWUAflReNv}>j4-5jtyBtC|g;%z@Qmp zS;wOmU^)P>)|gQ763fT9c%7#c(*dZquQ;xgPn7*jG-)V#iZi9Aglw)?M`Ixfg5Ynl z%o-r`#Y!@uCftbCNSz|b&wM$@ZJv^*+%gNHmI5WRn(`H{MpE6#?HFVUS)`H~eOw<< ziXd@$;d}=y_5pZ?+D=SiT^hD8_&i{0#uFQw2{m_1b9KT(@}SF9(`UI*%2^x2XeT`O zLVvPshfQ_)p!As=RHKkT>lvWzn)x!3`QCMTem|AOi0K(1FBjR-+%L+7n@cfaWoa@7 k$D)D!>y&>Q8U(lI11vtpEfG`QQ2+n{07*qoM6N<$g4n?Q4*&oF literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanos.17.png b/tests_speculos/test-messages/nanos.17.png new file mode 100644 index 0000000000000000000000000000000000000000..006c26abaac6c76b2e871b194165596c0b416694 GIT binary patch literal 249 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!2~2j9iA5fsRN!ajv*Cu-d?-N*JQxc`taO| z-}VOL1%{U&gdOO)vH0QtN3CIpH=On}yd9NxI9#@L#=GYozkR+NZ@s(h_s<*5szDx| zmTO$v85!1fT#rxFzI@yL`PcK^*L-*W3fIF8>~K zzhRaAS`H?-=QM1i=Tu6{1-oD!M<5AJP1 literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanos.18.png b/tests_speculos/test-messages/nanos.18.png new file mode 100644 index 0000000000000000000000000000000000000000..7f6ab5e6010052a6c826d8a33994eba6cf7bbeed GIT binary patch literal 380 zcmV-?0fYXDP)uRr;yTsQ43OPlRlX!mA#zXLNU2R?VwJ6)EF1I*s6nq^p&4PTTOSmPEMbE{jtppwY z^J4|+t@vhuNa?L)$#$MM!@0Ht2hgw^YzuAo0F>?KHP7%21er<@_~X;u1W_f`a9 z&)8tifC~e#OpaQ*=)Jj^nQ=S=L>0_r)pnQRorNTQ`GlD2lQ9F#qKh$G{-E9;9g9Bp zfE@Dtsj?-U1BU#LN1Ji&^64|f)*teIloz|6`X;Q;G)@u1A53~AKr&ym5+>|0gn zvW5LiL&|L@q&w=vhL<*jGs|g>G7NKF3d;VdJ5jk$xaSe?lM~j|DosAV`Wu@8S`9uU z7q5`tC&=KNf=XccGhj+N3z!Dsa!P;>=c0?w06qL+r021UsZzT};8_ZN!XiT#wtIkE z3c77I+0F1Fd|15&Dkufn_4rG!+K-xVMFh~z#je~F9sesW%JliE+s`wg*3VM^-to1^ zlZt0Qc@IKf^r=1QppsYGs~}mrNjsfxB>j1yZ}=KqcM%IxO8sSMhc_L*Xv%nkE?-+6 z4qU!6ZjS)?0&E-wUo@FEb06h8YM+1M%tLfVK&xBTUK6_~XJ&)*FYo)A#lDDHUg(VT z-^2hehw$l1KxQq6Md#r4Q9h})tm62Gjml@sT4N9dK@bE%a5eu4`0Nl|44q&A0000< KMNUMnLSTY>a@(5# literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanos.20.png b/tests_speculos/test-messages/nanos.20.png new file mode 100644 index 0000000000000000000000000000000000000000..85d9725b420f3973268d5c660e9e7c5f486f4adf GIT binary patch literal 483 zcmV<90UZ8`P)N2PZibzUHk|asKmB*xc&I8T)nlx!ARp-Il z{tO+RGHx+bj5UI}4glalJYMoQv=Dh{JP$#hy8(-lxws}=JyB<@L+SPUv!`=2=b7d_c{t+~xhV|KW2$tHVdc z;vM4q03GIrAdGnY31Ue36k-?@r!x*{cPv_T2iV77Jn6Ns;i}SZ5%De+_k>4>5WaW7 zv=DULY%)yuC48B^B_>h`^4sy7wrU(R-m((_FBiXYKe+M#rALW9-);MT2WWDh{P&L4 zo|z~+`$;7L{?Lc^B!CiE+Or^@x^X`lPU0P#JZjeoV%?Xrs2Eq-$q=iEIv`*>Eg9g) zNNeFk`ER3LQ{?uSuvh_h_PTT%E(X+Z<6UY`ggfpRd4SLfXx&YIc~60e$crz*%QEUV zEWHCTIO!r0CSbw)yw9f;uRr;yTsQ43OPlRlX!mA#zXLNU2R?VwJ6)EF1I*s6nq^p&4PTTOSmPEMbE{jtppwY z^J4|+t@vhuNa?L)$#$MM!@0Ht2hgw^YzuAo0F>?KHP7%21er<@_~X;u1W_f`a9 z&)8tifC~e#OpaQ*=)Jj^nQ=S=L>0_r)pnQRorNTQ`GlD2lQ9F#qKh$G{-E9;9g9Bp zfE@Dtsj?-U1BU#LN1Ji&^64|f)*teIloz|6`X;Q;`q0{{R30002+7GHNy-~z3+ zxOVp&XYz#ayC?9f9DPMUcTeCNatlAGwxGj>ub2kO7Np*GGT|1~*LzQf;@a;dD_|$W z6|hGmlB|HC;pp}s0002s9p=4D>BG2Ty6&!i@L*=GwLE`;ov?JcXMGvPA0@dH4((5V zxxTI-O4%e5a2CGYj^^vb+lmmY0C%6mH>V_zM8|-(kd#;+t=%7JaK(0f%eWPL}q1q`rz3aP}SG$xsuId|*u#6EJu@eCs<4_8%*o zfT0NDWjr0rBa1hpifWIAL(vMrR?04a0000006xwyJ@(1R7Nm>p00000NkvXXu0mjf DP-wNr literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanox.02.png b/tests_speculos/test-messages/nanox.02.png new file mode 100644 index 0000000000000000000000000000000000000000..2689ddb23270a26a35f8ff07481a8bca2379a633 GIT binary patch literal 420 zcmV;V0bBlwP)nlAi|(~vR+WluX=;uO@@dz*}mYrjdk1MVd8 z4!B1mO1T3}!pWO|0001hZy4_`fr@cvaQCP*j6_8LoNyPhJH>jc)~c6UvQotO@@x7! z?0BP#HV17C;da1APe&#@nU=1%uljn?St#5NScRsyn+G_K1kbz#nca1-z}Ip_!U z5p6QeH0bUJjER0pl!DI2PUj!@xOd}7!u<_3Lo|*1LwMR4@DK(706=;o65)P}tfE}R z+8zCAX<;m`?Qd%17e;%{5U3QPV>cErb>LRv+xHqD`{0i`ELJhj#OqXY-}eet5Y=K$ zfN^b+23gt%1xi8PDuVm;z3)|`)<+{wKvA(i^)K^UF8}}l00000|H2<88sc?;>f&_( O0000N<|&{NwR> zSS7b}97k!)X`xl3zESME{08dUpX^Ze-%RO!(G#FnAAf!ju`Hl8pvKuNjL+ns8PZy`mNd z65j!3@KFO%J52t)vCReZH!1Lv#t~Ay8|)-EqEa9}qHfV~2m_PIxia_b4HR zkN}QXa-k~OR26PptipY9W)MjqJsUBQCASU$JOcS|&!cibW$o;+p62O=^Ix^3Y0L${ z45;H$bHCaVtS+?(8glAsYPQt+Rh7i2BCNeW76^=N-Kx|+6A4;y8qsG=a0ldhXY~OP zh)T70Pj)q>noX@OZyq^#jG2;(u#Ut00{b9L#g=y*^2iy`3?P{1ngay-xgw8| zr#2NV+Vv}xsac@}S@kZXazp$d>*gbRe}qfBCFLo-KTe%Bz`~9qG6zPXJ83wXeK3{# z-8*8zGV52mX~mgv$NStrQdTkjOc-n<)g6r`Q;S)Jp1mHZo}Sw!kO+D+)HGz$hB;0j zj?}fPUrmvQv-Dz?Mfk$-Mx%9?dqI77mDRc;BeXfYbU-*EeSmK>hOj6E)roRd;8GQ< zuC)kvCrZhk%noHA8-mE&NNQbU zv67cKfr@{v_nE`OnSJyK`}J6lZb^R%w#bH-_!BRkmk%aX8DXyua9tnMzepZEX(002ovPDHLkV1n9JZrA_- literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanox.04.png b/tests_speculos/test-messages/nanox.04.png new file mode 100644 index 0000000000000000000000000000000000000000..7e49f56fd264dd1ff1b92fa5440aaa83712b341b GIT binary patch literal 780 zcmV+n1M~ceP)YgJ~7 z^p~-C6jNuwGoV;cRbN}CTz@3u_cD)OMLC?`AWa${LuLTW(_&?2l-|*o2uBr4k!n^c zAt9Xj4k#BNb|Io8y>#3?c&RI;o?yLWbe5%`0+)N1QA<$M@6}(*`lC~=RGrOaO0h=9 z1%vo)C_4k#$mt8hdLmn#wva6@ErV>m`)sV&kaMmAtZ_Ygzodj1dHAk#K-91e9R86@ z&}5_~2mk=UVn&}|rXd~uqBv<6v`MGNM*C6s0Zsjqs_jO}lPxQmVh>_$sU#o4s1fO^ zU?r8OboJaK%o%O6nVWM(FZHJJz06ZXQ>(?j-_x{42WI#p_WpKkY6qm2yxb)|H!oXQ z?*yv(N-cIy3c|few{lz6QqKT)e=4D`vacAC6v{-DNWD7CUkXN_I)ZS+y_@XwJ{!@= z@cuLySHt_0K_9FHtI$dsPDURLWr%yv>a(cUM%h_*5Zklc=C{hDhANMxSV7SuZH5@9 zzm=1nQZ=nzKhWt7i>FJZY$md%9CnOasZr_l<#Y(8PBHGi3aD5fVRjn5?e}y*hzh71 z59E=LV$DkQR^$)DbF{|y*q@m$!7~FaamFH!(y?ijRHbZsT^#cF^*a+W)r@+0 z-MLD@)a6M_b+?66MD;Cjcxr#=9h|TH_8|Oa)c|0000< KMNUMnLSTZKo@qY- literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanox.05.png b/tests_speculos/test-messages/nanox.05.png new file mode 100644 index 0000000000000000000000000000000000000000..1e4be69934787c368c2b5ed677cc7f5a2470be3b GIT binary patch literal 355 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|`hpba4!+nDh2#G+&c}K^PbX0`eJS}y70`%8T>n^%dV>bk>TM}C(cF@9*Jbj^5WTF;gKGIO)y zjlJ*9*PE9W@jdm~5r5wEu#TD8y6+Pnh8_O<-8#^Bx$bnOy3REWn^jXeAFev)TXe-F zTky7j?62N_lL@yzwx7O!=&4O1W7^|)7fcr%%i5*3D%0_G;tl)cpW3Io3*7!cdXW1o sV&B=e%knGs=l(tai5cc!1_ryAe9w9}zGZ%X&;TUp>FVdQ&MBb@086r+N&o-= literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanox.06.png b/tests_speculos/test-messages/nanox.06.png new file mode 100644 index 0000000000000000000000000000000000000000..dbbdb276b0c5dc45bbcf2bcd75d07d53f0a031f5 GIT binary patch literal 409 zcmV;K0cQS*P)`q0{{R30002+7GHNy-~z3+ zxOVp&XYz#ayC?9f9DPMUcTeCNatlAGwxGj>ub2kO7Np*GGT|1~*LzQf;@a;dD_|$W z6|hGmlB|HC;pp}s0002s9p=4D>BG2Ty6&!i@L*=GwLE`;ov?JcXMGvPA0@dH4((5V zxxTI-O4%e5a2CGYj^^vb+lmmY0C%6mH>V_zM8|-(kd#;+t=%7JaK(0f%eWPL}q1q`rz3aP}SG$xsuId|*u#6EJu@eCs<4_8%*o zfT0NDWjr0rBa1hpifWIAL(vMrR?04a0000006xwyJ@(1R7Nm>p00000NkvXXu0mjf DP-wNr literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanox.07.png b/tests_speculos/test-messages/nanox.07.png new file mode 100644 index 0000000000000000000000000000000000000000..2689ddb23270a26a35f8ff07481a8bca2379a633 GIT binary patch literal 420 zcmV;V0bBlwP)nlAi|(~vR+WluX=;uO@@dz*}mYrjdk1MVd8 z4!B1mO1T3}!pWO|0001hZy4_`fr@cvaQCP*j6_8LoNyPhJH>jc)~c6UvQotO@@x7! z?0BP#HV17C;da1APe&#@nU=1%uljn?St#5NScRsyn+G_K1kbz#nca1-z}Ip_!U z5p6QeH0bUJjER0pl!DI2PUj!@xOd}7!u<_3Lo|*1LwMR4@DK(706=;o65)P}tfE}R z+8zCAX<;m`?Qd%17e;%{5U3QPV>cErb>LRv+xHqD`{0i`ELJhj#OqXY-}eet5Y=K$ zfN^b+23gt%1xi8PDuVm;z3)|`)<+{wKvA(i^)K^UF8}}l00000|H2<88sc?;>f&_( O00009k)x0T6td94(P;D&k! zKw4#gWgJpMLhs7W9Wk51&3+pM&@EBQjnLR zAw2(ZhD@a^OhvvWnpj9!Nmu{_shI8(V&#uz7`dR4O2eohxi}{hkMfz11=dTfONg}^gvd?>A1z5 zU;^M>EanOx?Lfob_XYD(*tHd`>j@Ql!NUVH(Y$I!5cpu}!?f7Z3Mu1uuKBK$aJabk zbu`^@E>z3OeR9^>atG{Nl5cfHNM%UNH~~Wl<-yYGq;_EZdD}k~!o6nyD-;YBhS*lA z$E-IjOvAhG<1fJ-5&^PQ}%+76q)y zUChuB^?K|}JQH*X1%t1$Xk>apQYNVAdZxOY^GYYGLDFuhS3y~WdS9vA;$iL;kak0@ z2s0 zaXmd%14~{IK|HXu^3)d}skYFp6(d8nx2S4J6ELAZJUWW+SZpZeGn>AqHET)mzGT|< zOl2Nv0u&!{U~W0XPA6pWn$zaxitFjI(Tw}ND&Tcmez4D#kd?Ok$g0}-b+m$#PjUfD2CFs% zmA6VFIc+@}8a8A$18UgSop(01lKevwes9P4OUNk^<#MpIFLUQH9GefK8K6@2I+-EQ z+R&!>62n;$tIn$JQp$NHLv&8j3;z+FX(rHtdh?^wUo>J1;U(?IcFDvc6 z$7mIu*PIAFV$UKa+yhMRI<~%^!A*EG*7qm?0IXp7JK$88Cery)JCt&+-|bQFwCb$) zeh>K{UVy4*c^l90l0=d{^@stZUZSB!oCyLL+}(Y8i0;qEgC+3LHYpPyMfv@ zbjrOPGl|+v^=*i^TIX%^mUxAf{PUPj#{1h)T#ffPLkymTD)b}+CliCE;9dX#00000 p000000000000000004kxd;@G1)Az}O6S)8Y002ovPDHLkV1g}R_CEjs literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanox.10.png b/tests_speculos/test-messages/nanox.10.png new file mode 100644 index 0000000000000000000000000000000000000000..1e4be69934787c368c2b5ed677cc7f5a2470be3b GIT binary patch literal 355 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|`hpba4!+nDh2#G+&c}K^PbX0`eJS}y70`%8T>n^%dV>bk>TM}C(cF@9*Jbj^5WTF;gKGIO)y zjlJ*9*PE9W@jdm~5r5wEu#TD8y6+Pnh8_O<-8#^Bx$bnOy3REWn^jXeAFev)TXe-F zTky7j?62N_lL@yzwx7O!=&4O1W7^|)7fcr%%i5*3D%0_G;tl)cpW3Io3*7!cdXW1o sV&B=e%knGs=l(tai5cc!1_ryAe9w9}zGZ%X&;TUp>FVdQ&MBb@086r+N&o-= literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanox.11.png b/tests_speculos/test-messages/nanox.11.png new file mode 100644 index 0000000000000000000000000000000000000000..dbbdb276b0c5dc45bbcf2bcd75d07d53f0a031f5 GIT binary patch literal 409 zcmV;K0cQS*P)`q0{{R30002+7GHNy-~z3+ zxOVp&XYz#ayC?9f9DPMUcTeCNatlAGwxGj>ub2kO7Np*GGT|1~*LzQf;@a;dD_|$W z6|hGmlB|HC;pp}s0002s9p=4D>BG2Ty6&!i@L*=GwLE`;ov?JcXMGvPA0@dH4((5V zxxTI-O4%e5a2CGYj^^vb+lmmY0C%6mH>V_zM8|-(kd#;+t=%7JaK(0f%eWPL}q1q`rz3aP}SG$xsuId|*u#6EJu@eCs<4_8%*o zfT0NDWjr0rBa1hpifWIAL(vMrR?04a0000006xwyJ@(1R7Nm>p00000NkvXXu0mjf DP-wNr literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanox.12.png b/tests_speculos/test-messages/nanox.12.png new file mode 100644 index 0000000000000000000000000000000000000000..e10e0049c4e39446eb67baea703f77a107925640 GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|?+Yba4!+nDh4L#dXaJJPnEG zite?1uUEO3Dt_9-IXm@SlJWodS%n-^xma{qfx2Lzg@f$rvmoH=d6AC$%=Q=jSJKIT-wu*=_G3~M%K@Xo_i(g zHdj=f}0nbj2ce(`^ylg~}&|D3(uKYYj4wN_8> zZ+{nI$SOM_u`XWN`GJVa%)|8wewyiq%N;jcrZ>2qaZP6O%USIHhQGde$#(P2X_0Am za>cr)_d91w^xa&kTe8(~cJ!Sjw%PrA_UXTy*^pHBRaLg|Hp>(n%N@&IHecX<8rA+@ zt@?Z3Tc`BAn|mvEM67U?-sqotgLD1FM;eFjtmKVW*_-_7yhZ8LXPMe>Vn4o%Nf9f^ z`+hoJ<0!|6lg&?=W2Y^gE_+jW0snV<+r|F-=BUKBu>UH)aehY~%a6I%n_H~9H~cgB gd3YxaEEIwCC1L+UJAKP%sYgMAp00i_>zopr07kXNa{vGU literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanox.13.png b/tests_speculos/test-messages/nanox.13.png new file mode 100644 index 0000000000000000000000000000000000000000..7e236da60aac4c243bde35b1f1e6e89b08667b7e GIT binary patch literal 428 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|=lvba4!+nDh2#VBTQ`0oTB& zQuR0Wo^O`Q2WmDw=j^nt5PA1=lMD~DM8GjkMxZtr5RaFeIxXl`>A9PWJ5Spt#mT?z zkH1&P;r-Yk#X0pV}Dj{o?hW-Zy=?izjJaqX7)Mcd|YI-h=T-^N3W6J?kG zsd?Qd8ht@p>COA>q=Gw5QAIz?pG+$f-N}3ESr6;0jpAR03lgPGld6B7o*MV>#H`y! zpFf`19I{W?*l^Et&)jv(dvo#%y9H&+D*r~jv1Ih*`>M;l@h!^|i{cH-9d=)6OPYIl z_s=a4YiAv+{Ce}N*k8fJ6E#+L=v`OauW`ftseOd9Vpo0gztetaZXQk3OWC@eV>^@I zG|T@dk^`q0{{R30002+7GHNy-~z3+ zxOVp&XYz#ayC?9f9DPMUcTeCNatlAGwxGj>ub2kO7Np*GGT|1~*LzQf;@a;dD_|$W z6|hGmlB|HC;pp}s0002s9p=4D>BG2Ty6&!i@L*=GwLE`;ov?JcXMGvPA0@dH4((5V zxxTI-O4%e5a2CGYj^^vb+lmmY0C%6mH>V_zM8|-(kd#;+t=%7JaK(0f%eWPL}q1q`rz3aP}SG$xsuId|*u#6EJu@eCs<4_8%*o zfT0NDWjr0rBa1hpifWIAL(vMrR?04a0000006xwyJ@(1R7Nm>p00000NkvXXu0mjf DP-wNr literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanox.15.png b/tests_speculos/test-messages/nanox.15.png new file mode 100644 index 0000000000000000000000000000000000000000..2689ddb23270a26a35f8ff07481a8bca2379a633 GIT binary patch literal 420 zcmV;V0bBlwP)nlAi|(~vR+WluX=;uO@@dz*}mYrjdk1MVd8 z4!B1mO1T3}!pWO|0001hZy4_`fr@cvaQCP*j6_8LoNyPhJH>jc)~c6UvQotO@@x7! z?0BP#HV17C;da1APe&#@nU=1%uljn?St#5NScRsyn+G_K1kbz#nca1-z}Ip_!U z5p6QeH0bUJjER0pl!DI2PUj!@xOd}7!u<_3Lo|*1LwMR4@DK(706=;o65)P}tfE}R z+8zCAX<;m`?Qd%17e;%{5U3QPV>cErb>LRv+xHqD`{0i`ELJhj#OqXY-}eet5Y=K$ zfN^b+23gt%1xi8PDuVm;z3)|`)<+{wKvA(i^)K^UF8}}l00000|H2<88sc?;>f&_( O00008>?wPWFy}Vouz~K%%15CO! z7$D(_xl=@#i}7j=Q)fY(ngOGtsa^+)-{?I>3~GT{4N(NZ6E~NggRE&zEJ5A^ZGzVy zj996752uumsF8_)ttIFxuaD?rEiWL&8!|%8EP{7Q%T9^lJzuBjW%leCx)IT8pod=Z z=O%@`2e=#=xuHmK6Mh))Jenj)l6(+)e>cxCOjs&BD(o19F&vS-4FG*%vs|@BbivmH zt}W9Rv)LE)`C$=0U=gzEsf$>62_lax=6y}vR+tM{XakzH4l!NRfi;+_?MjDZfu8}P z0$2g226b3yIC#xkgC!`OvVyr8fc-Wz&i23Vl0K%qr~o#&gQtQhW%%Q5uh zh+PW!_GCwv{a~EO!_^28vFOk7O?H5GZrruR)Wg-#X3S|w^&RPt$`9G+g_Qp)XBX??|=piN|j;0NE)pQA$LQWTE29WdYu2_%&JJGC&Ciq1c zKfr}CQ_91j4$xIy+fT^o>2Y>j>d!1L_4e%Xkj@)@@}fQ$=RrNJR6)LB~*Xn<7tv`qe^z4v(RX4Ra1=l#i?Z^J6GUk|arzB>8##0igQrI#{3q Q%m4rY07*qoM6N<$f;54=I{*Lx literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanox.17.png b/tests_speculos/test-messages/nanox.17.png new file mode 100644 index 0000000000000000000000000000000000000000..91dcbdcafffb2d9619d06a89d1ca3cd941e0b3e6 GIT binary patch literal 540 zcmV+%0^|LOP)mqYZL}h^ z$86gHZykW)bZZ~|k30lSy{Uc0z5$q;iH`1D;YfrRonL}xb#Ik#)b4qN7xe?ME}_Ws z8vG9M&mA_d=sjpq-s4+>|G+FV)!#-bHPzpYF*u1;=%hiB8H1%LF8}}l0000000000 e00000u*^5y!{N7GRDl}+0000 zuWQcObvoKG!N0}hrTa|xe8-(zw8a>!Xl>+sw*kLor^xwHhAPHbNh~_UXY7MA7cDw{ zSv>>7rz4uRXqcY%{5^hClwseGN@{rRq7QM&m&x$b~X6r)JeI zHk2TVOy^w%VLgusKu{hGiD5Y!>TIr1AGH<&00000AG8&owgJxVxlESJ^i9g0OfnGP~49upQ9g!*Wnp}sue8rI&8|aH{q35mSt%3r(7uw zCC%Z^o@3E)oq@u?F{vMp>SVmXjE1Z6{$hy1PN+gV;W@k$>0XC#L#yV2+{8A8{nI)Z z8b{1>tu<0<=xkOxdlzZ&YC7%QQqn)I-xEGHVs)X`wCGEu!eL>Ut{jU#OIe>FZ`p4< zv?AmT^_`fVx-_l7WAmJOo8vv)(`%OutCJKq4^F8X+T4`)BQza9pJrV3v90|oDeOG) zhN@24V6<5as#nS{dIlI>Gw1!xTpkQ{$`9Ec3(nOU&@9K5F!{w;xV0r_+1$REgLX?Q uzu*0|0{{R3000000000000000NB#pM$!Pv;QncX!0000^PbX0`eJS}y70`%8T>n^%dV>bk>TM}C(cF@9*Jbj^5WTF;gKGIO)y zjlJ*9*PE9W@jdm~5r5wEu#TD8y6+Pnh8_O<-8#^Bx$bnOy3REWn^jXeAFev)TXe-F zTky7j?62N_lL@yzwx7O!=&4O1W7^|)7fcr%%i5*3D%0_G;tl)cpW3Io3*7!cdXW1o sV&B=e%knGs=l(tai5cc!1_ryAe9w9}zGZ%X&;TUp>FVdQ&MBb@086r+N&o-= literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanox.20.png b/tests_speculos/test-messages/nanox.20.png new file mode 100644 index 0000000000000000000000000000000000000000..dbbdb276b0c5dc45bbcf2bcd75d07d53f0a031f5 GIT binary patch literal 409 zcmV;K0cQS*P)`q0{{R30002+7GHNy-~z3+ zxOVp&XYz#ayC?9f9DPMUcTeCNatlAGwxGj>ub2kO7Np*GGT|1~*LzQf;@a;dD_|$W z6|hGmlB|HC;pp}s0002s9p=4D>BG2Ty6&!i@L*=GwLE`;ov?JcXMGvPA0@dH4((5V zxxTI-O4%e5a2CGYj^^vb+lmmY0C%6mH>V_zM8|-(kd#;+t=%7JaK(0f%eWPL}q1q`rz3aP}SG$xsuId|*u#6EJu@eCs<4_8%*o zfT0NDWjr0rBa1hpifWIAL(vMrR?04a0000006xwyJ@(1R7Nm>p00000NkvXXu0mjf DP-wNr literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanox.21.png b/tests_speculos/test-messages/nanox.21.png new file mode 100644 index 0000000000000000000000000000000000000000..7e236da60aac4c243bde35b1f1e6e89b08667b7e GIT binary patch literal 428 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|=lvba4!+nDh2#VBTQ`0oTB& zQuR0Wo^O`Q2WmDw=j^nt5PA1=lMD~DM8GjkMxZtr5RaFeIxXl`>A9PWJ5Spt#mT?z zkH1&P;r-Yk#X0pV}Dj{o?hW-Zy=?izjJaqX7)Mcd|YI-h=T-^N3W6J?kG zsd?Qd8ht@p>COA>q=Gw5QAIz?pG+$f-N}3ESr6;0jpAR03lgPGld6B7o*MV>#H`y! zpFf`19I{W?*l^Et&)jv(dvo#%y9H&+D*r~jv1Ih*`>M;l@h!^|i{cH-9d=)6OPYIl z_s=a4YiAv+{Ce}N*k8fJ6E#+L=v`OauW`ftseOd9Vpo0gztetaZXQk3OWC@eV>^@I zG|T@dk^g@f$rvmoH=d6AC$%=Q=jSJKIT-wu*=_G3~M%K@Xo_i(g zHdj=f}0nbj2ce(`^ylg~}&|D3(uKYYj4wN_8> zZ+{nI$SOM_u`XWN`GJVa%)|8wewyiq%N;jcrZ>2qaZP6O%USIHhQGde$#(P2X_0Am za>cr)_d91w^xa&kTe8(~cJ!Sjw%PrA_UXTy*^pHBRaLg|Hp>(n%N@&IHecX<8rA+@ zt@?Z3Tc`BAn|mvEM67U?-sqotgLD1FM;eFjtmKVW*_-_7yhZ8LXPMe>Vn4o%Nf9f^ z`+hoJ<0!|6lg&?=W2Y^gE_+jW0snV<+r|F-=BUKBu>UH)aehY~%a6I%n_H~9H~cgB gd3YxaEEIwCC1L+UJAKP%sYgMAp00i_>zopr07kXNa{vGU literal 0 HcmV?d00001 diff --git a/tests_speculos/test-messages/nanox.23.png b/tests_speculos/test-messages/nanox.23.png new file mode 100644 index 0000000000000000000000000000000000000000..dbbdb276b0c5dc45bbcf2bcd75d07d53f0a031f5 GIT binary patch literal 409 zcmV;K0cQS*P)`q0{{R30002+7GHNy-~z3+ zxOVp&XYz#ayC?9f9DPMUcTeCNatlAGwxGj>ub2kO7Np*GGT|1~*LzQf;@a;dD_|$W z6|hGmlB|HC;pp}s0002s9p=4D>BG2Ty6&!i@L*=GwLE`;ov?JcXMGvPA0@dH4((5V zxxTI-O4%e5a2CGYj^^vb+lmmY0C%6mH>V_zM8|-(kd#;+t=%7JaK(0f%eWPL}q1q`rz3aP}SG$xsuId|*u#6EJu@eCs<4_8%*o zfT0NDWjr0rBa1hpifWIAL(vMrR?04a0000006xwyJ@(1R7Nm>p00000NkvXXu0mjf DP-wNr literal 0 HcmV?d00001