From d1eb71ab10c134e705237e5d9e43e50943245ddd Mon Sep 17 00:00:00 2001 From: Robert Lukotka Date: Wed, 14 Sep 2022 17:31:57 +0200 Subject: [PATCH 1/4] NFT support --- CMakeLists.txt | 1 + app/src/apdu_handler.c | 87 +++--- app/src/common/app_main.c | 16 +- app/src/common/app_main.h | 2 + app/src/common/tx.c | 37 ++- app/src/parser.c | 220 +++++++++++-- app/src/parser.h | 3 +- app/src/parser_impl.c | 25 +- app/src/parser_impl.h | 2 +- app/src/parser_txdef.h | 2 + app/src/script_parser.c | 171 ++++++++++ app/src/script_parser.h | 39 +++ app/src/tx_metadata.h | 2 +- deps/ledger-zxlib/dockerized_build.mk | 1 + docs/APDUSPEC.md | 55 ++-- fuzz/parser_parse.cpp | 2 +- js/src/index.js | 3 +- js/src/signTransaction.js | 38 ++- tests/script_parser.cpp | 294 ++++++++++++++++++ tests/testvectors.cpp | 3 +- tests/tx_metadata.cpp | 4 +- tests_speculos/package.json | 2 + tests_speculos/test-transaction-nft.js | 239 ++++++++++++++ .../test-transaction-nft/nanos.01.png | Bin 0 -> 380 bytes .../test-transaction-nft/nanos.02.png | Bin 0 -> 364 bytes .../test-transaction-nft/nanos.03.png | Bin 0 -> 367 bytes .../test-transaction-nft/nanos.04.png | Bin 0 -> 394 bytes .../test-transaction-nft/nanos.05.png | Bin 0 -> 458 bytes .../test-transaction-nft/nanos.06.png | Bin 0 -> 544 bytes .../test-transaction-nft/nanos.07.png | Bin 0 -> 418 bytes .../test-transaction-nft/nanos.08.png | Bin 0 -> 399 bytes .../test-transaction-nft/nanos.09.png | Bin 0 -> 329 bytes .../test-transaction-nft/nanos.10.png | Bin 0 -> 649 bytes .../test-transaction-nft/nanos.11.png | Bin 0 -> 648 bytes .../test-transaction-nft/nanos.12.png | Bin 0 -> 311 bytes .../test-transaction-nft/nanos.13.png | Bin 0 -> 472 bytes .../test-transaction-nft/nanos.14.png | Bin 0 -> 318 bytes .../test-transaction-nft/nanos.15.png | Bin 0 -> 353 bytes .../test-transaction-nft/nanos.16.png | Bin 0 -> 412 bytes .../test-transaction-nft/nanos.17.png | Bin 0 -> 455 bytes .../test-transaction-nft/nanos.18.png | Bin 0 -> 512 bytes .../test-transaction-nft/nanos.19.png | Bin 0 -> 249 bytes .../test-transaction-nft/nanos.20.png | Bin 0 -> 380 bytes .../test-transaction-nft/nanos.21.png | Bin 0 -> 263 bytes .../test-transaction-nft/nanos.22.png | Bin 0 -> 367 bytes .../test-transaction-nft/nanos.23.png | Bin 0 -> 394 bytes .../test-transaction-nft/nanos.24.png | Bin 0 -> 458 bytes .../test-transaction-nft/nanos.25.png | Bin 0 -> 399 bytes .../test-transaction-nft/nanos.26.png | Bin 0 -> 329 bytes .../test-transaction-nft/nanos.27.png | Bin 0 -> 411 bytes .../test-transaction-nft/nanos.28.png | Bin 0 -> 340 bytes .../test-transaction-nft/nanos.29.png | Bin 0 -> 649 bytes .../test-transaction-nft/nanos.30.png | Bin 0 -> 648 bytes .../test-transaction-nft/nanos.31.png | Bin 0 -> 311 bytes .../test-transaction-nft/nanos.32.png | Bin 0 -> 472 bytes .../test-transaction-nft/nanos.33.png | Bin 0 -> 318 bytes .../test-transaction-nft/nanos.34.png | Bin 0 -> 353 bytes .../test-transaction-nft/nanos.35.png | Bin 0 -> 412 bytes .../test-transaction-nft/nanos.36.png | Bin 0 -> 455 bytes .../test-transaction-nft/nanos.37.png | Bin 0 -> 512 bytes .../test-transaction-nft/nanos.38.png | Bin 0 -> 249 bytes .../test-transaction-nft/nanos.39.png | Bin 0 -> 380 bytes .../test-transaction-nft/nanox.01.png | Bin 0 -> 409 bytes .../test-transaction-nft/nanox.02.png | Bin 0 -> 420 bytes .../test-transaction-nft/nanox.03.png | Bin 0 -> 405 bytes .../test-transaction-nft/nanox.04.png | Bin 0 -> 469 bytes .../test-transaction-nft/nanox.05.png | Bin 0 -> 524 bytes .../test-transaction-nft/nanox.06.png | Bin 0 -> 534 bytes .../test-transaction-nft/nanox.07.png | Bin 0 -> 701 bytes .../test-transaction-nft/nanox.08.png | Bin 0 -> 441 bytes .../test-transaction-nft/nanox.09.png | Bin 0 -> 358 bytes .../test-transaction-nft/nanox.10.png | Bin 0 -> 956 bytes .../test-transaction-nft/nanox.11.png | Bin 0 -> 499 bytes .../test-transaction-nft/nanox.12.png | Bin 0 -> 341 bytes .../test-transaction-nft/nanox.13.png | Bin 0 -> 539 bytes .../test-transaction-nft/nanox.14.png | Bin 0 -> 359 bytes .../test-transaction-nft/nanox.15.png | Bin 0 -> 423 bytes .../test-transaction-nft/nanox.16.png | Bin 0 -> 459 bytes .../test-transaction-nft/nanox.17.png | Bin 0 -> 517 bytes .../test-transaction-nft/nanox.18.png | Bin 0 -> 573 bytes .../test-transaction-nft/nanox.19.png | Bin 0 -> 355 bytes .../test-transaction-nft/nanox.20.png | Bin 0 -> 409 bytes .../test-transaction-nft/nanox.21.png | Bin 0 -> 420 bytes .../test-transaction-nft/nanox.22.png | Bin 0 -> 316 bytes .../test-transaction-nft/nanox.23.png | Bin 0 -> 469 bytes .../test-transaction-nft/nanox.24.png | Bin 0 -> 524 bytes .../test-transaction-nft/nanox.25.png | Bin 0 -> 534 bytes .../test-transaction-nft/nanox.26.png | Bin 0 -> 441 bytes .../test-transaction-nft/nanox.27.png | Bin 0 -> 358 bytes .../test-transaction-nft/nanox.28.png | Bin 0 -> 464 bytes .../test-transaction-nft/nanox.29.png | Bin 0 -> 379 bytes .../test-transaction-nft/nanox.30.png | Bin 0 -> 956 bytes .../test-transaction-nft/nanox.31.png | Bin 0 -> 499 bytes .../test-transaction-nft/nanox.32.png | Bin 0 -> 341 bytes .../test-transaction-nft/nanox.33.png | Bin 0 -> 539 bytes .../test-transaction-nft/nanox.34.png | Bin 0 -> 359 bytes .../test-transaction-nft/nanox.35.png | Bin 0 -> 423 bytes .../test-transaction-nft/nanox.36.png | Bin 0 -> 459 bytes .../test-transaction-nft/nanox.37.png | Bin 0 -> 517 bytes .../test-transaction-nft/nanox.38.png | Bin 0 -> 573 bytes .../test-transaction-nft/nanox.39.png | Bin 0 -> 355 bytes .../test-transaction-nft/nanox.40.png | Bin 0 -> 409 bytes tests_speculos/yarn-error.log | 59 ++++ 103 files changed, 1182 insertions(+), 125 deletions(-) create mode 100644 app/src/script_parser.c create mode 100644 app/src/script_parser.h create mode 100644 tests/script_parser.cpp create mode 100644 tests_speculos/test-transaction-nft.js create mode 100644 tests_speculos/test-transaction-nft/nanos.01.png create mode 100644 tests_speculos/test-transaction-nft/nanos.02.png create mode 100644 tests_speculos/test-transaction-nft/nanos.03.png create mode 100644 tests_speculos/test-transaction-nft/nanos.04.png create mode 100644 tests_speculos/test-transaction-nft/nanos.05.png create mode 100644 tests_speculos/test-transaction-nft/nanos.06.png create mode 100644 tests_speculos/test-transaction-nft/nanos.07.png create mode 100644 tests_speculos/test-transaction-nft/nanos.08.png create mode 100644 tests_speculos/test-transaction-nft/nanos.09.png create mode 100644 tests_speculos/test-transaction-nft/nanos.10.png create mode 100644 tests_speculos/test-transaction-nft/nanos.11.png create mode 100644 tests_speculos/test-transaction-nft/nanos.12.png create mode 100644 tests_speculos/test-transaction-nft/nanos.13.png create mode 100644 tests_speculos/test-transaction-nft/nanos.14.png create mode 100644 tests_speculos/test-transaction-nft/nanos.15.png create mode 100644 tests_speculos/test-transaction-nft/nanos.16.png create mode 100644 tests_speculos/test-transaction-nft/nanos.17.png create mode 100644 tests_speculos/test-transaction-nft/nanos.18.png create mode 100644 tests_speculos/test-transaction-nft/nanos.19.png create mode 100644 tests_speculos/test-transaction-nft/nanos.20.png create mode 100644 tests_speculos/test-transaction-nft/nanos.21.png create mode 100644 tests_speculos/test-transaction-nft/nanos.22.png create mode 100644 tests_speculos/test-transaction-nft/nanos.23.png create mode 100644 tests_speculos/test-transaction-nft/nanos.24.png create mode 100644 tests_speculos/test-transaction-nft/nanos.25.png create mode 100644 tests_speculos/test-transaction-nft/nanos.26.png create mode 100644 tests_speculos/test-transaction-nft/nanos.27.png create mode 100644 tests_speculos/test-transaction-nft/nanos.28.png create mode 100644 tests_speculos/test-transaction-nft/nanos.29.png create mode 100644 tests_speculos/test-transaction-nft/nanos.30.png create mode 100644 tests_speculos/test-transaction-nft/nanos.31.png create mode 100644 tests_speculos/test-transaction-nft/nanos.32.png create mode 100644 tests_speculos/test-transaction-nft/nanos.33.png create mode 100644 tests_speculos/test-transaction-nft/nanos.34.png create mode 100644 tests_speculos/test-transaction-nft/nanos.35.png create mode 100644 tests_speculos/test-transaction-nft/nanos.36.png create mode 100644 tests_speculos/test-transaction-nft/nanos.37.png create mode 100644 tests_speculos/test-transaction-nft/nanos.38.png create mode 100644 tests_speculos/test-transaction-nft/nanos.39.png create mode 100644 tests_speculos/test-transaction-nft/nanox.01.png create mode 100644 tests_speculos/test-transaction-nft/nanox.02.png create mode 100644 tests_speculos/test-transaction-nft/nanox.03.png create mode 100644 tests_speculos/test-transaction-nft/nanox.04.png create mode 100644 tests_speculos/test-transaction-nft/nanox.05.png create mode 100644 tests_speculos/test-transaction-nft/nanox.06.png create mode 100644 tests_speculos/test-transaction-nft/nanox.07.png create mode 100644 tests_speculos/test-transaction-nft/nanox.08.png create mode 100644 tests_speculos/test-transaction-nft/nanox.09.png create mode 100644 tests_speculos/test-transaction-nft/nanox.10.png create mode 100644 tests_speculos/test-transaction-nft/nanox.11.png create mode 100644 tests_speculos/test-transaction-nft/nanox.12.png create mode 100644 tests_speculos/test-transaction-nft/nanox.13.png create mode 100644 tests_speculos/test-transaction-nft/nanox.14.png create mode 100644 tests_speculos/test-transaction-nft/nanox.15.png create mode 100644 tests_speculos/test-transaction-nft/nanox.16.png create mode 100644 tests_speculos/test-transaction-nft/nanox.17.png create mode 100644 tests_speculos/test-transaction-nft/nanox.18.png create mode 100644 tests_speculos/test-transaction-nft/nanox.19.png create mode 100644 tests_speculos/test-transaction-nft/nanox.20.png create mode 100644 tests_speculos/test-transaction-nft/nanox.21.png create mode 100644 tests_speculos/test-transaction-nft/nanox.22.png create mode 100644 tests_speculos/test-transaction-nft/nanox.23.png create mode 100644 tests_speculos/test-transaction-nft/nanox.24.png create mode 100644 tests_speculos/test-transaction-nft/nanox.25.png create mode 100644 tests_speculos/test-transaction-nft/nanox.26.png create mode 100644 tests_speculos/test-transaction-nft/nanox.27.png create mode 100644 tests_speculos/test-transaction-nft/nanox.28.png create mode 100644 tests_speculos/test-transaction-nft/nanox.29.png create mode 100644 tests_speculos/test-transaction-nft/nanox.30.png create mode 100644 tests_speculos/test-transaction-nft/nanox.31.png create mode 100644 tests_speculos/test-transaction-nft/nanox.32.png create mode 100644 tests_speculos/test-transaction-nft/nanox.33.png create mode 100644 tests_speculos/test-transaction-nft/nanox.34.png create mode 100644 tests_speculos/test-transaction-nft/nanox.35.png create mode 100644 tests_speculos/test-transaction-nft/nanox.36.png create mode 100644 tests_speculos/test-transaction-nft/nanox.37.png create mode 100644 tests_speculos/test-transaction-nft/nanox.38.png create mode 100644 tests_speculos/test-transaction-nft/nanox.39.png create mode 100644 tests_speculos/test-transaction-nft/nanox.40.png create mode 100644 tests_speculos/yarn-error.log diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d088483..ebace6cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,6 +97,7 @@ file(GLOB_RECURSE LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/app/src/parser_impl.c ${CMAKE_CURRENT_SOURCE_DIR}/app/src/json/json_parser.c ${CMAKE_CURRENT_SOURCE_DIR}/app/src/tx_metadata.c + ${CMAKE_CURRENT_SOURCE_DIR}/app/src/script_parser.c app/src/base32.c app/src/crypto.c ) diff --git a/app/src/apdu_handler.c b/app/src/apdu_handler.c index dc1e7156..07ec6d2b 100644 --- a/app/src/apdu_handler.c +++ b/app/src/apdu_handler.c @@ -33,6 +33,7 @@ #include "hdpath.h" #include "parser_impl.h" #include "message.h" +#include "script_parser.h" __Z_INLINE void handleGetPubkey(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { hasPubkey = false; @@ -83,50 +84,56 @@ __Z_INLINE void handleGetPubkey(volatile uint32_t *flags, volatile uint32_t *tx, __Z_INLINE void handleSign(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { process_chunk_response_t callType = process_chunk(tx, rx); - if (callType == PROCESS_CHUNK_NOT_FINISHED) { - THROW(APDU_CODE_OK); - } - if (callType == PROCESS_CHUNK_FINISHED_MESSAGE) { - zxerr_t err = message_parse(); - if (err != zxerr_ok) { - const char *error_msg = "Invalid message"; - int error_msg_length = strlen(error_msg); - MEMCPY(G_io_apdu_buffer, error_msg, error_msg_length); - *tx += (error_msg_length); - 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) { - int error_msg_length = strlen(error_msg); - MEMCPY(G_io_apdu_buffer, error_msg, error_msg_length); - *tx += (error_msg_length); - THROW(APDU_CODE_DATA_INVALID); - } + switch (callType) { + case PROCESS_CHUNK_NOT_FINISHED: + THROW(APDU_CODE_OK); + case PROCESS_CHUNK_FINISHED_MESSAGE:; + zxerr_t err = message_parse(); + if (err != zxerr_ok) { + const char *error_msg = "Invalid message"; + int error_msg_length = strlen(error_msg); + MEMCPY(G_io_apdu_buffer, error_msg, error_msg_length); + *tx += (error_msg_length); + 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; + break; + case PROCESS_CHUNK_FINISHED_NFT1: + case PROCESS_CHUNK_FINISHED_NFT2: + case PROCESS_CHUNK_FINISHED_NO_METADATA: + case PROCESS_CHUNK_FINISHED_WITH_METADATA: ; + const char *error_msg = tx_parse(callType); + + if (error_msg != NULL) { + int error_msg_length = strlen(error_msg); + MEMCPY(G_io_apdu_buffer, error_msg, error_msg_length); + *tx += (error_msg_length); + THROW(APDU_CODE_DATA_INVALID); + } + show_address = SHOW_ADDRESS_NONE; + loadAddressCompareHdPathFromSlot(); + + //if we found matching hdPath on slot 0 + if (show_address == SHOW_ADDRESS_YES || show_address == SHOW_ADDRESS_YES_HASH_MISMATCH) { + checkAddressUsedInTx(); + } + else { + addressUsedInTx = 0; + } - show_address = SHOW_ADDRESS_NONE; - loadAddressCompareHdPathFromSlot(); - - //if we found matching hdPath on slot 0 - if (show_address == SHOW_ADDRESS_YES || show_address == SHOW_ADDRESS_YES_HASH_MISMATCH) { - checkAddressUsedInTx(); + CHECK_APP_CANARY() + view_review_init(tx_getItem, tx_getNumItems, app_sign); + view_review_show(); + *flags |= IO_ASYNCH_REPLY; + break; + default: + THROW(APDU_CODE_UNKNOWN); } - else { - addressUsedInTx = 0; - } - - CHECK_APP_CANARY() - view_review_init(tx_getItem, tx_getNumItems, app_sign); - view_review_show(); - *flags |= IO_ASYNCH_REPLY; } __Z_INLINE void handleSlotStatus(__Z_UNUSED volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { diff --git a/app/src/common/app_main.c b/app/src/common/app_main.c index 5174ce83..ebfbc2b6 100644 --- a/app/src/common/app_main.c +++ b/app/src/common/app_main.c @@ -116,8 +116,9 @@ void extractHDPathAndCryptoOptions(uint32_t rx, uint32_t offset) { process_chunk_response_t process_chunk(__Z_UNUSED volatile uint32_t *tx, uint32_t rx) { const uint8_t payloadType = G_io_apdu_buffer[OFFSET_PAYLOAD_TYPE]; + const uint8_t p2 = G_io_apdu_buffer[OFFSET_P2]; - if (G_io_apdu_buffer[OFFSET_P2] != 0) { + if (p2 != 0 && payloadType != 0x02) { THROW(APDU_CODE_INVALIDP1P2); } @@ -127,7 +128,7 @@ process_chunk_response_t process_chunk(__Z_UNUSED volatile uint32_t *tx, uint32_ uint32_t added; switch (payloadType) { - case 0: + case 0x00: tx_initialize(); tx_reset(); extractHDPathAndCryptoOptions(rx, OFFSET_DATA); @@ -144,7 +145,16 @@ process_chunk_response_t process_chunk(__Z_UNUSED volatile uint32_t *tx, uint32_ if (added != rx - OFFSET_DATA) { THROW(APDU_CODE_OUTPUT_BUFFER_TOO_SMALL); } - return PROCESS_CHUNK_FINISHED_NO_METADATA; + switch (p2) { + case 0x01: + return PROCESS_CHUNK_FINISHED_NO_METADATA; + case 0x02: + return PROCESS_CHUNK_FINISHED_NFT1; + case 0x03: + return PROCESS_CHUNK_FINISHED_NFT2; + default: + THROW(APDU_CODE_INVALIDP1P2); + } case 0x03: if (storeTxMetadata(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA) != PARSER_OK) { initStoredTxMetadata(); //delete merkle tree proof on error for redundant security diff --git a/app/src/common/app_main.h b/app/src/common/app_main.h index 9f9fcbb0..6ad96323 100644 --- a/app/src/common/app_main.h +++ b/app/src/common/app_main.h @@ -49,6 +49,8 @@ typedef enum { PROCESS_CHUNK_NOT_FINISHED = 0, PROCESS_CHUNK_FINISHED_WITH_METADATA, PROCESS_CHUNK_FINISHED_NO_METADATA, + PROCESS_CHUNK_FINISHED_NFT1, + PROCESS_CHUNK_FINISHED_NFT2, PROCESS_CHUNK_FINISHED_MESSAGE, } process_chunk_response_t; diff --git a/app/src/common/tx.c b/app/src/common/tx.c index 9305c93f..dc6b5594 100644 --- a/app/src/common/tx.c +++ b/app/src/common/tx.c @@ -75,11 +75,14 @@ uint8_t *tx_get_buffer() { } const char *tx_parse(process_chunk_response_t typeOfCall) { + script_parsed_type_t scriptType = (typeOfCall == PROCESS_CHUNK_FINISHED_NFT1) ? SCRIPT_TYPE_NFT_SETUP_COLLECTION : + (typeOfCall == PROCESS_CHUNK_FINISHED_NFT2) ? SCRIPT_TYPE_NFT_TRANSFER : SCRIPT_TYPE_UNKNOWN; // parse tx uint8_t err = parser_parse( &ctx_parsed_tx, tx_get_buffer(), - tx_get_buffer_length()); + tx_get_buffer_length(), + scriptType); if (err != PARSER_OK) { return parser_getErrorDescription(err); @@ -87,19 +90,25 @@ const char *tx_parse(process_chunk_response_t typeOfCall) { //parse metadata parser_tx_obj.metadataInitialized = false; - if (typeOfCall == PROCESS_CHUNK_FINISHED_WITH_METADATA) { - MEMZERO(&parser_tx_obj.metadata, sizeof(parser_tx_obj.metadata)); - err = parseTxMetadata(parser_tx_obj.hash.digest, &parser_tx_obj.metadata); - if (err != PARSER_OK) { - return parser_getErrorDescription(err); - } - parser_tx_obj.metadataInitialized = true; - } - else { - //If no metadata provided = script is not known, expert mode must be on - if (!app_mode_expert()) { - return parser_getErrorDescription(PARSER_UNEXPECTED_SCRIPT); - } + switch (typeOfCall) { + case PROCESS_CHUNK_FINISHED_WITH_METADATA: + MEMZERO(&parser_tx_obj.metadata, sizeof(parser_tx_obj.metadata)); + err = parseTxMetadata(parser_tx_obj.hash.digest, &parser_tx_obj.metadata); + if (err != PARSER_OK) { + return parser_getErrorDescription(err); + } + parser_tx_obj.metadataInitialized = true; + break; + case PROCESS_CHUNK_FINISHED_NFT1: + case PROCESS_CHUNK_FINISHED_NFT2: + break; // we do not need metadata for these scripts + case PROCESS_CHUNK_FINISHED_NO_METADATA: + if (!app_mode_expert()) { // we do not need metadata for these scripts, but this workflow should work only in expert mode + return parser_getErrorDescription(PARSER_UNEXPECTED_SCRIPT); + } + break; + default: + return parser_getErrorDescription(PARSER_UNEXPECTED_ERROR); } CHECK_APP_CANARY() diff --git a/app/src/parser.c b/app/src/parser.c index 474b5ea6..4e00279e 100644 --- a/app/src/parser.c +++ b/app/src/parser.c @@ -35,9 +35,9 @@ #define MAX_JSON_ARRAY_TOKEN_COUNT 64 -parser_error_t parser_parse(parser_context_t *ctx, const uint8_t *data, size_t dataLen) { +parser_error_t parser_parse(parser_context_t *ctx, const uint8_t *data, size_t dataLen, script_parsed_type_t scriptType) { CHECK_PARSER_ERR(parser_init(ctx, data, dataLen)) - return _read(ctx, &parser_tx_obj); + return _read(ctx, &parser_tx_obj, scriptType); } parser_error_t parser_validate(const parser_context_t *ctx) { @@ -624,15 +624,145 @@ parser_error_t parser_getItem_internal(int8_t *displayIdx, } } else { - SCREEN(true) { - snprintf(outKey, outKeyLen, "Script hash"); - pageHexString(outVal, outValLen, parser_tx_obj.hash.digest, sizeof(parser_tx_obj.hash.digest), pageIdx, pageCount); - return PARSER_OK; - } - SCREEN(true) { - snprintf(outKey, outKeyLen, "Verify script hash"); - snprintf(outVal, outValLen, "on a secure device."); - return PARSER_OK; + const char storageString[] = "/storage/"; + const char publicString[] = "/public/"; + switch (parser_tx_obj.parsedScript.script_type) { + case SCRIPT_TYPE_NFT_SETUP_COLLECTION: + //validate basic assumptions on the script + if (parser_tx_obj.parsedScript.elements_count != 9) { + return PARSER_UNEXPECTED_ERROR; + } + // storagePath should be preceeded with "/storage/" + if (parser_tx_obj.parsedScript.elements[2].data < parser_tx_obj.parsedScript.elements[1].data + (sizeof(storageString)-1) || + memcmp(parser_tx_obj.parsedScript.elements[2].data - (sizeof(storageString)-1), storageString, sizeof(storageString)-1)) { + return PARSER_UNEXPECTED_ERROR; + } + // publicCollectionContractName and publicCollectionName should be separated by a single '.' + if (parser_tx_obj.parsedScript.elements[5].data + parser_tx_obj.parsedScript.elements[5].length + 1 != + parser_tx_obj.parsedScript.elements[6].data || + parser_tx_obj.parsedScript.elements[5].data[parser_tx_obj.parsedScript.elements[5].length] != '.') { + return PARSER_UNEXPECTED_ERROR; + } + // publicPath should be preceeded with "/public/" + if (parser_tx_obj.parsedScript.elements[7].data < parser_tx_obj.parsedScript.elements[6].data + (sizeof(publicString)-1) || + memcmp(parser_tx_obj.parsedScript.elements[7].data - (sizeof(publicString)-1), publicString, sizeof(publicString)-1)) { + return PARSER_UNEXPECTED_ERROR; + } + + SCREEN(true) { + snprintf(outKey, outKeyLen, "Set Up NFT"); + snprintf(outVal, outValLen, "Collection"); + return PARSER_OK; + } + SCREEN(true) { + snprintf(outKey, outKeyLen, "Contract Name"); + pageStringExt(outVal, outValLen, + (const char *) parser_tx_obj.parsedScript.elements[0].data, + parser_tx_obj.parsedScript.elements[0].length, + pageIdx, pageCount); + return PARSER_OK; + } + SCREEN(true) { + snprintf(outKey, outKeyLen, "Contract Address"); + pageStringExt(outVal, outValLen, + (const char *) parser_tx_obj.parsedScript.elements[1].data, + parser_tx_obj.parsedScript.elements[1].length, + pageIdx, pageCount); + return PARSER_OK; + } + SCREEN(true) { + snprintf(outKey, outKeyLen, "Storage Path"); + pageStringExt(outVal, outValLen, + (const char *) parser_tx_obj.parsedScript.elements[2].data - (sizeof(storageString)-1), + parser_tx_obj.parsedScript.elements[2].length + (sizeof(storageString)-1), + pageIdx, pageCount); + return PARSER_OK; + } + SCREEN(true) { + snprintf(outKey, outKeyLen, "Public Coll. Name"); + pageStringExt(outVal, outValLen, + (const char *) parser_tx_obj.parsedScript.elements[5].data, + parser_tx_obj.parsedScript.elements[5].length + parser_tx_obj.parsedScript.elements[6].length + 1, + pageIdx, pageCount); + return PARSER_OK; + } + SCREEN(true) { + snprintf(outKey, outKeyLen, "Public Path"); + pageStringExt(outVal, outValLen, + (const char *) parser_tx_obj.parsedScript.elements[7].data - (sizeof(publicString)-1), + parser_tx_obj.parsedScript.elements[7].length + (sizeof(publicString)-1), + pageIdx, pageCount); + return PARSER_OK; + } + break; + case SCRIPT_TYPE_NFT_TRANSFER: + //validate basic assumptions on the script + if (parser_tx_obj.parsedScript.elements_count != 7) { + return PARSER_UNEXPECTED_ERROR; + } + // storagePath should be preceeded with "/storage/" + if (parser_tx_obj.parsedScript.elements[2].data < parser_tx_obj.parsedScript.elements[1].data + (sizeof(storageString)-1) || + memcmp(parser_tx_obj.parsedScript.elements[2].data - (sizeof(storageString)-1), storageString, sizeof(storageString)-1)) { + return PARSER_UNEXPECTED_ERROR; + } + // publicPath should be preceeded with "/public/" + if (parser_tx_obj.parsedScript.elements[6].data < parser_tx_obj.parsedScript.elements[5].data + (sizeof(publicString)-1) || + memcmp(parser_tx_obj.parsedScript.elements[6].data - (sizeof(publicString)-1), publicString, sizeof(publicString)-1)) { + return PARSER_UNEXPECTED_ERROR; + } + + SCREEN(true) { + snprintf(outKey, outKeyLen, "Transfer NFT"); + snprintf(outVal, outValLen, ""); + return PARSER_OK; + } + SCREEN(true) { + snprintf(outKey, outKeyLen, "Contract Name"); + pageStringExt(outVal, outValLen, + (const char *) parser_tx_obj.parsedScript.elements[0].data, + parser_tx_obj.parsedScript.elements[0].length, + pageIdx, pageCount); + return PARSER_OK; + } + SCREEN(true) { + snprintf(outKey, outKeyLen, "Contract Address"); + pageStringExt(outVal, outValLen, + (const char *) parser_tx_obj.parsedScript.elements[1].data, + parser_tx_obj.parsedScript.elements[1].length, + pageIdx, pageCount); + return PARSER_OK; + } + SCREEN(true) { + snprintf(outKey, outKeyLen, "Storage Path"); + pageStringExt(outVal, outValLen, + (const char *) parser_tx_obj.parsedScript.elements[2].data - (sizeof(storageString)-1), + parser_tx_obj.parsedScript.elements[2].length + (sizeof(storageString)-1), + pageIdx, pageCount); + return PARSER_OK; + } + SCREEN(true) { + snprintf(outKey, outKeyLen, "Public Path"); + pageStringExt(outVal, outValLen, + (const char *) parser_tx_obj.parsedScript.elements[6].data - (sizeof(publicString)-1), + parser_tx_obj.parsedScript.elements[6].length + (sizeof(publicString)-1), + pageIdx, pageCount); + return PARSER_OK; + } + break; + case SCRIPT_TYPE_UNKNOWN: + SCREEN(true) { + snprintf(outKey, outKeyLen, "Script hash"); + pageHexString(outVal, outValLen, parser_tx_obj.hash.digest, sizeof(parser_tx_obj.hash.digest), pageIdx, pageCount); + return PARSER_OK; + } + SCREEN(true) { + snprintf(outKey, outKeyLen, "Verify script hash"); + snprintf(outVal, outValLen, "on a secure device."); + return PARSER_OK; + } + break; + default: + return PARSER_UNEXPECTED_ERROR; } } @@ -699,28 +829,56 @@ parser_error_t parser_getItem_internal(int8_t *displayIdx, } } else { // No metadata - SCREEN(true) { - snprintf(outKey, outKeyLen, "Script arguments"); - snprintf(outVal, outValLen, "Number of arguments: %d", parser_tx_obj.arguments.argCount); - return PARSER_OK; - } - for(size_t i=0; idigest, sizeof(s->digest)); sha256(script.buffer, script.bufferLen, s->digest); + MEMZERO(e, sizeof(*e)); + e->script_type = SCRIPT_TYPE_UNKNOWN; + switch (scriptType) { + case SCRIPT_TYPE_NFT_SETUP_COLLECTION: + if (!parseNFT1(e, script.buffer, script.bufferLen)) { + return PARSER_UNEXPECTED_SCRIPT; + } + break; + case SCRIPT_TYPE_NFT_TRANSFER: + if (!parseNFT2(e, script.buffer, script.bufferLen)) { + return PARSER_UNEXPECTED_SCRIPT; + } + break; + case SCRIPT_TYPE_UNKNOWN: + break; + default: + return PARSER_UNEXPECTED_ERROR; + } + return PARSER_OK; } @@ -540,7 +559,7 @@ parser_error_t _readProposalAuthorizers(parser_context_t *c, flow_proposal_autho return PARSER_OK; } -parser_error_t _read(parser_context_t *c, parser_tx_t *v) { +parser_error_t _read(parser_context_t *c, parser_tx_t *v, script_parsed_type_t scriptType) { rlp_kind_e kind; uint32_t bytesConsumed; @@ -563,7 +582,7 @@ parser_error_t _read(parser_context_t *c, parser_tx_t *v) { // Go through the inner list - CHECK_PARSER_ERR(_readScript(&ctx_rootInnerList, &v->hash)) + CHECK_PARSER_ERR(_readScript(&ctx_rootInnerList, &v->hash, &v->parsedScript, scriptType)) CHECK_PARSER_ERR(_readArguments(&ctx_rootInnerList, &v->arguments)) CHECK_PARSER_ERR(_readReferenceBlockId(&ctx_rootInnerList, &v->referenceBlockId)) CHECK_PARSER_ERR(_readGasLimit(&ctx_rootInnerList, &v->gasLimit)) diff --git a/app/src/parser_impl.h b/app/src/parser_impl.h index a10aa2fa..83297092 100644 --- a/app/src/parser_impl.h +++ b/app/src/parser_impl.h @@ -30,7 +30,7 @@ extern parser_tx_t parser_tx_obj; parser_error_t parser_init(parser_context_t *ctx, const uint8_t *buffer, uint16_t bufferSize); -parser_error_t _read(parser_context_t *c, parser_tx_t *v); +parser_error_t _read(parser_context_t *c, parser_tx_t *v, script_parsed_type_t scriptType); parser_error_t _validateTx(const parser_context_t *c, const parser_tx_t *v); diff --git a/app/src/parser_txdef.h b/app/src/parser_txdef.h index 6dbd18cb..df623236 100644 --- a/app/src/parser_txdef.h +++ b/app/src/parser_txdef.h @@ -28,6 +28,7 @@ extern "C" { #include #include #include "crypto.h" +#include "script_parser.h" typedef enum { CHAIN_ID_UNKNOWN, @@ -77,6 +78,7 @@ typedef struct { typedef struct { bool metadataInitialized; parsed_tx_metadata_t metadata; + script_parsed_elements_t parsedScript; flow_script_hash_t hash; flow_argument_list_t arguments; flow_reference_block_id_t referenceBlockId; diff --git a/app/src/script_parser.c b/app/src/script_parser.c new file mode 100644 index 00000000..d1c65ed5 --- /dev/null +++ b/app/src/script_parser.c @@ -0,0 +1,171 @@ +#include "script_parser.h" + + +static bool isElementChar(uint8_t scriptChar) { + return ('a'<=scriptChar && scriptChar<='z') || + ('A'<=scriptChar && scriptChar<='Z') || + ('0'<=scriptChar && scriptChar<='9') || + ('_'==scriptChar); +} + +//returns parsed element length, 0 indicates error +static size_t parseElement(size_t index, const uint8_t NV_VOLATILE *scriptToParse, size_t scriptToParseSize) { + size_t elementLength = 0; + while (index + elementLength < scriptToParseSize) { + uint8_t scriptChar = scriptToParse[index + elementLength]; + if (!isElementChar(scriptChar)) { + return elementLength; + } + elementLength += 1; + } + return elementLength; +} + + +bool parseScript(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLATILE *scriptToParse, size_t scriptToParseSize, + const uint8_t *scriptTemplate, size_t scriptTemplateSize) { + parsedElements->elements_count = 0; + size_t scriptRead = 0; + size_t templateRead = 0; + while (templateRead < scriptTemplateSize) { + uint8_t templateChar = scriptTemplate[templateRead]; + + if (templateChar == 1) { //token to parse + size_t elementLen = parseElement(scriptRead, scriptToParse, scriptToParseSize); + if (elementLen == 0 || parsedElements->elements_count == MAX_SCRIPT_PARSED_ELEMENTS) { + return false; + } + parsedElements->elements[parsedElements->elements_count].data = scriptToParse + scriptRead; + parsedElements->elements[parsedElements->elements_count].length = elementLen; + + parsedElements->elements_count += 1; + scriptRead += elementLen; + templateRead += 1; + + continue; + } + + if (templateChar != scriptToParse[scriptRead]) { + return false; + } + scriptRead += 1; + templateRead += 1; + } + + if (scriptRead != scriptToParseSize || templateRead != scriptTemplateSize) { + return false; + } + + parsedElements->script_type = SCRIPT_TYPE_UNKNOWN; + return true; +} + + +#define ELEMENTS_MUST_BE_EQUAL(pE, i, j) { \ + if ((i >= pE->elements_count) || (j >= pE->elements_count)) return false; \ + if (pE->elements[i].length != pE->elements[j].length) return false; \ + if (MEMCMP(pE->elements[i].data, pE->elements[j].data, pE->elements[i].length)) return false; \ +} + +// Elements : +// 0 - contractName +// 1 - contractAddress +// 2 - storagePath +// 3 - contractName +// 4 - storagePath +// 5 - publicCollectionContractName +// 6 - publicCollectionName +// 7 - publicPath +// 8 - storagePath +bool parseNFT1(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLATILE *scriptToParse, size_t scriptToParseSize) { + const char template[] = + "import NonFungibleToken from 0xNONFUNGIBLETOKEN\n" + "import MetadataViews from 0xMETADATAVIEWS\n" + "import \001 from \001\n" + "transaction {\n" + " prepare(acct: AuthAccount) {\n" + " let collectionType = acct.type(at: /storage/\001)\n" + " // if there already is a collection stored, return\n" + " if (collectionType != nil) {\n" + " return\n" + " }\n" + " // create empty collection\n" + " let collection <- \001.createEmptyCollection()\n" + " // put the new Collection in storage\n" + " acct.save(<-collection, to: /storage/\001)\n" + " // create a public capability for the collection\n" + " acct.link<&{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, \001.\001, MetadataViews.ResolverCollection}>(\n" + " /public/\001,\n" + " target: /storage/\001\n" + " )\n" + " }\n" + "}\n"; + + if(!parseScript(parsedElements, scriptToParse, scriptToParseSize, (const uint8_t *) template, sizeof(template)-1)) { // -1 to strip terminating 0 + return false; + } + + ELEMENTS_MUST_BE_EQUAL(parsedElements, 0, 3); + ELEMENTS_MUST_BE_EQUAL(parsedElements, 2, 4); + ELEMENTS_MUST_BE_EQUAL(parsedElements, 2, 8); + + parsedElements->script_type = SCRIPT_TYPE_NFT_SETUP_COLLECTION; + return true; +} + + +// Elements : +// 0 - contractName +// 1 - contractAddress +// 2 - storagePath +// 3 - contractName +// 4 - contractName +// 5 - storagePath +// 6 - publicPath +bool parseNFT2(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLATILE *scriptToParse, size_t scriptToParseSize) { + const char template[] = + "import NonFungibleToken from 0xNONFUNGIBLETOKEN\n" + "import \001 from \001\n" + "transaction(recipient: Address, withdrawID: UInt64) {\n" + " // local variable for storing the transferred nft\n" + " let transferToken: @NonFungibleToken.NFT\n" + " prepare(owner: AuthAccount) {\n" + " // check if collection exists\n" + " if (owner.type(at: /storage/\001) != Type<@\001.Collection>()) {\n" + " panic(\"Could not borrow a reference to the stored collection\")\n" + " }\n" + " // borrow a reference to the collection\n" + " let collectionRef = owner\n" + " .borrow<&\001.Collection>(from: /storage/\001)!\n" + " // withdraw the NFT\n" + " self.transferToken <- collectionRef.withdraw(withdrawID: withdrawID)\n" + " }\n" + " execute {\n" + " // get the recipient's public account object\n" + " let recipient = getAccount(recipient)\n" + " // get receivers capability\n" + " let nonFungibleTokenCapability = recipient\n" + " .getCapability<&{NonFungibleToken.CollectionPublic}>(/public/\001)\n" + " // check the recipient has a NonFungibleToken public capability\n" + " if (!nonFungibleTokenCapability.check()) {\n" + " panic(\"Could not borrow a reference to the receiver's collection\")\n" + " }\n" + " // deposit nft to recipients collection\n" + " nonFungibleTokenCapability\n" + " .borrow()!\n" + " .deposit(token: <-self.transferToken)\n" + " }\n" + "}\n"; + if(!parseScript(parsedElements, scriptToParse, scriptToParseSize, (const uint8_t *) template, sizeof(template)-1)) { // -1 to strip terminating 0 + return false; + } + + ELEMENTS_MUST_BE_EQUAL(parsedElements, 0, 3); + ELEMENTS_MUST_BE_EQUAL(parsedElements, 0, 4); + ELEMENTS_MUST_BE_EQUAL(parsedElements, 2, 5); + + parsedElements->script_type = SCRIPT_TYPE_NFT_TRANSFER; + return true; +} + +#undef ELEMENTS_MUST_BE_EQUAL \ No newline at end of file diff --git a/app/src/script_parser.h b/app/src/script_parser.h new file mode 100644 index 00000000..9a9db20e --- /dev/null +++ b/app/src/script_parser.h @@ -0,0 +1,39 @@ +#include +#include + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_SCRIPT_PARSED_ELEMENTS 9 + +typedef enum { + SCRIPT_TYPE_UNKNOWN = 0x00, + SCRIPT_TYPE_NFT_SETUP_COLLECTION = 0x01, + SCRIPT_TYPE_NFT_TRANSFER = 0x02, +} script_parsed_type_t; + +typedef struct { + const NV_VOLATILE uint8_t *data; + size_t length; +} script_element_t; + +typedef struct { + script_parsed_type_t script_type; + size_t elements_count; + script_element_t elements[MAX_SCRIPT_PARSED_ELEMENTS]; +} script_parsed_elements_t; + +bool parseScript(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLATILE *scriptToParse, size_t scriptToParseSize, + const uint8_t *scriptTemplate, size_t scriptTemplateSize); + +bool parseNFT1(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLATILE *scriptToParse, size_t scriptToParseSize); + +bool parseNFT2(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLATILE *scriptToParse, size_t scriptToParseSize); + + +#ifdef __cplusplus +} //end extern C +#endif diff --git a/app/src/tx_metadata.h b/app/src/tx_metadata.h index 3a1351c6..a2e366c0 100644 --- a/app/src/tx_metadata.h +++ b/app/src/tx_metadata.h @@ -32,7 +32,7 @@ typedef enum { ARGUMENT_TYPE_OPTIONALARRAY = 4 } argument_type_e; -#define PARSER_MAX_ARGCOUNT 10 +#define PARSER_MAX_ARGCOUNT 9 #define METADATA_HASH_SIZE 32 //CX_SHA256_SIZE #define MAX_METADATA_MAX_ARRAY_ITEMS 20 diff --git a/deps/ledger-zxlib/dockerized_build.mk b/deps/ledger-zxlib/dockerized_build.mk index d3f9e939..3800d947 100644 --- a/deps/ledger-zxlib/dockerized_build.mk +++ b/deps/ledger-zxlib/dockerized_build.mk @@ -346,6 +346,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-transaction-nft.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 413315d8..411fba05 100644 --- a/docs/APDUSPEC.md +++ b/docs/APDUSPEC.md @@ -151,29 +151,35 @@ Each slot has the following structure #### Command -| Field | Type | Content | Expected | -| ----- | -------- | ---------------------- | ------------- | -| CLA | byte (1) | Application Identifier | 0x33 | -| INS | byte (1) | Instruction ID | 0x02 | -| P1 | byte (1) | Payload desc | 0 = init | -| | | | 1 = add | -| | | | 3 = template | -| | | | 4,5 = hashes | -| P2 | byte (1) | ---- | not used | -| L | byte (1) | Bytes in payload | (depends) | +| Field | Type | Content | Expected | +| ----- | -------- | ---------------------- | ------------------ | +| CLA | byte (1) | Application Identifier | 0x33 | +| INS | byte (1) | Instruction ID | 0x02 | +| P1 | byte (1) | Payload desc | 0 = init | +| | | | 1 = add | +| | | | 2 = final | +| | | | 3 = metadata | +| | | | 4 = MT proof | +| | | | 5 = MT proof final | +| | | | 10 = message final | +| P2 | byte (1) | ---- | (depends) | +| L | byte (1) | Bytes in payload | (depends) | The first packet/chunk includes only the derivation path -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): +All other packets/chunks contain data chunks that are described below. There are following 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 packet, several add packets, final packet. +NFT workflow - Init packet, several add packets, final packet. Message signing workflow - Init packet, several add packets, final message packet (P1=0x10). ##### Init Packet P1 = 0x00 | Field | Type | Content | Expected | | ------- | -------- | -------------------- | -------- | +| P2 | byte (1) | | not used | +| ------- | -------- | -------------------- | -------- | | Path[0] | byte (4) | Derivation Path Data | 44' | | Path[1] | byte (4) | Derivation Path Data | 539' | | Path[2] | byte (4) | Derivation Path Data | ? | @@ -185,9 +191,11 @@ This clears data and sets detivation path and crypto options variable ##### Add Packet P1 = 0x01 -| Field | Type | Content | Expected | -| ----- | -------- | ------- | -------- | -| Data | bytes... | Message | | +| Field | Type | Content | Expected | +| ----- | -------- | ---------- | -------- | +| P2 | byte (1) | | not used | +| ----- | -------- | ---------- | -------- | +| Data | bytes... | see bellow | | Data is defined as: @@ -197,11 +205,16 @@ Data is defined as: Appends to data (transaction or message) -##### Fimal Packet P1 = 0x02 +##### Final Packet P1 = 0x02 -| Field | Type | Content | Expected | -| ----- | -------- | ------- | -------- | -| Data | bytes... | Message | | +| Field | Type | Content | Expected | +| ----- | -------- | ---------- | ---------- | +| P2 | byte (1) | workflow | 1, 2, or 3 | +| ----- | -------- | ---------- | ---------- | +| Data | bytes... | see bellow | | + +Workflow is defined as: 1 - Arbitrary message signing, 2 - Setup NFT collection, 3 - Transfer NFT +Arbitrary message signing requires expert mode and is able to handle any transaction. Setup NFT collection and Transfer NFT require the transaction to contain script (and arguments) conforming to NFT script templates (see script_parser.c). Data is defined as: @@ -215,6 +228,8 @@ Appends to transaction data and initiates transaction signing without metadata ( | Field | Type | Content | Expected | | ----- | -------- | -------- | -------- | +| P2 | byte (1) | | not used | +| ----- | -------- | -------- | -------- | | Data | bytes... | Metadata | | Metadata is defined as: @@ -265,6 +280,8 @@ Four APDUs for four levels of internal merkle tree nodes. Each internal nerkle t | Field | Type | Content | Expected | | ------------------- | ------------ | ---------------- | -------- | +| P2 | byte (1) | | not used | +| ------------------- | ------------ | ---------------- | -------- | | Merkle tree hash 1 | byte (32) | Merkle tree hash | | | Merkle tree hash 2 | byte (32) | Merkle tree hash | | | ... | | | | @@ -276,6 +293,8 @@ Validates merkle tree node. Validates that previous hash (metadata hash or merkl | Field | Type | Content | Expected | | ----- | -------- | ------- | -------- | +| P2 | byte (1) | | not used | +| ----- | -------- | ------- | -------- | | Data | bytes... | Message | | Data is defined as: diff --git a/fuzz/parser_parse.cpp b/fuzz/parser_parse.cpp index 0acb3ab3..db6eb660 100644 --- a/fuzz/parser_parse.cpp +++ b/fuzz/parser_parse.cpp @@ -22,7 +22,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) parser_context_t ctx; parser_error_t rc; - rc = parser_parse(&ctx, data, size); + rc = parser_parse(&ctx, data, size, SCRIPT_TYPE_UNKNOWN); if (rc != PARSER_OK) { return 0; } diff --git a/js/src/index.js b/js/src/index.js index 5d3764a8..7ee6926a 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -177,11 +177,12 @@ export default class FlowApp { for (let i = 0; i < chunks.length; i += 1) { const payloadType = chunks[i].type + const p2 = chunks[i].p2 const chunk = chunks[i].buffer // eslint-disable-next-line no-await-in-loop result = await this.transport - .send(CLA, INS.SIGN, payloadType, 0, chunk, [0x9000, 0x6984, 0x6a80]) + .send(CLA, INS.SIGN, payloadType, p2, chunk, [0x9000, 0x6984, 0x6a80]) .then((response) => { const errorCodeData = response.slice(-2); const returnCode = errorCodeData[0] * 256 + errorCodeData[1]; diff --git a/js/src/signTransaction.js b/js/src/signTransaction.js index 1fdd3e8a..0c870a87 100644 --- a/js/src/signTransaction.js +++ b/js/src/signTransaction.js @@ -12,6 +12,14 @@ const PAYLOAD_TYPE = { MESSAGE_LAST: 0x10, } +const P2_UNUSED = 0x00; + +const PAYLOAD_TYPE_LAST_P2 = { + ARBITRARY_MESSAGE_SIGNING: 0x01, + NFT_SETUP_COLLECTION: 0x02, + NFT_TRANSFER: 0x03, +} + export function signIsLastAPDU(type) { return (type === PAYLOAD_TYPE.LAST || type === PAYLOAD_TYPE.MERKLE_TREE_LAST || PAYLOAD_TYPE.MESSAGE_LAST) } @@ -23,7 +31,7 @@ function prepareBasicChunks(serializedPathBuffer, message) { const chunks = []; // First chunk (only path) - chunks.push({type: PAYLOAD_TYPE.INIT, buffer: serializedPathBuffer}); + chunks.push({type: PAYLOAD_TYPE.INIT, p2: P2_UNUSED, buffer: serializedPathBuffer}); const messageBuffer = Buffer.from(message); @@ -33,7 +41,7 @@ function prepareBasicChunks(serializedPathBuffer, message) { if (i > buffer.length) { end = buffer.length; } - chunks.push({type: PAYLOAD_TYPE.ADD, buffer:buffer.slice(i, end)}); + chunks.push({type: PAYLOAD_TYPE.ADD, p2: P2_UNUSED, buffer:buffer.slice(i, end)}); } return chunks; @@ -49,6 +57,8 @@ function signGetChunksv1(path, options, getVersionResponse, message) { //ExtraInfo is either // - script hash from merkleIndex - initiates transaction signing with metadata // - "Sign message" - initiates message signing +// - "nft1" - initiates Setup NFT collection signing +// - "nft2" - initiates Transfer NFT signing // - anything else - initiates transaction sining without metadata function signGetChunksv2(path, options, getVersionResponse, message, extraInfo) { const serializedPath = serializePath(path, getVersionResponse, options); @@ -56,6 +66,19 @@ function signGetChunksv2(path, options, getVersionResponse, message, extraInfo) if (extraInfo == "Sign message") { basicChunks[basicChunks.length-1].type = PAYLOAD_TYPE.MESSAGE_LAST + basicChunks[basicChunks.length-1].p2 = P2_UNUSED + return basicChunks; + } + + if (extraInfo == "nft1") { + basicChunks[basicChunks.length-1].type = PAYLOAD_TYPE.LAST + basicChunks[basicChunks.length-1].p2 = PAYLOAD_TYPE_LAST_P2.NFT_SETUP_COLLECTION + return basicChunks; + } + + if (extraInfo == "nft2") { + basicChunks[basicChunks.length-1].type = PAYLOAD_TYPE.LAST + basicChunks[basicChunks.length-1].p2 = PAYLOAD_TYPE_LAST_P2.NFT_TRANSFER return basicChunks; } @@ -64,6 +87,7 @@ function signGetChunksv2(path, options, getVersionResponse, message, extraInfo) const merkleI = merkleIndex[scriptHash.slice(0, 16)] if (merkleI === undefined) { basicChunks[basicChunks.length-1].type = PAYLOAD_TYPE.LAST + basicChunks[basicChunks.length-1].p2 = PAYLOAD_TYPE_LAST_P2.ARBITRARY_MESSAGE_SIGNING return basicChunks; } // other chunks @@ -75,11 +99,11 @@ function signGetChunksv2(path, options, getVersionResponse, message, extraInfo) return [ ...basicChunks, - { type: PAYLOAD_TYPE.TX_METADATA, buffer: Buffer.from(metadata, "hex"), }, - { type: PAYLOAD_TYPE.MERKLE_TREE, buffer: Buffer.from(merkleTreeLevel1, "hex"), }, - { type: PAYLOAD_TYPE.MERKLE_TREE, buffer: Buffer.from(merkleTreeLevel2, "hex"), }, - { type: PAYLOAD_TYPE.MERKLE_TREE, buffer: Buffer.from(merkleTreeLevel3, "hex"), }, - { type: PAYLOAD_TYPE.MERKLE_TREE_LAST, buffer: Buffer.from(merkleTreeLevel4, "hex"), }, + { type: PAYLOAD_TYPE.TX_METADATA, p2: P2_UNUSED, buffer: Buffer.from(metadata, "hex"), }, + { type: PAYLOAD_TYPE.MERKLE_TREE, p2: P2_UNUSED, buffer: Buffer.from(merkleTreeLevel1, "hex"), }, + { type: PAYLOAD_TYPE.MERKLE_TREE, p2: P2_UNUSED, buffer: Buffer.from(merkleTreeLevel2, "hex"), }, + { type: PAYLOAD_TYPE.MERKLE_TREE, p2: P2_UNUSED, buffer: Buffer.from(merkleTreeLevel3, "hex"), }, + { type: PAYLOAD_TYPE.MERKLE_TREE_LAST, p2: P2_UNUSED, buffer: Buffer.from(merkleTreeLevel4, "hex"), }, ] } diff --git a/tests/script_parser.cpp b/tests/script_parser.cpp new file mode 100644 index 00000000..c30bfee2 --- /dev/null +++ b/tests/script_parser.cpp @@ -0,0 +1,294 @@ +/******************************************************************************* +* (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 "gmock/gmock.h" +#include +#include +#include +#include +#include +#include +#include +#include + +testing::AssertionResult PARSE_TEST(std::string script, std::string template_, bool expectedResult, + std::vector expectedValues = {}) { + script_parsed_elements_t parsed; + bool result = parseScript(&parsed, (const uint8_t *) script.data(), script.size(), + (const uint8_t *) template_.data(), template_.size()); + if (result != expectedResult) { + return testing::AssertionFailure() << "Result: " << result << ", Expected: " << expectedResult; + } + if (result) { + if (parsed.elements_count != expectedValues.size()) { + return testing::AssertionFailure() << "Parsing error, Size: " << parsed.elements_count << ", Expected: " << expectedValues.size(); + } + + for(size_t i=0; i +testing::AssertionResult PARSE_NFT_TEST(NFTFunction parseNFTFunction, std::string script, bool expectedResult, + std::vector expectedValues = {}, + script_parsed_type_t expectedScriptType = SCRIPT_TYPE_UNKNOWN) { + script_parsed_elements_t parsed; + bool result = parseNFTFunction(&parsed, (const uint8_t *) script.data(), script.size()); + if (result != expectedResult) { + return testing::AssertionFailure() << "Result: " << result << ", Expected: " << expectedResult; + } + if (result) { + if (parsed.script_type != expectedScriptType) { + return testing::AssertionFailure() << "Parsing error, Script type: " << parsed.script_type << ", Expected: " << expectedScriptType; + } + + if (parsed.elements_count != expectedValues.size()) { + return testing::AssertionFailure() << "Parsing error, Size: " << parsed.elements_count << ", Expected: " << expectedValues.size(); + } + + for(size_t i=0; i(\n" + " /public/zzzzzzZ,\n" + " target: /storage/c\n" + " )\n" + " }\n" + "}\n"; + + EXPECT_TRUE(PARSE_NFT_TEST(parseNFT1, script1, true, {"aaa", "bbb", "c", "aaa", "c", "x", "_", "zzzzzzZ", "c"}, + SCRIPT_TYPE_NFT_SETUP_COLLECTION)); + + const char script2[] = + "import NonFungibleToken from 0xNONFUNGIBLETOKEN\n" + "import MetadataViews from 0xMETADATAVIEWS\n" + "import aaa from bbb\n" + "transaction {\n" + " prepare(acct: AuthAccount) {\n" + " let collectionType = acct.type(at: /storage/c)\n" + " // if there already is a collection stored, return\n" + " if (collectionType != nil) {\n" + " return\n" + " }\n" + " // create empty collection\n" + " let collection <- aaa.createEmptyCollection()\n" + " // put the new Collection in storage\n" + " acct.save(<-collection, to: /storage/cc)\n" + " // create a public capability for the collection\n" + " acct.link<&{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, x._, MetadataViews.ResolverCollection}>(\n" + " /public/zzzzzzZ,\n" + " target: /storage/c\n" + " )\n" + " }\n" + "}\n"; + + EXPECT_TRUE(PARSE_NFT_TEST(parseNFT1, script2, false)); //storages do not match + + const char script3[] = + "import NonFungibleToken from 0xNONFUNGIBLETOKEN\n" + "import MetadataViews from 0xMETADATAVIEWS\n" + "import aaaa from bbb\n" + "transaction {\n" + " prepare(acct: AuthAccount) {\n" + " let collectionType = acct.type(at: /storage/c)\n" + " // if there already is a collection stored, return\n" + " if (collectionType != nil) {\n" + " return\n" + " }\n" + " // create empty collection\n" + " let collection <- aaa.createEmptyCollection()\n" + " // put the new Collection in storage\n" + " acct.save(<-collection, to: /storage/c)\n" + " // create a public capability for the collection\n" + " acct.link<&{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, x._, MetadataViews.ResolverCollection}>(\n" + " /public/zzzzzzZ,\n" + " target: /storage/c\n" + " )\n" + " }\n" + "}\n"; + + EXPECT_TRUE(PARSE_NFT_TEST(parseNFT1, script3, false)); //contract names do not match +} + +TEST(script_parse, parseNFT2) { + EXPECT_TRUE(PARSE_NFT_TEST(parseNFT2, "", false)); + + const char script1[] = + "import NonFungibleToken from 0xNONFUNGIBLETOKEN\n" + "import aaaa from bbb\n" + "transaction(recipient: Address, withdrawID: UInt64) {\n" + " // local variable for storing the transferred nft\n" + " let transferToken: @NonFungibleToken.NFT\n" + " prepare(owner: AuthAccount) {\n" + " // check if collection exists\n" + " if (owner.type(at: /storage/ststst) != Type<@aaaa.Collection>()) {\n" + " panic(\"Could not borrow a reference to the stored collection\")\n" + " }\n" + " // borrow a reference to the collection\n" + " let collectionRef = owner\n" + " .borrow<&aaaa.Collection>(from: /storage/ststst)!\n" + " // withdraw the NFT\n" + " self.transferToken <- collectionRef.withdraw(withdrawID: withdrawID)\n" + " }\n" + " execute {\n" + " // get the recipient's public account object\n" + " let recipient = getAccount(recipient)\n" + " // get receivers capability\n" + " let nonFungibleTokenCapability = recipient\n" + " .getCapability<&{NonFungibleToken.CollectionPublic}>(/public/publicPath)\n" + " // check the recipient has a NonFungibleToken public capability\n" + " if (!nonFungibleTokenCapability.check()) {\n" + " panic(\"Could not borrow a reference to the receiver's collection\")\n" + " }\n" + " // deposit nft to recipients collection\n" + " nonFungibleTokenCapability\n" + " .borrow()!\n" + " .deposit(token: <-self.transferToken)\n" + " }\n" + "}\n"; + + EXPECT_TRUE(PARSE_NFT_TEST(parseNFT2, script1, true, {"aaaa", "bbb", "ststst", "aaaa", "aaaa", "ststst", "publicPath"}, + SCRIPT_TYPE_NFT_TRANSFER)); + + const char script2[] = + "import NonFungibleToken from 0xNONFUNGIBLETOKEN\n" + "import aaaa from bbb\n" + "transaction(recipient: Address, withdrawID: UInt64) {\n" + " // local variable for storing the transferred nft\n" + " let transferToken: @NonFungibleToken.NFT\n" + " prepare(owner: AuthAccount) {\n" + " // check if collection exists\n" + " if (owner.type(at: /storage/ststst) != Type<@aaaa.Collection>()) {\n" + " panic(\"Could not borrow a reference to the stored collection\")\n" + " }\n" + " // borrow a reference to the collection\n" + " let collectionRef = owner\n" + " .borrow<&aaaaa.Collection>(from: /storage/ststst)!\n" + " // withdraw the NFT\n" + " self.transferToken <- collectionRef.withdraw(withdrawID: withdrawID)\n" + " }\n" + " execute {\n" + " // get the recipient's public account object\n" + " let recipient = getAccount(recipient)\n" + " // get receivers capability\n" + " let nonFungibleTokenCapability = recipient\n" + " .getCapability<&{NonFungibleToken.CollectionPublic}>(/public/publicPath)\n" + " // check the recipient has a NonFungibleToken public capability\n" + " if (!nonFungibleTokenCapability.check()) {\n" + " panic(\"Could not borrow a reference to the receiver's collection\")\n" + " }\n" + " // deposit nft to recipients collection\n" + " nonFungibleTokenCapability\n" + " .borrow()!\n" + " .deposit(token: <-self.transferToken)\n" + " }\n" + "}\n"; + + EXPECT_TRUE(PARSE_NFT_TEST(parseNFT2, script2, false)); //contractName mismatch + + const char script3[] = + "import NonFungibleToken from 0xNONFUNGIBLETOKEN\n" + "import aaaa from bbb\n" + "transaction(recipient: Address, withdrawID: UInt64) {\n" + " // local variable for storing the transferred nft\n" + " let transferToken: @NonFungibleToken.NFT\n" + " prepare(owner: AuthAccount) {\n" + " // check if collection exists\n" + " if (owner.type(at: /storage/stst) != Type<@aaaa.Collection>()) {\n" + " panic(\"Could not borrow a reference to the stored collection\")\n" + " }\n" + " // borrow a reference to the collection\n" + " let collectionRef = owner\n" + " .borrow<&aaaa.Collection>(from: /storage/ststst)!\n" + " // withdraw the NFT\n" + " self.transferToken <- collectionRef.withdraw(withdrawID: withdrawID)\n" + " }\n" + " execute {\n" + " // get the recipient's public account object\n" + " let recipient = getAccount(recipient)\n" + " // get receivers capability\n" + " let nonFungibleTokenCapability = recipient\n" + " .getCapability<&{NonFungibleToken.CollectionPublic}>(/public/publicPath)\n" + " // check the recipient has a NonFungibleToken public capability\n" + " if (!nonFungibleTokenCapability.check()) {\n" + " panic(\"Could not borrow a reference to the receiver's collection\")\n" + " }\n" + " // deposit nft to recipients collection\n" + " nonFungibleTokenCapability\n" + " .borrow()!\n" + " .deposit(token: <-self.transferToken)\n" + " }\n" + "}\n"; + + EXPECT_TRUE(PARSE_NFT_TEST(parseNFT2, script3, false)); // storage mismatch +} \ No newline at end of file diff --git a/tests/testvectors.cpp b/tests/testvectors.cpp index aab959ac..b002b781 100644 --- a/tests/testvectors.cpp +++ b/tests/testvectors.cpp @@ -51,7 +51,7 @@ void check_testcase(const testcase_t &testcase) { sha256((const uint8_t *) tc.script.c_str(), tc.script.length(), scriptHash); - err = parser_parse(&ctx, tc.blob.data(), tc.blob.size()); + err = parser_parse(&ctx, tc.blob.data(), tc.blob.size(), SCRIPT_TYPE_UNKNOWN); if (tc.valid) { ASSERT_EQ(err, PARSER_OK) << parser_getErrorDescription(err); } else { @@ -155,7 +155,6 @@ INSTANTIATE_TEST_SUITE_P( ::testing::ValuesIn(GetJsonTestCases("testvectors/manifestPayloadCases.json")), VerifyTestVectors::PrintToStringParamName() ); - TEST_P(VerifyTestVectors, CheckUIOutput_Manual) { check_testcase(GetParam()); } #pragma clang diagnostic pop diff --git a/tests/tx_metadata.cpp b/tests/tx_metadata.cpp index 128dc247..a0a31c3d 100644 --- a/tests/tx_metadata.cpp +++ b/tests/tx_metadata.cpp @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2020 Zondax GmbH +* (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. @@ -111,7 +111,7 @@ TEST(tx_data, validateScriptHash) { } -TEST(tx_data, parseCompressedTxData) { +TEST(tx_metadata, parseCompressedTxData) { parser_error_t err; parsed_tx_metadata_t result; err = _parseTxMetadata(hashTokenTranfer1, TX_METADATA_ADD_NEW_KEY, sizeof(TX_METADATA_ADD_NEW_KEY), &result); diff --git a/tests_speculos/package.json b/tests_speculos/package.json index 1f3bc6d5..d54f2444 100644 --- a/tests_speculos/package.json +++ b/tests_speculos/package.json @@ -8,6 +8,8 @@ "dependencies": { "@ledgerhq/hw-transport-node-speculos": "^6.20.0", "@onflow/ledger": "file:../js", + "@onflow/encode": "^0.0.8", + "deepmerge": "^4.2.2", "assert": "^2.0.0", "strict": "^0.0.0" }, diff --git a/tests_speculos/test-transaction-nft.js b/tests_speculos/test-transaction-nft.js new file mode 100644 index 00000000..9d6e752a --- /dev/null +++ b/tests_speculos/test-transaction-nft.js @@ -0,0 +1,239 @@ +'use strict'; + +import merge from "deepmerge"; +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 { encodeTransactionPayload, encodeTransactionEnvelope } from "@onflow/encode"; +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 + +const EMULATOR = "Emulator"; +const TESTNET = "Testnet"; +const MAINNET = "Mainnet"; + +const ADDRESS_EMULATOR = "ed2d4f9eb8bcd4ac"; +const ADDRESS_TESTNET = "99a8ac2c71d4f6bd"; +const ADDRESS_MAINNET = "f19c161bc24cf4b4"; + +const ADDRESSES = { + [EMULATOR]: ADDRESS_EMULATOR, + [TESTNET]: ADDRESS_TESTNET, + [MAINNET]: ADDRESS_MAINNET, +}; + +const basePayloadTx = (network) => { + const address = ADDRESSES[network]; + + return { + script: "", + arguments: [], + refBlock: "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b", + gasLimit: 42, + proposalKey: { + address: address, + keyId: 4, + sequenceNum: 10, + }, + payer: address, + authorizers: [address], + }; +}; + +const combineMerge = (target, source, options) => { + // empty list always overwrites target + if (source.length == 0) return source + + const destination = target.slice() + + source.forEach((item, index) => { + if (typeof destination[index] === "undefined") { + destination[index] = options.cloneUnlessOtherwiseSpecified(item, options) + } else if (options.isMergeableObject(item)) { + destination[index] = merge(target[index], item, options) + } else if (target.indexOf(item) === -1) { + destination.push(item) + } + }) + + return destination +}; + +const buildPayloadTx = (network, partialTx) => + merge(basePayloadTx(network), partialTx, {arrayMerge: combineMerge}); + + +const getTxEnvelope = (script, args) => { + const tx = buildPayloadTx(MAINNET, { + script: script, + arguments: args, + }) + return encodeTransactionEnvelope({...tx, payloadSigs: []}); +} + +//--------------------------TESTS----------------------------- + +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 contractName = "contractName" + const contractAddress = "contractAddress" + const storagePath = "storagePath" + const publicCollectionContractName = "publicCollectionContractName" + const publicCollectionName = "publicCollectionName" + const publicPath = "publicPath" + const script = +`import NonFungibleToken from 0xNONFUNGIBLETOKEN +import MetadataViews from 0xMETADATAVIEWS +import ${contractName} from ${contractAddress} +transaction { + prepare(acct: AuthAccount) { + let collectionType = acct.type(at: /storage/${storagePath}) + // if there already is a collection stored, return + if (collectionType != nil) { + return + } + // create empty collection + let collection <- ${contractName}.createEmptyCollection() + // put the new Collection in storage + acct.save(<-collection, to: /storage/${storagePath}) + // create a public capability for the collection + acct.link<&{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, ${publicCollectionContractName}.${publicCollectionName}, MetadataViews.ResolverCollection}>( + /public/${publicPath}, + target: /storage/${storagePath} + ) + } +} +` + const args = [] + const txBlob = Buffer.from(getTxEnvelope(script, args), "hex") + console.log("Script") + console.log(script) + console.log("Tx built") + console.log(txBlob.toString("hex")) + + testStep(" - - -", "NFT 1 correct"); + const signPromise = app.sign(path, txBlob, options, "nft1"); + await device.review("Review transaction"); + 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-transaction"); + const hasher = new jsSHA(SHA3_256.name, "UINT8ARRAY"); + hasher.update(tag); + hasher.update(txBlob); + const digestHex = hasher.getHash("HEX"); + const ec = new EC(ECDSA_P256.name); + assert.ok(ec.verify(digestHex, signResponse.signatureDER.toString("hex"), pubkeyHex, 'hex')); +} + +{ + const contractName = "contractName" + const contractAddress = "contractAddress" + const storagePath = "storagePath" + const publicPath = "publicPath" + const script = +`import NonFungibleToken from 0xNONFUNGIBLETOKEN +import ${contractName} from ${contractAddress} +transaction(recipient: Address, withdrawID: UInt64) { + // local variable for storing the transferred nft + let transferToken: @NonFungibleToken.NFT + prepare(owner: AuthAccount) { + // check if collection exists + if (owner.type(at: /storage/${storagePath}) != Type<@${contractName}.Collection>()) { + panic("Could not borrow a reference to the stored collection") + } + // borrow a reference to the collection + let collectionRef = owner + .borrow<&${contractName}.Collection>(from: /storage/${storagePath})! + // withdraw the NFT + self.transferToken <- collectionRef.withdraw(withdrawID: withdrawID) + } + execute { + // get the recipient's public account object + let recipient = getAccount(recipient) + // get receivers capability + let nonFungibleTokenCapability = recipient + .getCapability<&{NonFungibleToken.CollectionPublic}>(/public/${publicPath}) + // check the recipient has a NonFungibleToken public capability + if (!nonFungibleTokenCapability.check()) { + panic("Could not borrow a reference to the receiver's collection") + } + // deposit nft to recipients collection + nonFungibleTokenCapability + .borrow()! + .deposit(token: <-self.transferToken) + } +} +` + const args = [ + { + "type": "Address", + "value": "Ny nice address" + }, + { + "type": "UInt64", + "value": "123456" + }, + ] + const txBlob = Buffer.from(getTxEnvelope(script, args), "hex") + console.log("Script") + console.log(script) + console.log("Tx built") + console.log(txBlob.toString("hex")) + + testStep(" - - -", "NFT 2 correct"); + const signPromise = app.sign(path, txBlob, options, "nft2"); + await device.review("Review transaction"); + 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-transaction"); + const hasher = new jsSHA(SHA3_256.name, "UINT8ARRAY"); + hasher.update(tag); + hasher.update(txBlob); + const digestHex = hasher.getHash("HEX"); + const ec = new EC(ECDSA_P256.name); + assert.ok(ec.verify(digestHex, signResponse.signatureDER.toString("hex"), pubkeyHex, 'hex')); +} + + +await transport.close() +testEnd(scriptName); +process.stdin.pause() diff --git a/tests_speculos/test-transaction-nft/nanos.01.png b/tests_speculos/test-transaction-nft/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;4uZjsrg+Lrl-{A1w%ge9$#jTvn@ULfE?ssL+i&*A#-? zPzr2Ds_DDr@ye}6{g3o-GcquI z(7PtWG4uZ42?sMg1=}XeJ$~PAa`>NXVbg>Kdae<{3U0wGjxz?eM6x!0c*c2QsUj;- zdEfh-hTus`X_^8|-{cZwdo%=$*3WHt<2YNxH6lO(7-Dd6`6u)4zbv;De(P9+#5`U7 KT-G@yGywqYCYOl- literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.04.png b/tests_speculos/test-transaction-nft/nanos.04.png new file mode 100644 index 0000000000000000000000000000000000000000..a5231df298377749b76ae510ed58c515b22bf565 GIT binary patch literal 394 zcmV;50d@X~P)>oqNVb1p*;FVy|;C`{o4f+xb|a;s#rFr`xr$eu=&q zv2DrFm)C2xe2|e2x16QZD(sOp48w%lcwk=`2Wwgrle}@}`f1ptGw#Iaq1S%=>=G;W zEgTny-~EMbS8Vi4?MvMXoEdeXlf!&=F57VykR_F{vs7l3JD{}&(Fr)NAm&^=4LRwZ zDDET@jo?pMW9KA6OHJlv0C66+!?b>%>-s18X=wV{f-On4z;EvX$w(xlHhp57k%{Gf z5<@b#aHiQ9H3L@~uJqc8sXGP$0R9TgB1D0?-Q;St)Ciw)4a9VarQM{T=6AyVYJ@2f z;#w;I67NwX-XcPz7`TE;45<-s5g{^EuOM}X)Cl_`1c%`hFp6;zqJZ8BASv`tHG7n2 z8g9pN)*pBlIjCwOH_FV0-LiKs+b49yW2!@Y1d@f0 zsotQg{&95c4YKN^6rs$d?LYzeZ2mv$RpoVTI zEP$P`wqtD#u={s>0c#!bIZUM>r}s9)U@&0e2UnL=y^~g^Gynhq07*qoM6N<$g440h A8vp_xRD5C|^`Qz`ejP=+MTB;s7i>*++io}W*(B$J5f1$7}ZlPqbg zEpWONc7#>He$i<}#yFA54*+g?b7FP=lX`nJQ*eq(z8_FUNU^MWj7*-}Bh%!tYmTfW zg{o5+5Uck8Qd;#sulRsbC$gk8(rRe8x}4eJBBSF3Dtg!T$!{_0IXE5KYU3s>suit( ztTiB)>vtaMj8-&1?&o_XYw&R-qK9qcn@nc8kFpz{!%QNhC411F=}A_pFx3lcELkqK zmanqeY&JiKIuy|`0wFaIsRc%2%J_Pb0Wf|UlWQbAoOQQJUBwvN0Pr3o{hpKz<&=t9 zA`ndEApDv5Kzyy_ZZ?_h1x}Gz+CZuyJVX>C++w_qHMXEmtn635`RW0fzeUu!*Ne<$ z=tAjDrkQbKqC%q_(*sEY6c)9(>OkC;kFj81%3R5eb(EOTSPLu5Rt0%npY ztk_Z}K4R#}yikPhMU7F&dclcH`2mZ++fboe0ixFcr9Uo`u0-Mlj*Nk5JhCeIqg5SK i*~pl;ur9R)eItKat+q1I>5r@c0000Vw6ouMP$1dxk2n|edO8!Y=l3}+sU#x0pcdjY%d*zG z1x>3Wj@SfjmpVnHk29Zo2jG!+CzbQx)Mf1^u)E6M15yYnRt!(^sk5wwD#uL|va%E@ zR}nxe?KdyIT3;6qC~~Gqj(WNp*{rSsKY>CYTc3TKsUCyFk+p7IgiTh-3@Ane z@^kI3V;#{I)lb{`k@&iKJ2G)aZ1-1LjB=l3cie}SL`9{z(e3G3)=^=p3o0H(ZjDwq zA%u`Wr{lE&VqR@B!J*@EUc^eM)v|8S70v^VKQ43N&x;_)^T}x$(3LwFw%L!1ZY2*? zLoOk%0-4vvqn_yn@dG9*OCRX7aq*1kGy^(v!xAy@5<)Ov2aKB0H(WxLxL^}@(`|GG zrnz_NF$1zas^!{17LzIaCahRl!*<3&Q>RD>A%qY@2qApZX52yL?hukPmx}Rz)$RiluDA z=k~6DHuk7AXlgx)rp1i+hz7ttJd0{edJs*mbQ~x1_Szf(%_^R9#ZaY~A-w`dLZz57 zmrX!V+EzfC6SDi5NTngQ?59PTnHMuCG>^<5IG(dW3L1$~W+mV;M7)uLjG3$*Xn;5% t0S!$4)guj3kTD2=F$jP$2mk=UkRP0A3fKQGc|-sJ002ovPDHLkV1g9!t9JkZ literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.09.png b/tests_speculos/test-transaction-nft/nanos.09.png new file mode 100644 index 0000000000000000000000000000000000000000..2ab1484075defe1e2767c45b891fe0ae0287540c GIT binary patch literal 329 zcmV-P0k-~$P)Qi3buy{@WDSA}D(rPjB|h)8y;2aL_u0nU4oM+X4$og* z69d9oc~+}VV0-D3wXhdxA!6kb8;nltlRDfrH`3_Q7Q*#daydM{mF#D4JoZV$Ilj~Y zbm_V51j!G8v+J)(+a^IPH5lq~FO6}48iP!v@W-noTy9VY z1~8=#ASvQI^OwUAc0hfl^29*=ho?*HfVXhHoXj%2Fd_>J3kwrLQxTx(s5>vY1dBGn zrN4D|W0?p+o$R$(J>{n%F9DzdKyhzLmOVJdW|ViN5ZV|ERRhGxG?eEORF)et7bwH@dRdAfg@EU& jYKxfbl1eNr{3HAV_0#cv`)$N600000NkvXXu0mjfy1gh5 literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.11.png b/tests_speculos/test-transaction-nft/nanos.11.png new file mode 100644 index 0000000000000000000000000000000000000000..5ebc566538c13f46cb76bcb35bd4c7f3819742a1 GIT binary patch literal 648 zcmV;30(bq1P) zRs{x(Bl-5Ye9qm)Q_Hv(C=ro)Pah1Y^+Iw%XG4f(A*)(i#ugh68xvX# zR!aynV7C)Ly}0I&7*PQm_gDfO^4us&7g!wu>ID%1_YMGyF0b~$d{mX^#Uc`ir>Y&r zRWA(YDq5_8w&W^aRwFI>-Gc*8TbQo%ufi+-6;sGCK^4??<-z-mL@p&2hfEcqgk;U| zywN%V;&3e9DlKE-$Xf;@?ao8P<;>xyvwZ@pRA0;38dH*1V32q)7~V%vt^U(<-3O4M5PGDA|A7x3L2Ujv*Cu-p;(p*Q~(f+W6pq z{RaMb3ugxjtlGht`u)g^2rb>U+t_+JZ-!l-k(=s%^zEW2k9u=8ZoU=z%b8Y@c2PcP z_m(nM_ZLc)TfJS>zqh(fb}^nH#rg71&d-$TGo?S)KH;)F>%_^`9<Zn@c>HH%tb|K|aE84l`Ca9{Y>YqLk?M<7Vd)78&qol`;+ E0A^=~jQ{`u literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.13.png b/tests_speculos/test-transaction-nft/nanos.13.png new file mode 100644 index 0000000000000000000000000000000000000000..41b0ce2b951314b34dcb1c5718b6dca8e065af5e GIT binary patch literal 472 zcmV;}0Vn>6P)Nkla)G~QywM^fN;3q|GwUxTS_&2HWWQZ%2Hya&d^U(>U=3@ze~Y&NS$K`S)I8> z2K-N^Q!+QwzD~)Qy=Wu4#-uNN26S9ls!sn&!(v9fZxKPaTw9Q`F4`Ehp-G)Z4Xy2W z)(~jk?t!bx7|S(=(mulN783L$KN6i-tYc4BmFdX(@wNcoY-bt65f^7^uH>}|RS ztiQl!n*acwM3J8i1kKPeRVCxrv$5p!AElCHEmOmc-A{HL6@IzozlQuaevxF&iHuag zhN|4xZkt4H@GpPV_$v~5qd?WBI&eAz9(C$Ok1~^yswFcblj3;)I`w1)_+H&TigE^i zJ+h)F$scR%~2boL?tm1)*x;j8YSetjuZ zOlA**%bnJ+-A22_E?0a%9$wr(+uP&tajX7&)2*N0Yuf42+%s8SpW3UsUGgX8FbdA@w|{p8t;rRnH3X@qE6br60-V}tMSx!&-*6%5%ki@!l&X_$kBS%?&Q7JN z_t(By2~Dfzt5fJPHSQ%~K3YFt8{JKZ{3VVhUp$OZhxBSzT+2?ksTh0vEL67B_>waqJuzMQVV4v`VAsS4G;)2St#_}TU* zn$tg4I z1)#5~t~Zbh$m%sMD@}-DyTYx>0RR910000000000kof}qe>LcCF21+`0000EpLbPNSZ?Rk z{xAwJP%X(2P#5~@c{`szUR5CtDJ|%J%|CvB)#P${@*#i1T{Tp1s`Ki4XbMl}B$~`D z^*UF;=r^*AYNe{JAr zT#I< zS9X7B0cxPwlQCM;moqa)t=$k%o6&(4)bl=bm5>aFR#_ zV_T4j{(qL+1q~oiC}$!Mf9RT_p0){DOG#FhYjgtiRFhG00000000000001h9A8SATkiagEVBRr002ovPDHLkV1it}%}f9Q literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.18.png b/tests_speculos/test-transaction-nft/nanos.18.png new file mode 100644 index 0000000000000000000000000000000000000000..52b06db91d6825dc858bbdf6384330dc370de38e GIT binary patch literal 512 zcmV+b0{{JqP)9$&a>M-t;_CUjSRLmnRm1-JJSnjBBfqT5;Az=j8exhE~(DGeQFQ8IoM};WSmX* zXTS#F^dV?)U#3o=)n0#_vK<|VWpDgtq{GJXYddRSKOHVU$Tr`Az1ZBNmmss-PTl_@ z3gKfMhTlE{uJAZAU&aWzYkkOMG9mQX&a99afuhb`I}IV|DN7>|UPMv<9d)liqUaIMiVeZ|YUd+CY-IF8>~K zzhRaAS`H?-=QM1i=Tu6{1-oD!M<5AJP1 literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.20.png b/tests_speculos/test-transaction-nft/nanos.20.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;)U3ec8u<3c z|NjYMJG7WpmaK4yxwBb|kL5^v+5Kf*3p3u`KR8uiv}fUq`Ll{922Z!$aH}=)etfg6-F0Ac{FYFhd@Or_0cMr;Kn)Lp=JkSepa6m|1?pWa*p){>4ATdu@KbLh* G2~7ZXxN(61 literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.22.png b/tests_speculos/test-transaction-nft/nanos.22.png new file mode 100644 index 0000000000000000000000000000000000000000..1eba76f2bd9fa2851ddea1aa4c7ed4e7ee65cc16 GIT binary patch literal 367 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!2~2j9iA64Fff{Vx;TbZ%y~QGE?=_(Pix`9 z@AV1#cUS`(w{-+8RC|7Ulg^(lOGP63iVc513%RlVwefTvhU||a-)+BKOkP!~XKl!x z_KR(T#Gk9X)@0d51TTNQ*_=<+q;1-Vy=zX2Sl!@coma_UyxHxfX{mc{sajX3`*yu0 zUQ71BmW~WIz3#nd@tYZ^61c=}S-h!u-XgWp?fj{iOwwyKr)7$qDP*lp;(rlqcxhHx z(IuPzLhQGmpBCs^{kkz^r~j@{GfN%Q>Pzr2Ds_DDr@ye}6{g3o-GcquI z(7PtWG4uZ42?sMg1=}XeJ$~PAa`>NXVbg>Kdae<{3U0wGjxz?eM6x!0c*c2QsUj;- zdEfh-hTus`X_^8|-{cZwdo%=$*3WHt<2YNxH6lO(7-Dd6`6u)4zbv;De(P9+#5`U7 KT-G@yGywqYCYOl- literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.23.png b/tests_speculos/test-transaction-nft/nanos.23.png new file mode 100644 index 0000000000000000000000000000000000000000..a5231df298377749b76ae510ed58c515b22bf565 GIT binary patch literal 394 zcmV;50d@X~P)>oqNVb1p*;FVy|;C`{o4f+xb|a;s#rFr`xr$eu=&q zv2DrFm)C2xe2|e2x16QZD(sOp48w%lcwk=`2Wwgrle}@}`f1ptGw#Iaq1S%=>=G;W zEgTny-~EMbS8Vi4?MvMXoEdeXlf!&=F57VykR_F{vs7l3JD{}&(Fr)NAm&^=4LRwZ zDDET@jo?pMW9KA6OHJlv0C66+!?b>%>-s18X=wV{f-On4z;EvX$w(xlHhp57k%{Gf z5<@b#aHiQ9H3L@~uJqc8sXGP$0R9TgB1D0?-Q;St)Ciw)4a9VarQM{T=6AyVYJ@2f z;#w;I67NwX-XcPz7`TE;45<-s5g{^EuOM}X)Cl_`1c%`hFp6;zqJZ8BASv`tHG7n2 z8g9pN)*pBlIjCwOH_FV0-LiKs+b49yW2!@Y1d@f0 zsotQg{&95c4YKN^6rs$d?LYzeZ2mv$RpoVTI zEP$P`wqtD#u={s>0c#!bIZUM>r}s9)U@&0e2UnL=y^~g^Gynhq07*qoM6N<$g440h A8vppZX52yL?hukPmx}Rz)$RiluDA z=k~6DHuk7AXlgx)rp1i+hz7ttJd0{edJs*mbQ~x1_Szf(%_^R9#ZaY~A-w`dLZz57 zmrX!V+EzfC6SDi5NTngQ?59PTnHMuCG>^<5IG(dW3L1$~W+mV;M7)uLjG3$*Xn;5% t0S!$4)guj3kTD2=F$jP$2mk=UkRP0A3fKQGc|-sJ002ovPDHLkV1g9!t9JkZ literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.26.png b/tests_speculos/test-transaction-nft/nanos.26.png new file mode 100644 index 0000000000000000000000000000000000000000..2ab1484075defe1e2767c45b891fe0ae0287540c GIT binary patch literal 329 zcmV-P0k-~$P)1)=JvD68AkUeV)P(3!~3p3l~CpO?BCRFuUJ{ zDA^~Y<2ZTOY812k0c-7j37XXh@!~*wtAU+Gm2+s=!h65pdk2XsZqJgI8jci|uFPlcq}2Yt=~fwbU$no_)5NoJ%$w;7-kVjw5T>O!pO z#Q_wfie3EECM;m%rQ0BoA|6lcf9?PP000000001ZFkb^N_XR=ErVaoA002ovPDHLk FV1jB}v1kAQ literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.28.png b/tests_speculos/test-transaction-nft/nanos.28.png new file mode 100644 index 0000000000000000000000000000000000000000..5064ec2ba3863073372ac327cd43701ddfcc7ccc GIT binary patch literal 340 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!2~2j9iA64FfdAcx;TbZ%y~P*QK(sg$CY{U zcl!<9Web%AI9^65Zu#HK?QN&3BEi;?6enH!Y^!SjnmzW{gzKyCv$wuYG;#j##&o0O z^wn3_?ycis(cc-qarI1{d9!WiHm*LqR_7M;UQH{3|2*%1cPFfhWUURn)?OUrU-ImT z-5=xJcQR(3YLEJtX%-!rTk`s&kISx}KdCcJ*ozO^JyJE4j`S*=!a9A5(az-b9v2A) zhK`OCy=4li`e(Nmhiz1SeQxTncmMh9vr?C(b$?Pw!>+#BI(6zk2J9FT_aZ jDWrJ|0fPkw9&qnrl*!zvvZqK@2_)v}>gTe~DWM4fiN%TF literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.29.png b/tests_speculos/test-transaction-nft/nanos.29.png new file mode 100644 index 0000000000000000000000000000000000000000..05f27c3a1261e43d42b7e462d3f1e8f54f927ee8 GIT binary patch literal 649 zcmV;40(Sk0P)Qi3buy{@WDSA}D(rPjB|h)8y;2aL_u0nU4oM+X4$og* z69d9oc~+}VV0-D3wXhdxA!6kb8;nltlRDfrH`3_Q7Q*#daydM{mF#D4JoZV$Ilj~Y zbm_V51j!G8v+J)(+a^IPH5lq~FO6}48iP!v@W-noTy9VY z1~8=#ASvQI^OwUAc0hfl^29*=ho?*HfVXhHoXj%2Fd_>J3kwrLQxTx(s5>vY1dBGn zrN4D|W0?p+o$R$(J>{n%F9DzdKyhzLmOVJdW|ViN5ZV|ERRhGxG?eEORF)et7bwH@dRdAfg@EU& jYKxfbl1eNr{3HAV_0#cv`)$N600000NkvXXu0mjfy1gh5 literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.30.png b/tests_speculos/test-transaction-nft/nanos.30.png new file mode 100644 index 0000000000000000000000000000000000000000..5ebc566538c13f46cb76bcb35bd4c7f3819742a1 GIT binary patch literal 648 zcmV;30(bq1P) zRs{x(Bl-5Ye9qm)Q_Hv(C=ro)Pah1Y^+Iw%XG4f(A*)(i#ugh68xvX# zR!aynV7C)Ly}0I&7*PQm_gDfO^4us&7g!wu>ID%1_YMGyF0b~$d{mX^#Uc`ir>Y&r zRWA(YDq5_8w&W^aRwFI>-Gc*8TbQo%ufi+-6;sGCK^4??<-z-mL@p&2hfEcqgk;U| zywN%V;&3e9DlKE-$Xf;@?ao8P<;>xyvwZ@pRA0;38dH*1V32q)7~V%vt^U(<-3O4M5PGDA|A7x3L2Ujv*Cu-p;(p*Q~(f+W6pq z{RaMb3ugxjtlGht`u)g^2rb>U+t_+JZ-!l-k(=s%^zEW2k9u=8ZoU=z%b8Y@c2PcP z_m(nM_ZLc)TfJS>zqh(fb}^nH#rg71&d-$TGo?S)KH;)F>%_^`9<Zn@c>HH%tb|K|aE84l`Ca9{Y>YqLk?M<7Vd)78&qol`;+ E0A^=~jQ{`u literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.32.png b/tests_speculos/test-transaction-nft/nanos.32.png new file mode 100644 index 0000000000000000000000000000000000000000..41b0ce2b951314b34dcb1c5718b6dca8e065af5e GIT binary patch literal 472 zcmV;}0Vn>6P)Nkla)G~QywM^fN;3q|GwUxTS_&2HWWQZ%2Hya&d^U(>U=3@ze~Y&NS$K`S)I8> z2K-N^Q!+QwzD~)Qy=Wu4#-uNN26S9ls!sn&!(v9fZxKPaTw9Q`F4`Ehp-G)Z4Xy2W z)(~jk?t!bx7|S(=(mulN783L$KN6i-tYc4BmFdX(@wNcoY-bt65f^7^uH>}|RS ztiQl!n*acwM3J8i1kKPeRVCxrv$5p!AElCHEmOmc-A{HL6@IzozlQuaevxF&iHuag zhN|4xZkt4H@GpPV_$v~5qd?WBI&eAz9(C$Ok1~^yswFcblj3;)I`w1)_+H&TigE^i zJ+h)F$scR%~2boL?tm1)*x;j8YSetjuZ zOlA**%bnJ+-A22_E?0a%9$wr(+uP&tajX7&)2*N0Yuf42+%s8SpW3UsUGgX8FbdA@w|{p8t;rRnH3X@qE6br60-V}tMSx!&-*6%5%ki@!l&X_$kBS%?&Q7JN z_t(By2~Dfzt5fJPHSQ%~K3YFt8{JKZ{3VVhUp$OZhxBSzT+2?ksTh0vEL67B_>waqJuzMQVV4v`VAsS4G;)2St#_}TU* zn$tg4I z1)#5~t~Zbh$m%sMD@}-DyTYx>0RR910000000000kof}qe>LcCF21+`0000EpLbPNSZ?Rk z{xAwJP%X(2P#5~@c{`szUR5CtDJ|%J%|CvB)#P${@*#i1T{Tp1s`Ki4XbMl}B$~`D z^*UF;=r^*AYNe{JAr zT#I< zS9X7B0cxPwlQCM;moqa)t=$k%o6&(4)bl=bm5>aFR#_ zV_T4j{(qL+1q~oiC}$!Mf9RT_p0){DOG#FhYjgtiRFhG00000000000001h9A8SATkiagEVBRr002ovPDHLkV1it}%}f9Q literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.37.png b/tests_speculos/test-transaction-nft/nanos.37.png new file mode 100644 index 0000000000000000000000000000000000000000..52b06db91d6825dc858bbdf6384330dc370de38e GIT binary patch literal 512 zcmV+b0{{JqP)9$&a>M-t;_CUjSRLmnRm1-JJSnjBBfqT5;Az=j8exhE~(DGeQFQ8IoM};WSmX* zXTS#F^dV?)U#3o=)n0#_vK<|VWpDgtq{GJXYddRSKOHVU$Tr`Az1ZBNmmss-PTl_@ z3gKfMhTlE{uJAZAU&aWzYkkOMG9mQX&a99afuhb`I}IV|DN7>|UPMv<9d)liqUaIMiVeZ|YUd+CY-IF8>~K zzhRaAS`H?-=QM1i=Tu6{1-oD!M<5AJP1 literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.39.png b/tests_speculos/test-transaction-nft/nanos.39.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;`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-transaction-nft/nanox.02.png b/tests_speculos/test-transaction-nft/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&_( O0000YMY+BBwOuz7f}N4U_y6zE4l|_v(E88416FzbJi;7T{80U{IKD^t)YDI=%Yk&K=jg z_gj~#Jcx9hzfCG??kus(8^!7)1WRVGdCFb$JN@sQkjjo%3SYUGrkg9hm3b#J`%k^Y z{B+Towf#N^W}iB`W#)(3ZXZRT$}af%dfiqFsY;IDhyTd!Pm;>1+0y*MNQ-rrquMW{ wsR6>ZhD8R#p&84X6(%B+~BW^XH|FWv^{{)b*r>mdKI;Vst0MuKv>;M1& literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.04.png b/tests_speculos/test-transaction-nft/nanox.04.png new file mode 100644 index 0000000000000000000000000000000000000000..a5eebf2fd5d947746fb37a9705f86a514bb2a30e GIT binary patch literal 469 zcmV;`0V@89P)CbaA{Beo0cvUV%Ijyptd?uhq-F;~!gdoTBcCMiOjQqg$x4v>*4Vu%C!kX#T zGE}KHj+*wpczlDbg-lu%B5#KfY8RPzm9r)Hsvx(_$TGv)oS8?>+4TgJnUxpQW==+J zq$gU8Q5`rMM@_$ry|18kwa4hQksVi>U9X@rMaQJ)LaisTV}n(ocebUBW(46UFy{PEY##V&+jqGZ+71M~>iL7-l%QL3E zxvOfXRAyD4(Y%V+XxxuxORGI-hPpjPqj%Ps!;bEoa(Xi^o9=+#{)FIu-XX7Ab2r80~%fF2XnEJ^W_*AF4&B z`j0V6P4yqf9DEXQp-;LgGIOvM$<@>$4X`Hr?&2JkXXz zB-{bsbrg@#9SpA|7)@n=SJ-$2dz(IU2c%G!+nG2AhHXIIeB-3=Z8~89Y`zj_zj!D) z*{FHwSlB_GtmjWj{2SVxgo~J3yEVOCUhra`54Jhg&%r$Dh4rrJD<)w8006w+;3eq5 z`|v%Ue-l2O1-tU2mh)aAZ8fx*mm}|8$=0!}q29`9ce!ow?h{`G9rS#9USGjmiIm&% z!fvQR6J*q^nH%HUNOplvVSwi;7L4%o-|1^V{j?b3jhEB000000000000000T;?Aa&CXE@cvv9- O0000{MKoyd07jMO#(4idAl%RJJ0Bs(`Y7AB$Rz+5ky0XR%jN zE4M2)r97IzEy&M7msW|`QEAclXHfZ0Lbjmplx(DCGbDG3vq!8ai8_Y*?@#9?T$Qgr z)lz<$lvK;5tH&M(-a6CwkaJdV>=)U}mx(#Zi$npl;&R)qaX>2^1jycF=ivz!jn5$o2CaZjG5)$M9!-tUgtQRm{Ub$@Ah z#YU>3UjeF>w$@o2^u~&vwJz_>_Gky0+k#4otwo{L{U2<;AF*Dwbz>WKr4=#qZ*N@H zLLc;?_1n=E;FrA9zm#!l&8l1AO37L4RVBWvZaO>VyYsm1yixzRb!F>Y4jiw?Pe5&< zf8Ed{(YI&wkFu_fzWbkzH=-G8a)H&3VOR0J_W4(qL3t)I=!DTjDYj3Lo z+g{fnv&l?(73^z(&GF@U29~W&wogF?A-~zwTYEWz&^P@($r)i~0outisrymsbZuj? zUu}AQtnX#>TDu?t00000015WptKK3Cj+(bOO{m%QG+treEad_SUdm@P6u{YP&UV-P zN>YTN3$lL`%Q{ZajNL;iWlmX=Ow~3`&a)?EDsyYy?<<0Is)cBQ=8L`lI~+HfP@3SL z%V)LWI|X-jfNkQns`H`%}0AhOve zf!142z33>6O{aP~boAe1t3b;#F#?pdgTN3Z%2Y-$q&-$cmz+SJ4H8u8G^D*n4b3b5 zed81lXFeVQBq5FHn3@_)Jr#Qkd0C4h(1s`O!0K`M2N{3@0000uihrvd_wDG|BFxHa z<_K{)b18I!VNapP11Nz3lD^IlmT z?47Oa&sa0FMyWXVR!Q96)A_$0pZR2MoIY=%T&>hE`77E9JD%KT_!_;UUjEYkr`Gl+ z>rR{gS?y4LbfY<6TlqEK`%3kXkNnsszVBTi1N&0tcl!bv=Fc=e5InhD(Z)z3C2NDo gVQyduA_JYzyq}haZ(1|sZzV|B)78&qol`;+07#*}+yDRo literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.09.png b/tests_speculos/test-transaction-nft/nanox.09.png new file mode 100644 index 0000000000000000000000000000000000000000..5481d5daf483cceb519fd0f4ebf5930d351d3b04 GIT binary patch literal 358 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|`hsba4!+nDh2#v{17Gk3-b~_*VEpm3K}@=~aGSRY^_N&oVdE7dTqw+^knESk-)7B2@eNyldtc7AZ;CI~mU8 z;Gg?T2oP!gOWqBi5#6Y!ZC% z(B}VB4Mrp5DaB5|r4M}k9G_`-VoLW1uM(!3zRRzFFW5A5RanRD|M}00>=y5zaaTe3 x@9hvL-!J)Yd^(2Qkx8=KTq+nrUPge1#Cx0F&!n{!Yn}!Pdb;|#taD0e0sy(jlY0OF literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.10.png b/tests_speculos/test-transaction-nft/nanox.10.png new file mode 100644 index 0000000000000000000000000000000000000000..d1eb962cac4f32c7034717609e4e6e0874e6e85b GIT binary patch literal 956 zcmV;t14I0YP)#o^SgRZ_1-H-99)`$V0g`F5k1H3f0FX6xK8{|jQL&7JZUD%& z&X73(Q*2{`LVdNuQ%hXS(fq2zzr0>uGPj_6hmkL+)2sM2S=+tJ!7V5~@6sUNK$bTO z)#jmTlso|Hxvl*`8UZ!cgQ@F9zEmw2VBZ^!pWBh%g6KQxgoSr!e*m(2QWQl|yf3~Q zs@`wO6Btkpfuti(it=_1gbK9@uYEF(m~e6gB}+JDjsfIZ&9%up#iw}ukfn5nQgmS{ zV(b|sC{8nI)ILGYkT24Ei2XUfb0b3oaAy!(?-iyQq^20l223k?$F_dbxy$<`Sd-+W zqj7?}HklDh5aTu%Y$!EYNKl1XIaExC*M5O%zx8&eVZs2YbC8)S{PId9Gz@A(i?!b7 z{~{*e1ZuSt$$cTq;miZT?9|t9C6as>$(qrZnCo2{lua0?Oqgys;A~<>SQ^t4Fq2%_ ze;P`vEBjBT#^9CIF7!&AB-a=mC6->Pri~@-2gZ}EtbTOs1bQEd#VS!<^HICyh%wPo z>u%y0AYw?FJ;ZDHQ6IXc0X7}qqh zUS|0;(hF9tue?=qZAZO60Mb)Hb^1qaM$NthRG3{`HMJ&Es?}5xbeh+jn&=uSCy}Bk zilQir{|fb=^6IVHk{r~tAdpaG1#tWgomR0;>+tHv1$h>r`DyEZ2lwV*Yr2Tc5^aBsbAuzfij@E#JyoeB~pM~}9qfJqV566~h1H#d!x zJx30w6&Hwi0VKxtXC|j>z1h$HIjp#HMc6F3e4003aSpOh%2l=}Pidc}`d z2g^8h?|Wk;tcv5DQc9_N{ldsH$YW%LD}Jwss8m^d>W%eoAmzq}1yF@KS|ztFz@B^2 z-}Mka)j8_l6YW|Bl;`%k;P5KFU9)}qw9Fo0sm3n5tyh^WDBEhEwyZcaP z1*m(I37RTs@7|hQhLPiQk}A0~B_zrmg5D#Hot;*X=@&&BN6K*Dv~UPY&$|uijAO^- zC=qEiD_T-=1vD3JzH0HgfuBmbe=#AVQ{c>TOSCJc&f!=1VK!v~S4il(ipZFr<6H zhK?DT@gj48Jw|@=9DZwTemmQ6@7^VzwIaSrH_2pwn?|b1{$|v{ofw7gG)OXauoUS9 p0000000000000000001%`3L!3i(3%EFfafB002ovPDHLkV1f@{-@*U@ literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.12.png b/tests_speculos/test-transaction-nft/nanox.12.png new file mode 100644 index 0000000000000000000000000000000000000000..6282738092eceb6610f61b605ec478d24dcbd738 GIT binary patch literal 341 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|^K-ba4!+nDh2#v{17Gk3-|3>mz3@bd>NdX90vdZ006*tz9!1bIm^qG(nPVR zw4&mUa4FDp%Xt1i-_s>7GIW-T62fY^JjG|Vf}C?o$zH|It)o|>99U^-K5yIU`BP8l zCi8cIMoyy{i4%K1J@2QKiZm(KNGAF+lzHZEo%sQ1+IPCCxuBd9s$23$)FUNkV>W0B zcbK1o7S(`e{It5>W0IF-|C1dlsGwuhj8-p%GQ&jk)$zOb$T|_SVLDp_aF|Z?FU+ct zgtAakDzzIm=~dfh6$5#Eo3z#J-T_eY*8;HgtrY+O>SKmJbzGi9dP5Aco`a2jB#oM+ zN4??|uh~v%T4_7No|xwuzmdn)nlMcD+c0+gjkO|lpDWsHbaqjUX8qA>$=Q}<(ZbXN z~<|U_!kUe@}Zwq>C@YrLpp5P^?JX$298Pg)MSrI zej7quxc8By@!#k=8SihS;cC3U8Dj7xWT7Xya56Di3ho5}00000000000000000000 d00013#t*s(24EDlYE=LL002ovPDHLkV1i(v^qc?y literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.14.png b/tests_speculos/test-transaction-nft/nanox.14.png new file mode 100644 index 0000000000000000000000000000000000000000..bff4ff72cbd254541a899dcd315dc05f2d83a2e9 GIT binary patch literal 359 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|`hqba4!+nDh2#BHtkep0I(njFS8>Q#0CLyfe*yFlExrsk?U+y?V0t)&3ubUKMo`0nX;kMgOc0d%ZbTTRW}R z*m}{=4O4GU_f?B!JhbiBr9}ov*wfX|Wt~$(696jm BqdNcq literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.15.png b/tests_speculos/test-transaction-nft/nanox.15.png new file mode 100644 index 0000000000000000000000000000000000000000..c02f5ccbcf0b6ec66070447485920b78c64bdcf9 GIT binary patch literal 423 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|`Jiba4!+nDh2VbkSi29*4l~ zljm;uuex!1T}4}Bi-OQI!AsTqZp}zi{OGlw8>lPd-SK-S$7cMOa*_}_c=zqy=qlIp z=amy`=XOYZ`jA#p`Cp+$LCf&Y;Wy{KukpNN;xIfGGU=9%nciHZ+9a!PwqqsJvk%Gj zS4_F5acp+|qu3++r@7_+thnM8!L%yXzGXtcaqYbm`=tCOFCA!|bo|-dYq1Qa|984a z*abi>@&Dky$DKzPmLJVU zTpg;t@b9&S+cb?|SN)zm&A&(Iz*V0`wXZsxkL=*STO2>9vDa_cbf@EP`)6j$$KSkt zU6G<@U z`jF_i(?h0;w^sOxV6sK5>B*FON!6<<^@dp+gm+)?n{(KDVjM8QkU_#Md(BN6CP7Uf P&Cr z!Swm*Uh3UHtvdmAvYhhLnwSN1ZQ7;;IqK=zCGW)^6Hwm$NFk~luUYp$%I;a-V+CMQ zyTASbupd!*bs@ei&8;rZu#sBtfA#DQN%y*pVZ?@Ho)DN(`ibyASfoun$aM8iIHAU4 zxc7~q?&`bH;0e?I}@ACkdD_QhTjoSUv*_a0HKdmMd({_wh{ zN5<*ub?*^{oPhV&uimEW-N_t0_-A2ca~#KU9M816)9|r? z%2!_KQc6pG*oR&HthLr!@5ec%mj3jeeFE$3RjpP1A^#;m4v*MSGG=o&k;mH+*-+(~ zEpY^>4s`m{x~=YCbuY^r$!Kom`b&mt5mb;)gv;)2l0E`#z1G+W=bbCha@8tW?wc8| zNxuNk4CEU3UCAVtg)!==KmmM%Hrw6O9l zA2>7t007XVp5g2x-S8prs!1no;UKk9x|kvpyIMxna}yI*{q_J@L< zZQOY&c#5ffY{RP|7|9`$Pe;mT+(I6^HcgA!4jBD^FW}!C?ip(`gMR@@L0000000000000000Dv?b@?P)F-8jwG& zn3qEc%0x6scr(d9lo6tv$6$-Mpl`-zCv1!8^5*L>0Kj(SJO0~k-e`FNll;`)f}SFc z!v2q-?Sx~xQQQ~F#o|xyGliKOujQg$mZj*qEE+k=%GpDYSv`b${h{}()cayVcTTT= z;b!NQs8kzlROvDOf5~ZV#L{GX4>#`bb34~!Fxcp9?oYuq}g6?@^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-transaction-nft/nanox.20.png b/tests_speculos/test-transaction-nft/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-transaction-nft/nanox.21.png b/tests_speculos/test-transaction-nft/nanox.21.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&_( O0000pSZ(Y+=C<0N9u^c%J*x9`0bx1e>ilVm4V@Q?OF|~g zR_62G%^S5AFR}V3;^jDPpz zwb5ZWY#C1Oe!th~k)PG^?~@s$w=YP1ns?d8I;QMP=Q7L9Ojf7=ulu>{o%o-Y6h@FY z&u@F{ec}HXrMQ>*A0#$?*`Dd5`}b_mLC>;FVdQ&MBb@0E!BW!~g&Q literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.23.png b/tests_speculos/test-transaction-nft/nanox.23.png new file mode 100644 index 0000000000000000000000000000000000000000..a5eebf2fd5d947746fb37a9705f86a514bb2a30e GIT binary patch literal 469 zcmV;`0V@89P)CbaA{Beo0cvUV%Ijyptd?uhq-F;~!gdoTBcCMiOjQqg$x4v>*4Vu%C!kX#T zGE}KHj+*wpczlDbg-lu%B5#KfY8RPzm9r)Hsvx(_$TGv)oS8?>+4TgJnUxpQW==+J zq$gU8Q5`rMM@_$ry|18kwa4hQksVi>U9X@rMaQJ)LaisTV}n(ocebUBW(46UFy{PEY##V&+jqGZ+71M~>iL7-l%QL3E zxvOfXRAyD4(Y%V+XxxuxORGI-hPpjPqj%Ps!;bEoa(Xi^o9=+#{)FIu-XX7Ab2r80~%fF2XnEJ^W_*AF4&B z`j0V6P4yqf9DEXQp-;LgGIOvM$<@>$4X`Hr?&2JkXXz zB-{bsbrg@#9SpA|7)@n=SJ-$2dz(IU2c%G!+nG2AhHXIIeB-3=Z8~89Y`zj_zj!D) z*{FHwSlB_GtmjWj{2SVxgo~J3yEVOCUhra`54Jhg&%r$Dh4rrJD<)w8006w+;3eq5 z`|v%Ue-l2O1-tU2mh)aAZ8fx*mm}|8$=0!}q29`9ce!ow?h{`G9rS#9USGjmiIm&% z!fvQR6J*q^nH%HUNOplvVSwi;7L4%o-|1^V{j?b3jhEB000000000000000T;?Aa&CXE@cvv9- O0000{MKoyd07jMO#(4idAl%RJJ0Bs(`Y7AB$Rz+5ky0XR%jN zE4M2)r97IzEy&M7msW|`QEAclXHfZ0Lbjmplx(DCGbDG3vq!8ai8_Y*?@#9?T$Qgr z)lz<$lvK;5tH&M(-a6CwkaJdV>=)U}mx(#Zi$npl;&R)qaX>2^1jycF=ivz!jn5$o2CaZjG5)$M9!-tUgtQRm{Ub$@Ah z#YU>3UjeF>w$@o2^u~&vwJz_>_Gky0+k#4otwo{L{U2<;AF*Dwbz>WKr4=#qZ*N@H zLLc;?_1n=E;FrA9zm#!l&8l1AO37L4RVBWvZaO>VyYsm1yixzRb!F>Y4jiw?Pe5&< zf8Ed{(YI&wkFu_fzWbkzH=-G8a8I!VNapP11Nz3lD^IlmT z?47Oa&sa0FMyWXVR!Q96)A_$0pZR2MoIY=%T&>hE`77E9JD%KT_!_;UUjEYkr`Gl+ z>rR{gS?y4LbfY<6TlqEK`%3kXkNnsszVBTi1N&0tcl!bv=Fc=e5InhD(Z)z3C2NDo gVQyduA_JYzyq}haZ(1|sZzV|B)78&qol`;+07#*}+yDRo literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.27.png b/tests_speculos/test-transaction-nft/nanox.27.png new file mode 100644 index 0000000000000000000000000000000000000000..5481d5daf483cceb519fd0f4ebf5930d351d3b04 GIT binary patch literal 358 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|`hsba4!+nDh2#v{17Gk3-b~_*VEpm3K}@=~aGSRY^_N&oVdE7dTqw+^knESk-)7B2@eNyldtc7AZ;CI~mU8 z;Gg?T2oP!gOWqBi5#6Y!ZC% z(B}VB4Mrp5DaB5|r4M}k9G_`-VoLW1uM(!3zRRzFFW5A5RanRD|M}00>=y5zaaTe3 x@9hvL-!J)Yd^(2Qkx8=KTq+nrUPge1#Cx0F&!n{!Yn}!Pdb;|#taD0e0sy(jlY0OF literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.28.png b/tests_speculos/test-transaction-nft/nanox.28.png new file mode 100644 index 0000000000000000000000000000000000000000..ff4e54c6fe90e98c58da36c9503d7049f66c01e7 GIT binary patch literal 464 zcmV;>0WbcEP)8Dw(Q%N$Q;qpqU!|itI8>s1z*Wd(VlR75Im(e`+Pn*VFRN+jxOo{znb?cV zndwM%SnUH_f>X;RkBxxdb1EO$SLXy3)@GU6Jk|kq;H!{re;?Z72ArGOxYHevzq*{c>yzZK7Yr$p)k#_b>eemF491XExXT zO}z_=QS)j~{AQdT1c5H>M%=H$AH+B1`XMvq+xVe z<{gEUtVcjpm_nMUejKV(b+P?DX&W$Xm8~N7B|Hf4yrprHMRx|>P)v;x9UwKLG4xRKwk2~HZ*Hx4wu|L#$hVnMPiFcxY&Tr7$~OMU z>1i*odmU!vJM??U*WWH@6@>rZ4sr7RlJCZ+6WFT5+vLNi=UTxCbO{n@I9#o^SgRZ_1-H-99)`$V0g`F5k1H3f0FX6xK8{|jQL&7JZUD%& z&X73(Q*2{`LVdNuQ%hXS(fq2zzr0>uGPj_6hmkL+)2sM2S=+tJ!7V5~@6sUNK$bTO z)#jmTlso|Hxvl*`8UZ!cgQ@F9zEmw2VBZ^!pWBh%g6KQxgoSr!e*m(2QWQl|yf3~Q zs@`wO6Btkpfuti(it=_1gbK9@uYEF(m~e6gB}+JDjsfIZ&9%up#iw}ukfn5nQgmS{ zV(b|sC{8nI)ILGYkT24Ei2XUfb0b3oaAy!(?-iyQq^20l223k?$F_dbxy$<`Sd-+W zqj7?}HklDh5aTu%Y$!EYNKl1XIaExC*M5O%zx8&eVZs2YbC8)S{PId9Gz@A(i?!b7 z{~{*e1ZuSt$$cTq;miZT?9|t9C6as>$(qrZnCo2{lua0?Oqgys;A~<>SQ^t4Fq2%_ ze;P`vEBjBT#^9CIF7!&AB-a=mC6->Pri~@-2gZ}EtbTOs1bQEd#VS!<^HICyh%wPo z>u%y0AYw?FJ;ZDHQ6IXc0X7}qqh zUS|0;(hF9tue?=qZAZO60Mb)Hb^1qaM$NthRG3{`HMJ&Es?}5xbeh+jn&=uSCy}Bk zilQir{|fb=^6IVHk{r~tAdpaG1#tWgomR0;>+tHv1$h>r`DyEZ2lwV*Yr2Tc5^aBsbAuzfij@E#JyoeB~pM~}9qfJqV566~h1H#d!x zJx30w6&Hwi0VKxtXC|j>z1h$HIjp#HMc6F3e4003aSpOh%2l=}Pidc}`d z2g^8h?|Wk;tcv5DQc9_N{ldsH$YW%LD}Jwss8m^d>W%eoAmzq}1yF@KS|ztFz@B^2 z-}Mka)j8_l6YW|Bl;`%k;P5KFU9)}qw9Fo0sm3n5tyh^WDBEhEwyZcaP z1*m(I37RTs@7|hQhLPiQk}A0~B_zrmg5D#Hot;*X=@&&BN6K*Dv~UPY&$|uijAO^- zC=qEiD_T-=1vD3JzH0HgfuBmbe=#AVQ{c>TOSCJc&f!=1VK!v~S4il(ipZFr<6H zhK?DT@gj48Jw|@=9DZwTemmQ6@7^VzwIaSrH_2pwn?|b1{$|v{ofw7gG)OXauoUS9 p0000000000000000001%`3L!3i(3%EFfafB002ovPDHLkV1f@{-@*U@ literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.32.png b/tests_speculos/test-transaction-nft/nanox.32.png new file mode 100644 index 0000000000000000000000000000000000000000..6282738092eceb6610f61b605ec478d24dcbd738 GIT binary patch literal 341 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|^K-ba4!+nDh2#v{17Gk3-|3>mz3@bd>NdX90vdZ006*tz9!1bIm^qG(nPVR zw4&mUa4FDp%Xt1i-_s>7GIW-T62fY^JjG|Vf}C?o$zH|It)o|>99U^-K5yIU`BP8l zCi8cIMoyy{i4%K1J@2QKiZm(KNGAF+lzHZEo%sQ1+IPCCxuBd9s$23$)FUNkV>W0B zcbK1o7S(`e{It5>W0IF-|C1dlsGwuhj8-p%GQ&jk)$zOb$T|_SVLDp_aF|Z?FU+ct zgtAakDzzIm=~dfh6$5#Eo3z#J-T_eY*8;HgtrY+O>SKmJbzGi9dP5Aco`a2jB#oM+ zN4??|uh~v%T4_7No|xwuzmdn)nlMcD+c0+gjkO|lpDWsHbaqjUX8qA>$=Q}<(ZbXN z~<|U_!kUe@}Zwq>C@YrLpp5P^?JX$298Pg)MSrI zej7quxc8By@!#k=8SihS;cC3U8Dj7xWT7Xya56Di3ho5}00000000000000000000 d00013#t*s(24EDlYE=LL002ovPDHLkV1i(v^qc?y literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.34.png b/tests_speculos/test-transaction-nft/nanox.34.png new file mode 100644 index 0000000000000000000000000000000000000000..bff4ff72cbd254541a899dcd315dc05f2d83a2e9 GIT binary patch literal 359 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|`hqba4!+nDh2#BHtkep0I(njFS8>Q#0CLyfe*yFlExrsk?U+y?V0t)&3ubUKMo`0nX;kMgOc0d%ZbTTRW}R z*m}{=4O4GU_f?B!JhbiBr9}ov*wfX|Wt~$(696jm BqdNcq literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.35.png b/tests_speculos/test-transaction-nft/nanox.35.png new file mode 100644 index 0000000000000000000000000000000000000000..c02f5ccbcf0b6ec66070447485920b78c64bdcf9 GIT binary patch literal 423 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|`Jiba4!+nDh2VbkSi29*4l~ zljm;uuex!1T}4}Bi-OQI!AsTqZp}zi{OGlw8>lPd-SK-S$7cMOa*_}_c=zqy=qlIp z=amy`=XOYZ`jA#p`Cp+$LCf&Y;Wy{KukpNN;xIfGGU=9%nciHZ+9a!PwqqsJvk%Gj zS4_F5acp+|qu3++r@7_+thnM8!L%yXzGXtcaqYbm`=tCOFCA!|bo|-dYq1Qa|984a z*abi>@&Dky$DKzPmLJVU zTpg;t@b9&S+cb?|SN)zm&A&(Iz*V0`wXZsxkL=*STO2>9vDa_cbf@EP`)6j$$KSkt zU6G<@U z`jF_i(?h0;w^sOxV6sK5>B*FON!6<<^@dp+gm+)?n{(KDVjM8QkU_#Md(BN6CP7Uf P&Cr z!Swm*Uh3UHtvdmAvYhhLnwSN1ZQ7;;IqK=zCGW)^6Hwm$NFk~luUYp$%I;a-V+CMQ zyTASbupd!*bs@ei&8;rZu#sBtfA#DQN%y*pVZ?@Ho)DN(`ibyASfoun$aM8iIHAU4 zxc7~q?&`bH;0e?I}@ACkdD_QhTjoSUv*_a0HKdmMd({_wh{ zN5<*ub?*^{oPhV&uimEW-N_t0_-A2ca~#KU9M816)9|r? z%2!_KQc6pG*oR&HthLr!@5ec%mj3jeeFE$3RjpP1A^#;m4v*MSGG=o&k;mH+*-+(~ zEpY^>4s`m{x~=YCbuY^r$!Kom`b&mt5mb;)gv;)2l0E`#z1G+W=bbCha@8tW?wc8| zNxuNk4CEU3UCAVtg)!==KmmM%Hrw6O9l zA2>7t007XVp5g2x-S8prs!1no;UKk9x|kvpyIMxna}yI*{q_J@L< zZQOY&c#5ffY{RP|7|9`$Pe;mT+(I6^HcgA!4jBD^FW}!C?ip(`gMR@@L0000000000000000Dv?b@?P)F-8jwG& zn3qEc%0x6scr(d9lo6tv$6$-Mpl`-zCv1!8^5*L>0Kj(SJO0~k-e`FNll;`)f}SFc z!v2q-?Sx~xQQQ~F#o|xyGliKOujQg$mZj*qEE+k=%GpDYSv`b${h{}()cayVcTTT= z;b!NQs8kzlROvDOf5~ZV#L{GX4>#`bb34~!Fxcp9?oYuq}g6?@^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-transaction-nft/nanox.40.png b/tests_speculos/test-transaction-nft/nanox.40.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/yarn-error.log b/tests_speculos/yarn-error.log new file mode 100644 index 00000000..90f02a6c --- /dev/null +++ b/tests_speculos/yarn-error.log @@ -0,0 +1,59 @@ +Arguments: + /home/robert/.nvm/versions/node/v16.10.0/bin/node /usr/local/bin/yarn install + +PATH: + /home/robert/ledgerflow/bin:/home/robert/.nvm/versions/node/v16.10.0/bin:/usr/bin:/home/robert/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin + +Yarn version: + 1.22.10 + +Node version: + 16.10.0 + +Platform: + linux x64 + +Trace: + SyntaxError: /home/robert/git/ledger-app-flow/tests_speculos/package.json: Unexpected token . in JSON at position 322 + at JSON.parse () + at /usr/local/lib/node_modules/yarn/lib/cli.js:1625:59 + at Generator.next () + at step (/usr/local/lib/node_modules/yarn/lib/cli.js:310:30) + at /usr/local/lib/node_modules/yarn/lib/cli.js:321:13 + +npm manifest: + { + "name": "speculos-integration-tests", + "version": "1.0.0", + "description": "", + "license": "Apache-2.0", + "main": "index.js", + "type": "module", + "dependencies": { + "@ledgerhq/hw-transport-node-speculos": "^6.20.0", + "@onflow/ledger": "file:../js", + "@onflow/encode": "^0.0.8", + "deepmerge": "^4.2.2". + "assert": "^2.0.0", + "strict": "^0.0.0" + }, + "devDependencies": { + "@babel/cli": "^7.11.6", + "@babel/core": "^7.11.6", + "@babel/node": "^7.10.5", + "@babel/plugin-transform-runtime": "^7.11.5", + "@babel/preset-env": "^7.11.5", + "@babel/runtime": "^7.10.5", + "@ledgerhq/hw-transport-node-hid": "^6.27.1", + "babel-eslint": "^10.1.0", + "elliptic": "^6.5.3", + "jssha": "^3.1.1", + "wait-console-input": "^0.1.7" + } + } + +yarn manifest: + No manifest + +Lockfile: + No lockfile From 7bfb2ae2f55e7d153ec02ec14b7f4ca82f156326 Mon Sep 17 00:00:00 2001 From: Robert Lukotka Date: Tue, 29 Nov 2022 14:57:40 +0100 Subject: [PATCH 2/4] Add addresses of NFT and MetadataViews contracts --- app/src/parser.c | 62 +++--- app/src/script_parser.c | 71 ++++-- app/src/script_parser.h | 2 +- tests/script_parser.cpp | 27 +-- tests_speculos/test-transaction-nft.js | 208 +++++++++++++++++- .../test-transaction-nft/nanos.40.png | Bin 0 -> 364 bytes .../test-transaction-nft/nanos.41.png | Bin 0 -> 367 bytes .../test-transaction-nft/nanos.42.png | Bin 0 -> 394 bytes .../test-transaction-nft/nanos.43.png | Bin 0 -> 458 bytes .../test-transaction-nft/nanos.44.png | Bin 0 -> 544 bytes .../test-transaction-nft/nanos.45.png | Bin 0 -> 418 bytes .../test-transaction-nft/nanos.46.png | Bin 0 -> 399 bytes .../test-transaction-nft/nanos.47.png | Bin 0 -> 337 bytes .../test-transaction-nft/nanos.48.png | Bin 0 -> 649 bytes .../test-transaction-nft/nanos.49.png | Bin 0 -> 648 bytes .../test-transaction-nft/nanos.50.png | Bin 0 -> 311 bytes .../test-transaction-nft/nanos.51.png | Bin 0 -> 471 bytes .../test-transaction-nft/nanos.52.png | Bin 0 -> 318 bytes .../test-transaction-nft/nanos.53.png | Bin 0 -> 353 bytes .../test-transaction-nft/nanos.54.png | Bin 0 -> 404 bytes .../test-transaction-nft/nanos.55.png | Bin 0 -> 456 bytes .../test-transaction-nft/nanos.56.png | Bin 0 -> 512 bytes .../test-transaction-nft/nanos.57.png | Bin 0 -> 249 bytes .../test-transaction-nft/nanos.58.png | Bin 0 -> 380 bytes .../test-transaction-nft/nanos.59.png | Bin 0 -> 263 bytes .../test-transaction-nft/nanos.60.png | Bin 0 -> 367 bytes .../test-transaction-nft/nanos.61.png | Bin 0 -> 394 bytes .../test-transaction-nft/nanos.62.png | Bin 0 -> 458 bytes .../test-transaction-nft/nanos.63.png | Bin 0 -> 399 bytes .../test-transaction-nft/nanos.64.png | Bin 0 -> 329 bytes .../test-transaction-nft/nanos.65.png | Bin 0 -> 411 bytes .../test-transaction-nft/nanos.66.png | Bin 0 -> 340 bytes .../test-transaction-nft/nanos.67.png | Bin 0 -> 649 bytes .../test-transaction-nft/nanos.68.png | Bin 0 -> 648 bytes .../test-transaction-nft/nanos.69.png | Bin 0 -> 311 bytes .../test-transaction-nft/nanos.70.png | Bin 0 -> 472 bytes .../test-transaction-nft/nanos.71.png | Bin 0 -> 318 bytes .../test-transaction-nft/nanos.72.png | Bin 0 -> 353 bytes .../test-transaction-nft/nanos.73.png | Bin 0 -> 412 bytes .../test-transaction-nft/nanos.74.png | Bin 0 -> 455 bytes .../test-transaction-nft/nanos.75.png | Bin 0 -> 512 bytes .../test-transaction-nft/nanos.76.png | Bin 0 -> 249 bytes .../test-transaction-nft/nanos.77.png | Bin 0 -> 380 bytes .../test-transaction-nft/nanox.41.png | Bin 0 -> 420 bytes .../test-transaction-nft/nanox.42.png | Bin 0 -> 405 bytes .../test-transaction-nft/nanox.43.png | Bin 0 -> 469 bytes .../test-transaction-nft/nanox.44.png | Bin 0 -> 524 bytes .../test-transaction-nft/nanox.45.png | Bin 0 -> 534 bytes .../test-transaction-nft/nanox.46.png | Bin 0 -> 701 bytes .../test-transaction-nft/nanox.47.png | Bin 0 -> 441 bytes .../test-transaction-nft/nanox.48.png | Bin 0 -> 367 bytes .../test-transaction-nft/nanox.49.png | Bin 0 -> 956 bytes .../test-transaction-nft/nanox.50.png | Bin 0 -> 499 bytes .../test-transaction-nft/nanox.51.png | Bin 0 -> 341 bytes .../test-transaction-nft/nanox.52.png | Bin 0 -> 541 bytes .../test-transaction-nft/nanox.53.png | Bin 0 -> 359 bytes .../test-transaction-nft/nanox.54.png | Bin 0 -> 423 bytes .../test-transaction-nft/nanox.55.png | Bin 0 -> 463 bytes .../test-transaction-nft/nanox.56.png | Bin 0 -> 516 bytes .../test-transaction-nft/nanox.57.png | Bin 0 -> 573 bytes .../test-transaction-nft/nanox.58.png | Bin 0 -> 355 bytes .../test-transaction-nft/nanox.59.png | Bin 0 -> 409 bytes .../test-transaction-nft/nanox.60.png | Bin 0 -> 420 bytes .../test-transaction-nft/nanox.61.png | Bin 0 -> 316 bytes .../test-transaction-nft/nanox.62.png | Bin 0 -> 469 bytes .../test-transaction-nft/nanox.63.png | Bin 0 -> 524 bytes .../test-transaction-nft/nanox.64.png | Bin 0 -> 534 bytes .../test-transaction-nft/nanox.65.png | Bin 0 -> 441 bytes .../test-transaction-nft/nanox.66.png | Bin 0 -> 358 bytes .../test-transaction-nft/nanox.67.png | Bin 0 -> 464 bytes .../test-transaction-nft/nanox.68.png | Bin 0 -> 379 bytes .../test-transaction-nft/nanox.69.png | Bin 0 -> 956 bytes .../test-transaction-nft/nanox.70.png | Bin 0 -> 499 bytes .../test-transaction-nft/nanox.71.png | Bin 0 -> 341 bytes .../test-transaction-nft/nanox.72.png | Bin 0 -> 539 bytes .../test-transaction-nft/nanox.73.png | Bin 0 -> 359 bytes .../test-transaction-nft/nanox.74.png | Bin 0 -> 423 bytes .../test-transaction-nft/nanox.75.png | Bin 0 -> 459 bytes .../test-transaction-nft/nanox.76.png | Bin 0 -> 517 bytes .../test-transaction-nft/nanox.77.png | Bin 0 -> 573 bytes .../test-transaction-nft/nanox.78.png | Bin 0 -> 355 bytes .../test-transaction-nft/nanox.79.png | Bin 0 -> 409 bytes 82 files changed, 292 insertions(+), 78 deletions(-) create mode 100644 tests_speculos/test-transaction-nft/nanos.40.png create mode 100644 tests_speculos/test-transaction-nft/nanos.41.png create mode 100644 tests_speculos/test-transaction-nft/nanos.42.png create mode 100644 tests_speculos/test-transaction-nft/nanos.43.png create mode 100644 tests_speculos/test-transaction-nft/nanos.44.png create mode 100644 tests_speculos/test-transaction-nft/nanos.45.png create mode 100644 tests_speculos/test-transaction-nft/nanos.46.png create mode 100644 tests_speculos/test-transaction-nft/nanos.47.png create mode 100644 tests_speculos/test-transaction-nft/nanos.48.png create mode 100644 tests_speculos/test-transaction-nft/nanos.49.png create mode 100644 tests_speculos/test-transaction-nft/nanos.50.png create mode 100644 tests_speculos/test-transaction-nft/nanos.51.png create mode 100644 tests_speculos/test-transaction-nft/nanos.52.png create mode 100644 tests_speculos/test-transaction-nft/nanos.53.png create mode 100644 tests_speculos/test-transaction-nft/nanos.54.png create mode 100644 tests_speculos/test-transaction-nft/nanos.55.png create mode 100644 tests_speculos/test-transaction-nft/nanos.56.png create mode 100644 tests_speculos/test-transaction-nft/nanos.57.png create mode 100644 tests_speculos/test-transaction-nft/nanos.58.png create mode 100644 tests_speculos/test-transaction-nft/nanos.59.png create mode 100644 tests_speculos/test-transaction-nft/nanos.60.png create mode 100644 tests_speculos/test-transaction-nft/nanos.61.png create mode 100644 tests_speculos/test-transaction-nft/nanos.62.png create mode 100644 tests_speculos/test-transaction-nft/nanos.63.png create mode 100644 tests_speculos/test-transaction-nft/nanos.64.png create mode 100644 tests_speculos/test-transaction-nft/nanos.65.png create mode 100644 tests_speculos/test-transaction-nft/nanos.66.png create mode 100644 tests_speculos/test-transaction-nft/nanos.67.png create mode 100644 tests_speculos/test-transaction-nft/nanos.68.png create mode 100644 tests_speculos/test-transaction-nft/nanos.69.png create mode 100644 tests_speculos/test-transaction-nft/nanos.70.png create mode 100644 tests_speculos/test-transaction-nft/nanos.71.png create mode 100644 tests_speculos/test-transaction-nft/nanos.72.png create mode 100644 tests_speculos/test-transaction-nft/nanos.73.png create mode 100644 tests_speculos/test-transaction-nft/nanos.74.png create mode 100644 tests_speculos/test-transaction-nft/nanos.75.png create mode 100644 tests_speculos/test-transaction-nft/nanos.76.png create mode 100644 tests_speculos/test-transaction-nft/nanos.77.png create mode 100644 tests_speculos/test-transaction-nft/nanox.41.png create mode 100644 tests_speculos/test-transaction-nft/nanox.42.png create mode 100644 tests_speculos/test-transaction-nft/nanox.43.png create mode 100644 tests_speculos/test-transaction-nft/nanox.44.png create mode 100644 tests_speculos/test-transaction-nft/nanox.45.png create mode 100644 tests_speculos/test-transaction-nft/nanox.46.png create mode 100644 tests_speculos/test-transaction-nft/nanox.47.png create mode 100644 tests_speculos/test-transaction-nft/nanox.48.png create mode 100644 tests_speculos/test-transaction-nft/nanox.49.png create mode 100644 tests_speculos/test-transaction-nft/nanox.50.png create mode 100644 tests_speculos/test-transaction-nft/nanox.51.png create mode 100644 tests_speculos/test-transaction-nft/nanox.52.png create mode 100644 tests_speculos/test-transaction-nft/nanox.53.png create mode 100644 tests_speculos/test-transaction-nft/nanox.54.png create mode 100644 tests_speculos/test-transaction-nft/nanox.55.png create mode 100644 tests_speculos/test-transaction-nft/nanox.56.png create mode 100644 tests_speculos/test-transaction-nft/nanox.57.png create mode 100644 tests_speculos/test-transaction-nft/nanox.58.png create mode 100644 tests_speculos/test-transaction-nft/nanox.59.png create mode 100644 tests_speculos/test-transaction-nft/nanox.60.png create mode 100644 tests_speculos/test-transaction-nft/nanox.61.png create mode 100644 tests_speculos/test-transaction-nft/nanox.62.png create mode 100644 tests_speculos/test-transaction-nft/nanox.63.png create mode 100644 tests_speculos/test-transaction-nft/nanox.64.png create mode 100644 tests_speculos/test-transaction-nft/nanox.65.png create mode 100644 tests_speculos/test-transaction-nft/nanox.66.png create mode 100644 tests_speculos/test-transaction-nft/nanox.67.png create mode 100644 tests_speculos/test-transaction-nft/nanox.68.png create mode 100644 tests_speculos/test-transaction-nft/nanox.69.png create mode 100644 tests_speculos/test-transaction-nft/nanox.70.png create mode 100644 tests_speculos/test-transaction-nft/nanox.71.png create mode 100644 tests_speculos/test-transaction-nft/nanox.72.png create mode 100644 tests_speculos/test-transaction-nft/nanox.73.png create mode 100644 tests_speculos/test-transaction-nft/nanox.74.png create mode 100644 tests_speculos/test-transaction-nft/nanox.75.png create mode 100644 tests_speculos/test-transaction-nft/nanox.76.png create mode 100644 tests_speculos/test-transaction-nft/nanox.77.png create mode 100644 tests_speculos/test-transaction-nft/nanox.78.png create mode 100644 tests_speculos/test-transaction-nft/nanox.79.png diff --git a/app/src/parser.c b/app/src/parser.c index 4e00279e..77281160 100644 --- a/app/src/parser.c +++ b/app/src/parser.c @@ -629,23 +629,23 @@ parser_error_t parser_getItem_internal(int8_t *displayIdx, switch (parser_tx_obj.parsedScript.script_type) { case SCRIPT_TYPE_NFT_SETUP_COLLECTION: //validate basic assumptions on the script - if (parser_tx_obj.parsedScript.elements_count != 9) { + if (parser_tx_obj.parsedScript.elements_count != 11) { return PARSER_UNEXPECTED_ERROR; } // storagePath should be preceeded with "/storage/" - if (parser_tx_obj.parsedScript.elements[2].data < parser_tx_obj.parsedScript.elements[1].data + (sizeof(storageString)-1) || - memcmp(parser_tx_obj.parsedScript.elements[2].data - (sizeof(storageString)-1), storageString, sizeof(storageString)-1)) { + if (parser_tx_obj.parsedScript.elements[4].data < parser_tx_obj.parsedScript.elements[3].data + (sizeof(storageString)-1) || + memcmp(parser_tx_obj.parsedScript.elements[4].data - (sizeof(storageString)-1), storageString, sizeof(storageString)-1)) { return PARSER_UNEXPECTED_ERROR; } // publicCollectionContractName and publicCollectionName should be separated by a single '.' - if (parser_tx_obj.parsedScript.elements[5].data + parser_tx_obj.parsedScript.elements[5].length + 1 != - parser_tx_obj.parsedScript.elements[6].data || - parser_tx_obj.parsedScript.elements[5].data[parser_tx_obj.parsedScript.elements[5].length] != '.') { + if (parser_tx_obj.parsedScript.elements[7].data + parser_tx_obj.parsedScript.elements[7].length + 1 != + parser_tx_obj.parsedScript.elements[8].data || + parser_tx_obj.parsedScript.elements[7].data[parser_tx_obj.parsedScript.elements[7].length] != '.') { return PARSER_UNEXPECTED_ERROR; } // publicPath should be preceeded with "/public/" - if (parser_tx_obj.parsedScript.elements[7].data < parser_tx_obj.parsedScript.elements[6].data + (sizeof(publicString)-1) || - memcmp(parser_tx_obj.parsedScript.elements[7].data - (sizeof(publicString)-1), publicString, sizeof(publicString)-1)) { + if (parser_tx_obj.parsedScript.elements[9].data < parser_tx_obj.parsedScript.elements[8].data + (sizeof(publicString)-1) || + memcmp(parser_tx_obj.parsedScript.elements[9].data - (sizeof(publicString)-1), publicString, sizeof(publicString)-1)) { return PARSER_UNEXPECTED_ERROR; } @@ -657,57 +657,57 @@ parser_error_t parser_getItem_internal(int8_t *displayIdx, SCREEN(true) { snprintf(outKey, outKeyLen, "Contract Name"); pageStringExt(outVal, outValLen, - (const char *) parser_tx_obj.parsedScript.elements[0].data, - parser_tx_obj.parsedScript.elements[0].length, + (const char *) parser_tx_obj.parsedScript.elements[2].data, + parser_tx_obj.parsedScript.elements[2].length, pageIdx, pageCount); return PARSER_OK; } SCREEN(true) { snprintf(outKey, outKeyLen, "Contract Address"); pageStringExt(outVal, outValLen, - (const char *) parser_tx_obj.parsedScript.elements[1].data, - parser_tx_obj.parsedScript.elements[1].length, + (const char *) parser_tx_obj.parsedScript.elements[3].data, + parser_tx_obj.parsedScript.elements[3].length, pageIdx, pageCount); return PARSER_OK; } SCREEN(true) { snprintf(outKey, outKeyLen, "Storage Path"); pageStringExt(outVal, outValLen, - (const char *) parser_tx_obj.parsedScript.elements[2].data - (sizeof(storageString)-1), - parser_tx_obj.parsedScript.elements[2].length + (sizeof(storageString)-1), + (const char *) parser_tx_obj.parsedScript.elements[4].data - (sizeof(storageString)-1), + parser_tx_obj.parsedScript.elements[4].length + (sizeof(storageString)-1), pageIdx, pageCount); return PARSER_OK; } SCREEN(true) { snprintf(outKey, outKeyLen, "Public Coll. Name"); pageStringExt(outVal, outValLen, - (const char *) parser_tx_obj.parsedScript.elements[5].data, - parser_tx_obj.parsedScript.elements[5].length + parser_tx_obj.parsedScript.elements[6].length + 1, + (const char *) parser_tx_obj.parsedScript.elements[7].data, + parser_tx_obj.parsedScript.elements[7].length + parser_tx_obj.parsedScript.elements[8].length + 1, pageIdx, pageCount); return PARSER_OK; } SCREEN(true) { snprintf(outKey, outKeyLen, "Public Path"); pageStringExt(outVal, outValLen, - (const char *) parser_tx_obj.parsedScript.elements[7].data - (sizeof(publicString)-1), - parser_tx_obj.parsedScript.elements[7].length + (sizeof(publicString)-1), + (const char *) parser_tx_obj.parsedScript.elements[9].data - (sizeof(publicString)-1), + parser_tx_obj.parsedScript.elements[9].length + (sizeof(publicString)-1), pageIdx, pageCount); return PARSER_OK; } break; case SCRIPT_TYPE_NFT_TRANSFER: //validate basic assumptions on the script - if (parser_tx_obj.parsedScript.elements_count != 7) { + if (parser_tx_obj.parsedScript.elements_count != 8) { return PARSER_UNEXPECTED_ERROR; } // storagePath should be preceeded with "/storage/" - if (parser_tx_obj.parsedScript.elements[2].data < parser_tx_obj.parsedScript.elements[1].data + (sizeof(storageString)-1) || - memcmp(parser_tx_obj.parsedScript.elements[2].data - (sizeof(storageString)-1), storageString, sizeof(storageString)-1)) { + if (parser_tx_obj.parsedScript.elements[3].data < parser_tx_obj.parsedScript.elements[2].data + (sizeof(storageString)-1) || + memcmp(parser_tx_obj.parsedScript.elements[3].data - (sizeof(storageString)-1), storageString, sizeof(storageString)-1)) { return PARSER_UNEXPECTED_ERROR; } // publicPath should be preceeded with "/public/" - if (parser_tx_obj.parsedScript.elements[6].data < parser_tx_obj.parsedScript.elements[5].data + (sizeof(publicString)-1) || - memcmp(parser_tx_obj.parsedScript.elements[6].data - (sizeof(publicString)-1), publicString, sizeof(publicString)-1)) { + if (parser_tx_obj.parsedScript.elements[7].data < parser_tx_obj.parsedScript.elements[6].data + (sizeof(publicString)-1) || + memcmp(parser_tx_obj.parsedScript.elements[7].data - (sizeof(publicString)-1), publicString, sizeof(publicString)-1)) { return PARSER_UNEXPECTED_ERROR; } @@ -719,32 +719,32 @@ parser_error_t parser_getItem_internal(int8_t *displayIdx, SCREEN(true) { snprintf(outKey, outKeyLen, "Contract Name"); pageStringExt(outVal, outValLen, - (const char *) parser_tx_obj.parsedScript.elements[0].data, - parser_tx_obj.parsedScript.elements[0].length, + (const char *) parser_tx_obj.parsedScript.elements[1].data, + parser_tx_obj.parsedScript.elements[1].length, pageIdx, pageCount); return PARSER_OK; } SCREEN(true) { snprintf(outKey, outKeyLen, "Contract Address"); pageStringExt(outVal, outValLen, - (const char *) parser_tx_obj.parsedScript.elements[1].data, - parser_tx_obj.parsedScript.elements[1].length, + (const char *) parser_tx_obj.parsedScript.elements[2].data, + parser_tx_obj.parsedScript.elements[2].length, pageIdx, pageCount); return PARSER_OK; } SCREEN(true) { snprintf(outKey, outKeyLen, "Storage Path"); pageStringExt(outVal, outValLen, - (const char *) parser_tx_obj.parsedScript.elements[2].data - (sizeof(storageString)-1), - parser_tx_obj.parsedScript.elements[2].length + (sizeof(storageString)-1), + (const char *) parser_tx_obj.parsedScript.elements[3].data - (sizeof(storageString)-1), + parser_tx_obj.parsedScript.elements[3].length + (sizeof(storageString)-1), pageIdx, pageCount); return PARSER_OK; } SCREEN(true) { snprintf(outKey, outKeyLen, "Public Path"); pageStringExt(outVal, outValLen, - (const char *) parser_tx_obj.parsedScript.elements[6].data - (sizeof(publicString)-1), - parser_tx_obj.parsedScript.elements[6].length + (sizeof(publicString)-1), + (const char *) parser_tx_obj.parsedScript.elements[7].data - (sizeof(publicString)-1), + parser_tx_obj.parsedScript.elements[7].length + (sizeof(publicString)-1), pageIdx, pageCount); return PARSER_OK; } diff --git a/app/src/script_parser.c b/app/src/script_parser.c index d1c65ed5..97fb7e77 100644 --- a/app/src/script_parser.c +++ b/app/src/script_parser.c @@ -65,22 +65,38 @@ bool parseScript(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLA if ((i >= pE->elements_count) || (j >= pE->elements_count)) return false; \ if (pE->elements[i].length != pE->elements[j].length) return false; \ if (MEMCMP(pE->elements[i].data, pE->elements[j].data, pE->elements[i].length)) return false; \ +} + +#define ADDRESS_STRING_LENGTH 18 +#define NONFUNGIBLETOKEN_METADATAVIEWS_TESTNET "0x631e88ae7f1d7c20" +#define NONFUNGIBLETOKEN_METADATAVIEWS_MAINNET "0x1d7e57aa55817448" + +#define ELEMENT_MUST_BE_ADDRESS(pE, i) { \ + STATIC_ASSERT(sizeof(NONFUNGIBLETOKEN_METADATAVIEWS_TESTNET) == ADDRESS_STRING_LENGTH + 1, "Incompatible types"); \ + STATIC_ASSERT(sizeof(NONFUNGIBLETOKEN_METADATAVIEWS_MAINNET) == ADDRESS_STRING_LENGTH + 1, "Incompatible types"); \ + if (i >= pE->elements_count) return false; \ + if (pE->elements[i].length != ADDRESS_STRING_LENGTH) return false; \ + if (MEMCMP(pE->elements[i].data, NONFUNGIBLETOKEN_METADATAVIEWS_TESTNET, pE->elements[i].length) && \ + MEMCMP(pE->elements[i].data, NONFUNGIBLETOKEN_METADATAVIEWS_MAINNET, pE->elements[i].length)) return false; \ } + // Elements : -// 0 - contractName -// 1 - contractAddress -// 2 - storagePath -// 3 - contractName +// 0 - NonFungibleToken address +// 1 - MetadataViews address +// 2 - contractName +// 3 - contractAddress // 4 - storagePath -// 5 - publicCollectionContractName -// 6 - publicCollectionName -// 7 - publicPath -// 8 - storagePath +// 5 - contractName +// 6 - storagePath +// 7 - publicCollectionContractName +// 8 - publicCollectionName +// 9 - publicPath +// 10 - storagePath bool parseNFT1(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLATILE *scriptToParse, size_t scriptToParseSize) { const char template[] = - "import NonFungibleToken from 0xNONFUNGIBLETOKEN\n" - "import MetadataViews from 0xMETADATAVIEWS\n" + "import NonFungibleToken from \001\n" + "import MetadataViews from \001\n" "import \001 from \001\n" "transaction {\n" " prepare(acct: AuthAccount) {\n" @@ -105,9 +121,12 @@ bool parseNFT1(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLATI return false; } - ELEMENTS_MUST_BE_EQUAL(parsedElements, 0, 3); - ELEMENTS_MUST_BE_EQUAL(parsedElements, 2, 4); - ELEMENTS_MUST_BE_EQUAL(parsedElements, 2, 8); + ELEMENT_MUST_BE_ADDRESS(parsedElements, 0); + ELEMENT_MUST_BE_ADDRESS(parsedElements, 1); + + ELEMENTS_MUST_BE_EQUAL(parsedElements, 2, 5); + ELEMENTS_MUST_BE_EQUAL(parsedElements, 4, 6); + ELEMENTS_MUST_BE_EQUAL(parsedElements, 4, 10); parsedElements->script_type = SCRIPT_TYPE_NFT_SETUP_COLLECTION; return true; @@ -115,16 +134,17 @@ bool parseNFT1(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLATI // Elements : -// 0 - contractName -// 1 - contractAddress -// 2 - storagePath -// 3 - contractName +// 0 - NonFungibleToken address +// 1 - contractName +// 2 - contractAddress +// 3 - storagePath // 4 - contractName -// 5 - storagePath -// 6 - publicPath +// 5 - contractName +// 6 - storagePath +// 7 - publicPath bool parseNFT2(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLATILE *scriptToParse, size_t scriptToParseSize) { const char template[] = - "import NonFungibleToken from 0xNONFUNGIBLETOKEN\n" + "import NonFungibleToken from \001\n" "import \001 from \001\n" "transaction(recipient: Address, withdrawID: UInt64) {\n" " // local variable for storing the transferred nft\n" @@ -160,12 +180,15 @@ bool parseNFT2(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLATI return false; } - ELEMENTS_MUST_BE_EQUAL(parsedElements, 0, 3); - ELEMENTS_MUST_BE_EQUAL(parsedElements, 0, 4); - ELEMENTS_MUST_BE_EQUAL(parsedElements, 2, 5); + ELEMENT_MUST_BE_ADDRESS(parsedElements, 0); + + ELEMENTS_MUST_BE_EQUAL(parsedElements, 1, 4); + ELEMENTS_MUST_BE_EQUAL(parsedElements, 1, 5); + ELEMENTS_MUST_BE_EQUAL(parsedElements, 3, 6); parsedElements->script_type = SCRIPT_TYPE_NFT_TRANSFER; return true; } -#undef ELEMENTS_MUST_BE_EQUAL \ No newline at end of file +#undef ELEMENTS_MUST_BE_EQUAL +#undef ELEMENT_MUST_BE_ADDRESS \ No newline at end of file diff --git a/app/src/script_parser.h b/app/src/script_parser.h index 9a9db20e..0b9ed60f 100644 --- a/app/src/script_parser.h +++ b/app/src/script_parser.h @@ -7,7 +7,7 @@ extern "C" { #endif -#define MAX_SCRIPT_PARSED_ELEMENTS 9 +#define MAX_SCRIPT_PARSED_ELEMENTS 11 typedef enum { SCRIPT_TYPE_UNKNOWN = 0x00, diff --git a/tests/script_parser.cpp b/tests/script_parser.cpp index c30bfee2..fc09f6a4 100644 --- a/tests/script_parser.cpp +++ b/tests/script_parser.cpp @@ -78,7 +78,7 @@ testing::AssertionResult PARSE_NFT_TEST(NFTFunction parseNFTFunction, std::strin //templates 3-5 should always fail const char * TEMPLATE1 = "abb\001 aa \001"; const char * TEMPLATE2 = "abb\001 aa\001 a"; -const char * TEMPLATE3 = "\001 \001 \001 \001 \001 \001 \001 \001 \001 \001 \001"; +const char * TEMPLATE3 = "\001 \001 \001 \001 \001 \001 \001 \001 \001 \001 \001 \001"; const char * TEMPLATE4 = "\001\001"; const char * TEMPLATE5 = "\001a"; const char * TEMPLATE6 = "\001 a"; @@ -92,7 +92,7 @@ TEST(script_parse, parseScript) { EXPECT_TRUE(PARSE_TEST("abbxxy aa y?", TEMPLATE1, false)); EXPECT_TRUE(PARSE_TEST("abbxxy aa y a", TEMPLATE2, false)); EXPECT_TRUE(PARSE_TEST("abbxxy aay a", TEMPLATE2, true, {"xxy", "y"})); - EXPECT_TRUE(PARSE_TEST("a a a a a a a a a a a", TEMPLATE3, false)); + EXPECT_TRUE(PARSE_TEST("a a a a a a a a a a a a", TEMPLATE3, false)); EXPECT_TRUE(PARSE_TEST("aa", TEMPLATE4, false)); EXPECT_TRUE(PARSE_TEST("a a", TEMPLATE4, false)); EXPECT_TRUE(PARSE_TEST("ba", TEMPLATE5, false)); @@ -104,8 +104,8 @@ TEST(script_parse, parseNFT1) { EXPECT_TRUE(PARSE_NFT_TEST(parseNFT1, "", false)); const char script1[] = - "import NonFungibleToken from 0xNONFUNGIBLETOKEN\n" - "import MetadataViews from 0xMETADATAVIEWS\n" + "import NonFungibleToken from 0x631e88ae7f1d7c20\n" + "import MetadataViews from 0x631e88ae7f1d7c20\n" "import aaa from bbb\n" "transaction {\n" " prepare(acct: AuthAccount) {\n" @@ -126,12 +126,13 @@ TEST(script_parse, parseNFT1) { " }\n" "}\n"; - EXPECT_TRUE(PARSE_NFT_TEST(parseNFT1, script1, true, {"aaa", "bbb", "c", "aaa", "c", "x", "_", "zzzzzzZ", "c"}, + EXPECT_TRUE(PARSE_NFT_TEST(parseNFT1, script1, true, {"0x631e88ae7f1d7c20", "0x631e88ae7f1d7c20", + "aaa", "bbb", "c", "aaa", "c", "x", "_", "zzzzzzZ", "c"}, SCRIPT_TYPE_NFT_SETUP_COLLECTION)); const char script2[] = - "import NonFungibleToken from 0xNONFUNGIBLETOKEN\n" - "import MetadataViews from 0xMETADATAVIEWS\n" + "import NonFungibleToken from 0x631e88ae7f1d7c20\n" + "import MetadataViews from 0x631e88ae7f1d7c20\n" "import aaa from bbb\n" "transaction {\n" " prepare(acct: AuthAccount) {\n" @@ -155,8 +156,8 @@ TEST(script_parse, parseNFT1) { EXPECT_TRUE(PARSE_NFT_TEST(parseNFT1, script2, false)); //storages do not match const char script3[] = - "import NonFungibleToken from 0xNONFUNGIBLETOKEN\n" - "import MetadataViews from 0xMETADATAVIEWS\n" + "import NonFungibleToken from 0x631e88ae7f1d7c20\n" + "import MetadataViews from 0x631e88ae7f1d7c20\n" "import aaaa from bbb\n" "transaction {\n" " prepare(acct: AuthAccount) {\n" @@ -184,7 +185,7 @@ TEST(script_parse, parseNFT2) { EXPECT_TRUE(PARSE_NFT_TEST(parseNFT2, "", false)); const char script1[] = - "import NonFungibleToken from 0xNONFUNGIBLETOKEN\n" + "import NonFungibleToken from 0x1d7e57aa55817448\n" "import aaaa from bbb\n" "transaction(recipient: Address, withdrawID: UInt64) {\n" " // local variable for storing the transferred nft\n" @@ -217,11 +218,11 @@ TEST(script_parse, parseNFT2) { " }\n" "}\n"; - EXPECT_TRUE(PARSE_NFT_TEST(parseNFT2, script1, true, {"aaaa", "bbb", "ststst", "aaaa", "aaaa", "ststst", "publicPath"}, + EXPECT_TRUE(PARSE_NFT_TEST(parseNFT2, script1, true, {"0x1d7e57aa55817448", "aaaa", "bbb", "ststst", "aaaa", "aaaa", "ststst", "publicPath"}, SCRIPT_TYPE_NFT_TRANSFER)); const char script2[] = - "import NonFungibleToken from 0xNONFUNGIBLETOKEN\n" + "import NonFungibleToken from 0x1d7e57aa55817448\n" "import aaaa from bbb\n" "transaction(recipient: Address, withdrawID: UInt64) {\n" " // local variable for storing the transferred nft\n" @@ -257,7 +258,7 @@ TEST(script_parse, parseNFT2) { EXPECT_TRUE(PARSE_NFT_TEST(parseNFT2, script2, false)); //contractName mismatch const char script3[] = - "import NonFungibleToken from 0xNONFUNGIBLETOKEN\n" + "import NonFungibleToken from 0x1d7e57aa55817448\n" "import aaaa from bbb\n" "transaction(recipient: Address, withdrawID: UInt64) {\n" " // local variable for storing the transferred nft\n" diff --git a/tests_speculos/test-transaction-nft.js b/tests_speculos/test-transaction-nft.js index 9d6e752a..4e043439 100644 --- a/tests_speculos/test-transaction-nft.js +++ b/tests_speculos/test-transaction-nft.js @@ -85,8 +85,8 @@ const buildPayloadTx = (network, partialTx) => merge(basePayloadTx(network), partialTx, {arrayMerge: combineMerge}); -const getTxEnvelope = (script, args) => { - const tx = buildPayloadTx(MAINNET, { +const getTxEnvelope = (network, script, args) => { + const tx = buildPayloadTx(network, { script: script, arguments: args, }) @@ -113,8 +113,8 @@ const pubkeyHex = getPubkeyResponse.publicKey.toString("hex") const publicCollectionName = "publicCollectionName" const publicPath = "publicPath" const script = -`import NonFungibleToken from 0xNONFUNGIBLETOKEN -import MetadataViews from 0xMETADATAVIEWS +`import NonFungibleToken from 0x1d7e57aa55817448 +import MetadataViews from 0x1d7e57aa55817448 import ${contractName} from ${contractAddress} transaction { prepare(acct: AuthAccount) { @@ -136,13 +136,13 @@ transaction { } ` const args = [] - const txBlob = Buffer.from(getTxEnvelope(script, args), "hex") + const txBlob = Buffer.from(getTxEnvelope(MAINNET, script, args), "hex") console.log("Script") console.log(script) console.log("Tx built") console.log(txBlob.toString("hex")) - testStep(" - - -", "NFT 1 correct"); + testStep(" - - -", "NFT 1 mainnet correct"); const signPromise = app.sign(path, txBlob, options, "nft1"); await device.review("Review transaction"); const signResponse = await signPromise; @@ -166,7 +166,7 @@ transaction { const storagePath = "storagePath" const publicPath = "publicPath" const script = -`import NonFungibleToken from 0xNONFUNGIBLETOKEN +`import NonFungibleToken from 0x1d7e57aa55817448 import ${contractName} from ${contractAddress} transaction(recipient: Address, withdrawID: UInt64) { // local variable for storing the transferred nft @@ -209,13 +209,13 @@ transaction(recipient: Address, withdrawID: UInt64) { "value": "123456" }, ] - const txBlob = Buffer.from(getTxEnvelope(script, args), "hex") + const txBlob = Buffer.from(getTxEnvelope(MAINNET, script, args), "hex") console.log("Script") console.log(script) console.log("Tx built") console.log(txBlob.toString("hex")) - testStep(" - - -", "NFT 2 correct"); + testStep(" - - -", "NFT 2 mainnet correct"); const signPromise = app.sign(path, txBlob, options, "nft2"); await device.review("Review transaction"); const signResponse = await signPromise; @@ -233,6 +233,196 @@ transaction(recipient: Address, withdrawID: UInt64) { assert.ok(ec.verify(digestHex, signResponse.signatureDER.toString("hex"), pubkeyHex, 'hex')); } +{ + const contractName = "contractName" + const contractAddress = "contractAddress" + const storagePath = "storagePath" + const publicCollectionContractName = "publicCollectionContractName" + const publicCollectionName = "publicCollectionName" + const publicPath = "publicPath" + const script = +`import NonFungibleToken from 0x631e88ae7f1d7c20 +import MetadataViews from 0x631e88ae7f1d7c20 +import ${contractName} from ${contractAddress} +transaction { + prepare(acct: AuthAccount) { + let collectionType = acct.type(at: /storage/${storagePath}) + // if there already is a collection stored, return + if (collectionType != nil) { + return + } + // create empty collection + let collection <- ${contractName}.createEmptyCollection() + // put the new Collection in storage + acct.save(<-collection, to: /storage/${storagePath}) + // create a public capability for the collection + acct.link<&{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, ${publicCollectionContractName}.${publicCollectionName}, MetadataViews.ResolverCollection}>( + /public/${publicPath}, + target: /storage/${storagePath} + ) + } +} +` + const args = [] + const txBlob = Buffer.from(getTxEnvelope(TESTNET, script, args), "hex") + console.log("Script") + console.log(script) + console.log("Tx built") + console.log(txBlob.toString("hex")) + + testStep(" - - -", "NFT 1 testnet correct"); + const signPromise = app.sign(path, txBlob, options, "nft1"); + await device.review("Review transaction"); + 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-transaction"); + const hasher = new jsSHA(SHA3_256.name, "UINT8ARRAY"); + hasher.update(tag); + hasher.update(txBlob); + const digestHex = hasher.getHash("HEX"); + const ec = new EC(ECDSA_P256.name); + assert.ok(ec.verify(digestHex, signResponse.signatureDER.toString("hex"), pubkeyHex, 'hex')); +} + +{ + const contractName = "contractName" + const contractAddress = "contractAddress" + const storagePath = "storagePath" + const publicPath = "publicPath" + const script = +`import NonFungibleToken from 0x631e88ae7f1d7c20 +import ${contractName} from ${contractAddress} +transaction(recipient: Address, withdrawID: UInt64) { + // local variable for storing the transferred nft + let transferToken: @NonFungibleToken.NFT + prepare(owner: AuthAccount) { + // check if collection exists + if (owner.type(at: /storage/${storagePath}) != Type<@${contractName}.Collection>()) { + panic("Could not borrow a reference to the stored collection") + } + // borrow a reference to the collection + let collectionRef = owner + .borrow<&${contractName}.Collection>(from: /storage/${storagePath})! + // withdraw the NFT + self.transferToken <- collectionRef.withdraw(withdrawID: withdrawID) + } + execute { + // get the recipient's public account object + let recipient = getAccount(recipient) + // get receivers capability + let nonFungibleTokenCapability = recipient + .getCapability<&{NonFungibleToken.CollectionPublic}>(/public/${publicPath}) + // check the recipient has a NonFungibleToken public capability + if (!nonFungibleTokenCapability.check()) { + panic("Could not borrow a reference to the receiver's collection") + } + // deposit nft to recipients collection + nonFungibleTokenCapability + .borrow()! + .deposit(token: <-self.transferToken) + } +} +` + const args = [ + { + "type": "Address", + "value": "Ny nice address" + }, + { + "type": "UInt64", + "value": "123456" + }, + ] + const txBlob = Buffer.from(getTxEnvelope(MAINNET, script, args), "hex") + console.log("Script") + console.log(script) + console.log("Tx built") + console.log(txBlob.toString("hex")) + + testStep(" - - -", "NFT 2 testnet correct"); + const signPromise = app.sign(path, txBlob, options, "nft2"); + await device.review("Review transaction"); + 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-transaction"); + const hasher = new jsSHA(SHA3_256.name, "UINT8ARRAY"); + hasher.update(tag); + hasher.update(txBlob); + const digestHex = hasher.getHash("HEX"); + const ec = new EC(ECDSA_P256.name); + assert.ok(ec.verify(digestHex, signResponse.signatureDER.toString("hex"), pubkeyHex, 'hex')); +} + +{ + const contractName = "contractName" + const contractAddress = "contractAddress" + const storagePath = "storagePath" + const publicPath = "publicPath" + const script = +`import NonFungibleToken from 0x631e88ae7f1d7c21 +import ${contractName} from ${contractAddress} +transaction(recipient: Address, withdrawID: UInt64) { + // local variable for storing the transferred nft + let transferToken: @NonFungibleToken.NFT + prepare(owner: AuthAccount) { + // check if collection exists + if (owner.type(at: /storage/${storagePath}) != Type<@${contractName}.Collection>()) { + panic("Could not borrow a reference to the stored collection") + } + // borrow a reference to the collection + let collectionRef = owner + .borrow<&${contractName}.Collection>(from: /storage/${storagePath})! + // withdraw the NFT + self.transferToken <- collectionRef.withdraw(withdrawID: withdrawID) + } + execute { + // get the recipient's public account object + let recipient = getAccount(recipient) + // get receivers capability + let nonFungibleTokenCapability = recipient + .getCapability<&{NonFungibleToken.CollectionPublic}>(/public/${publicPath}) + // check the recipient has a NonFungibleToken public capability + if (!nonFungibleTokenCapability.check()) { + panic("Could not borrow a reference to the receiver's collection") + } + // deposit nft to recipients collection + nonFungibleTokenCapability + .borrow()! + .deposit(token: <-self.transferToken) + } +} +` + const args = [ + { + "type": "Address", + "value": "Ny nice address" + }, + { + "type": "UInt64", + "value": "123456" + }, + ] + const txBlob = Buffer.from(getTxEnvelope(MAINNET, script, args), "hex") + console.log("Script") + console.log(script) + console.log("Tx with invalid contract address build") + console.log(txBlob.toString("hex")) + + testStep(" - - -", "NFT 2 testnetinvalid address"); + const signPromise = app.sign(path, txBlob, options, "nft2"); + const signResponse = await signPromise; + assert.equal(signResponse.returnCode, 0x6984); + assert.equal(signResponse.errorMessage, "Data is invalid : Unexpected script"); +} + await transport.close() testEnd(scriptName); diff --git a/tests_speculos/test-transaction-nft/nanos.40.png b/tests_speculos/test-transaction-nft/nanos.40.png new file mode 100644 index 0000000000000000000000000000000000000000..984a817985a09edacf333fab009a642b12272fd7 GIT binary patch literal 364 zcmV-y0h9iTP)4uZjsrg+Lrl-{A1w%ge9$#jTvn@ULfE?ssL+i&*A#-? zPzr2Ds_DDr@ye}6{g3o-GcquI z(7PtWG4uZ42?sMg1=}XeJ$~PAa`>NXVbg>Kdae<{3U0wGjxz?eM6x!0c*c2QsUj;- zdEfh-hTus`X_^8|-{cZwdo%=$*3WHt<2YNxH6lO(7-Dd6`6u)4zbv;De(P9+#5`U7 KT-G@yGywqYCYOl- literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.42.png b/tests_speculos/test-transaction-nft/nanos.42.png new file mode 100644 index 0000000000000000000000000000000000000000..a5231df298377749b76ae510ed58c515b22bf565 GIT binary patch literal 394 zcmV;50d@X~P)>oqNVb1p*;FVy|;C`{o4f+xb|a;s#rFr`xr$eu=&q zv2DrFm)C2xe2|e2x16QZD(sOp48w%lcwk=`2Wwgrle}@}`f1ptGw#Iaq1S%=>=G;W zEgTny-~EMbS8Vi4?MvMXoEdeXlf!&=F57VykR_F{vs7l3JD{}&(Fr)NAm&^=4LRwZ zDDET@jo?pMW9KA6OHJlv0C66+!?b>%>-s18X=wV{f-On4z;EvX$w(xlHhp57k%{Gf z5<@b#aHiQ9H3L@~uJqc8sXGP$0R9TgB1D0?-Q;St)Ciw)4a9VarQM{T=6AyVYJ@2f z;#w;I67NwX-XcPz7`TE;45<-s5g{^EuOM}X)Cl_`1c%`hFp6;zqJZ8BASv`tHG7n2 z8g9pN)*pBlIjCwOH_FV0-LiKs+b49yW2!@Y1d@f0 zsotQg{&95c4YKN^6rs$d?LYzeZ2mv$RpoVTI zEP$P`wqtD#u={s>0c#!bIZUM>r}s9)U@&0e2UnL=y^~g^Gynhq07*qoM6N<$g440h A8vp_xRD5C|^`Qz`ejP=+MTB;s7i>*++io}W*(B$J5f1$7}ZlPqbg zEpWONc7#>He$i<}#yFA54*+g?b7FP=lX`nJQ*eq(z8_FUNU^MWj7*-}Bh%!tYmTfW zg{o5+5Uck8Qd;#sulRsbC$gk8(rRe8x}4eJBBSF3Dtg!T$!{_0IXE5KYU3s>suit( ztTiB)>vtaMj8-&1?&o_XYw&R-qK9qcn@nc8kFpz{!%QNhC411F=}A_pFx3lcELkqK zmanqeY&JiKIuy|`0wFaIsRc%2%J_Pb0Wf|UlWQbAoOQQJUBwvN0Pr3o{hpKz<&=t9 zA`ndEApDv5Kzyy_ZZ?_h1x}Gz+CZuyJVX>C++w_qHMXEmtn635`RW0fzeUu!*Ne<$ z=tAjDrkQbKqC%q_(*sEY6c)9(>OkC;kFj81%3R5eb(EOTSPLu5Rt0%npY ztk_Z}K4R#}yikPhMU7F&dclcH`2mZ++fboe0ixFcr9Uo`u0-Mlj*Nk5JhCeIqg5SK i*~pl;ur9R)eItKat+q1I>5r@c0000Vw6ouMP$1dxk2n|edO8!Y=l3}+sU#x0pcdjY%d*zG z1x>3Wj@SfjmpVnHk29Zo2jG!+CzbQx)Mf1^u)E6M15yYnRt!(^sk5wwD#uL|va%E@ zR}nxe?KdyIT3;6qC~~Gqj(WNp*{rSsKY>CYTc3TKsUCyFk+p7IgiTh-3@Ane z@^kI3V;#{I)lb{`k@&iKJ2G)aZ1-1LjB=l3cie}SL`9{z(e3G3)=^=p3o0H(ZjDwq zA%u`Wr{lE&VqR@B!J*@EUc^eM)v|8S70v^VKQ43N&x;_)^T}x$(3LwFw%L!1ZY2*? zLoOk%0-4vvqn_yn@dG9*OCRX7aq*1kGy^(v!xAy@5<)Ov2aKB0H(WxLxL^}@(`|GG zrnz_NF$1zas^!{17LzIaCahRl!*<3&Q>RD>A%qY@2qApZX52yL?hukPmx}Rz)$RiluDA z=k~6DHuk7AXlgx)rp1i+hz7ttJd0{edJs*mbQ~x1_Szf(%_^R9#ZaY~A-w`dLZz57 zmrX!V+EzfC6SDi5NTngQ?59PTnHMuCG>^<5IG(dW3L1$~W+mV;M7)uLjG3$*Xn;5% t0S!$4)guj3kTD2=F$jP$2mk=UkRP0A3fKQGc|-sJ002ovPDHLkV1g9!t9JkZ literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.47.png b/tests_speculos/test-transaction-nft/nanos.47.png new file mode 100644 index 0000000000000000000000000000000000000000..f2422205b6e1b91dbe0b798b0b95a8c4d2bf7e92 GIT binary patch literal 337 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!2~2j9iA64FfdAZx;TbZ%y~QGv{17Gk89(> z|Nj%j%N)B6A1=INcyMR>9SMUal?=VbQ*ukYa$f(7*|glYH*2!DjZbzg_r2+dqD{oN z-t2I^v$QU7LtBuE!tB}ZFZn$VWFXIURu0Aky+I2)#4eC-(I}c)&DWt)+)TG zGf`*B>G=Zr?dvs;CW!qp^4;apw^QyL<5vlvDU63Z=BmlHiC)%fjhEZ$+UC5p&g*>i z4u%HvM_YLnHq4d1y_7GU|3)M8%}b@HgIHCb#5g&i6|V3p%9DK|-FcelF{r5}E+5U5S_g literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.48.png b/tests_speculos/test-transaction-nft/nanos.48.png new file mode 100644 index 0000000000000000000000000000000000000000..05f27c3a1261e43d42b7e462d3f1e8f54f927ee8 GIT binary patch literal 649 zcmV;40(Sk0P)Qi3buy{@WDSA}D(rPjB|h)8y;2aL_u0nU4oM+X4$og* z69d9oc~+}VV0-D3wXhdxA!6kb8;nltlRDfrH`3_Q7Q*#daydM{mF#D4JoZV$Ilj~Y zbm_V51j!G8v+J)(+a^IPH5lq~FO6}48iP!v@W-noTy9VY z1~8=#ASvQI^OwUAc0hfl^29*=ho?*HfVXhHoXj%2Fd_>J3kwrLQxTx(s5>vY1dBGn zrN4D|W0?p+o$R$(J>{n%F9DzdKyhzLmOVJdW|ViN5ZV|ERRhGxG?eEORF)et7bwH@dRdAfg@EU& jYKxfbl1eNr{3HAV_0#cv`)$N600000NkvXXu0mjfy1gh5 literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.49.png b/tests_speculos/test-transaction-nft/nanos.49.png new file mode 100644 index 0000000000000000000000000000000000000000..5ebc566538c13f46cb76bcb35bd4c7f3819742a1 GIT binary patch literal 648 zcmV;30(bq1P) zRs{x(Bl-5Ye9qm)Q_Hv(C=ro)Pah1Y^+Iw%XG4f(A*)(i#ugh68xvX# zR!aynV7C)Ly}0I&7*PQm_gDfO^4us&7g!wu>ID%1_YMGyF0b~$d{mX^#Uc`ir>Y&r zRWA(YDq5_8w&W^aRwFI>-Gc*8TbQo%ufi+-6;sGCK^4??<-z-mL@p&2hfEcqgk;U| zywN%V;&3e9DlKE-$Xf;@?ao8P<;>xyvwZ@pRA0;38dH*1V32q)7~V%vt^U(<-3O4M5PGDA|A7x3L2Ujv*Cu-p;(p*Q~(f+W6pq z{RaMb3ugxjtlGht`u)g^2rb>U+t_+JZ-!l-k(=s%^zEW2k9u=8ZoU=z%b8Y@c2PcP z_m(nM_ZLc)TfJS>zqh(fb}^nH#rg71&d-$TGo?S)KH;)F>%_^`9<Zn@c>HH%tb|K|aE84l`Ca9{Y>YqLk?M<7Vd)78&qol`;+ E0A^=~jQ{`u literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.51.png b/tests_speculos/test-transaction-nft/nanos.51.png new file mode 100644 index 0000000000000000000000000000000000000000..7a7c2869b2dd625819cffe6211e5ab5dc7e04cf3 GIT binary patch literal 471 zcmV;|0Vw{7P);ww!SGvj$f6k&N!K}ubmW732sb%q97 z8~3cC(7YWNmcJJuw$9hp*2{Fe{~oIl!uF5LmnkcQSS_1~l3WKZKN49t(t|2*EDLk* zGknAP4Q##%A>%~LsGklC%~94scR%~2boL?tm1)*x;j8YSetjuZ zOlA**%bnJ+-A22_E?0a%9$wr(+uP&tajX7&)2*N0Yuf42+%s8SpW3UsUGgX8FbdA@w|{p8t;rRnH3X@qE6br60-V}tMSx!&-*6%5%ki@!l&X_$kBS%?&Q7JN z_t(By2~Dfzt5fJPHSQ%~K3YFt8{JKZ{3VVhUp$OZhxBS@X#CG5GfHdlc5Ws|xvpj%*4yF)UutWtGO;QX&jEN8GnQI(Mha;XNkG40_ zqMp9_?#i{Jm}zgWd2l;c**-J4rqUS?f8WIiOg@vmdE`ycXSieHZcMrfA>>G19HFR}^G#%Tdu`YQP!G3iZ&YF?>Lwi&1p~W{dV0YI zVkd}4eqPFh2hJ*@fUlkcy1#(lEe>jOU<37~xQ#OaEJ2nh80g9XH8(|rR2M4>Wa_&H zo}HVdcF`UHuw2xu3YMbz+UjSxZzA%qY@2qA=!f8z!AfH&5QbvV7)o3Uit4j#z%4STr(ii|JnE!L6__1ZrVGQxY^n!14tcb>%M8z+@)Xx zvk>%$+!yIv5Wj$_N1Uz<@JtTP9JnHqF)_>z-<9$&a>M-t;_CUjSRLmnRm1-JJSnjBBfqT5;Az=j8exhE~(DGeQFQ8IoM};WSmX* zXTS#F^dV?)U#3o=)n0#_vK<|VWpDgtq{GJXYddRSKOHVU$Tr`Az1ZBNmmss-PTl_@ z3gKfMhTlE{uJAZAU&aWzYkkOMG9mQX&a99afuhb`I}IV|DN7>|UPMv<9d)liqUaIMiVeZ|YUd+CY-IF8>~K zzhRaAS`H?-=QM1i=Tu6{1-oD!M<5AJP1 literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.58.png b/tests_speculos/test-transaction-nft/nanos.58.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;)U3ec8u<3c z|NjYMJG7WpmaK4yxwBb|kL5^v+5Kf*3p3u`KR8uiv}fUq`Ll{922Z!$aH}=)etfg6-F0Ac{FYFhd@Or_0cMr;Kn)Lp=JkSepa6m|1?pWa*p){>4ATdu@KbLh* G2~7ZXxN(61 literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.60.png b/tests_speculos/test-transaction-nft/nanos.60.png new file mode 100644 index 0000000000000000000000000000000000000000..1eba76f2bd9fa2851ddea1aa4c7ed4e7ee65cc16 GIT binary patch literal 367 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!2~2j9iA64Fff{Vx;TbZ%y~QGE?=_(Pix`9 z@AV1#cUS`(w{-+8RC|7Ulg^(lOGP63iVc513%RlVwefTvhU||a-)+BKOkP!~XKl!x z_KR(T#Gk9X)@0d51TTNQ*_=<+q;1-Vy=zX2Sl!@coma_UyxHxfX{mc{sajX3`*yu0 zUQ71BmW~WIz3#nd@tYZ^61c=}S-h!u-XgWp?fj{iOwwyKr)7$qDP*lp;(rlqcxhHx z(IuPzLhQGmpBCs^{kkz^r~j@{GfN%Q>Pzr2Ds_DDr@ye}6{g3o-GcquI z(7PtWG4uZ42?sMg1=}XeJ$~PAa`>NXVbg>Kdae<{3U0wGjxz?eM6x!0c*c2QsUj;- zdEfh-hTus`X_^8|-{cZwdo%=$*3WHt<2YNxH6lO(7-Dd6`6u)4zbv;De(P9+#5`U7 KT-G@yGywqYCYOl- literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.61.png b/tests_speculos/test-transaction-nft/nanos.61.png new file mode 100644 index 0000000000000000000000000000000000000000..a5231df298377749b76ae510ed58c515b22bf565 GIT binary patch literal 394 zcmV;50d@X~P)>oqNVb1p*;FVy|;C`{o4f+xb|a;s#rFr`xr$eu=&q zv2DrFm)C2xe2|e2x16QZD(sOp48w%lcwk=`2Wwgrle}@}`f1ptGw#Iaq1S%=>=G;W zEgTny-~EMbS8Vi4?MvMXoEdeXlf!&=F57VykR_F{vs7l3JD{}&(Fr)NAm&^=4LRwZ zDDET@jo?pMW9KA6OHJlv0C66+!?b>%>-s18X=wV{f-On4z;EvX$w(xlHhp57k%{Gf z5<@b#aHiQ9H3L@~uJqc8sXGP$0R9TgB1D0?-Q;St)Ciw)4a9VarQM{T=6AyVYJ@2f z;#w;I67NwX-XcPz7`TE;45<-s5g{^EuOM}X)Cl_`1c%`hFp6;zqJZ8BASv`tHG7n2 z8g9pN)*pBlIjCwOH_FV0-LiKs+b49yW2!@Y1d@f0 zsotQg{&95c4YKN^6rs$d?LYzeZ2mv$RpoVTI zEP$P`wqtD#u={s>0c#!bIZUM>r}s9)U@&0e2UnL=y^~g^Gynhq07*qoM6N<$g440h A8vppZX52yL?hukPmx}Rz)$RiluDA z=k~6DHuk7AXlgx)rp1i+hz7ttJd0{edJs*mbQ~x1_Szf(%_^R9#ZaY~A-w`dLZz57 zmrX!V+EzfC6SDi5NTngQ?59PTnHMuCG>^<5IG(dW3L1$~W+mV;M7)uLjG3$*Xn;5% t0S!$4)guj3kTD2=F$jP$2mk=UkRP0A3fKQGc|-sJ002ovPDHLkV1g9!t9JkZ literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.64.png b/tests_speculos/test-transaction-nft/nanos.64.png new file mode 100644 index 0000000000000000000000000000000000000000..2ab1484075defe1e2767c45b891fe0ae0287540c GIT binary patch literal 329 zcmV-P0k-~$P)1)=JvD68AkUeV)P(3!~3p3l~CpO?BCRFuUJ{ zDA^~Y<2ZTOY812k0c-7j37XXh@!~*wtAU+Gm2+s=!h65pdk2XsZqJgI8jci|uFPlcq}2Yt=~fwbU$no_)5NoJ%$w;7-kVjw5T>O!pO z#Q_wfie3EECM;m%rQ0BoA|6lcf9?PP000000001ZFkb^N_XR=ErVaoA002ovPDHLk FV1jB}v1kAQ literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.66.png b/tests_speculos/test-transaction-nft/nanos.66.png new file mode 100644 index 0000000000000000000000000000000000000000..5064ec2ba3863073372ac327cd43701ddfcc7ccc GIT binary patch literal 340 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!2~2j9iA64FfdAcx;TbZ%y~P*QK(sg$CY{U zcl!<9Web%AI9^65Zu#HK?QN&3BEi;?6enH!Y^!SjnmzW{gzKyCv$wuYG;#j##&o0O z^wn3_?ycis(cc-qarI1{d9!WiHm*LqR_7M;UQH{3|2*%1cPFfhWUURn)?OUrU-ImT z-5=xJcQR(3YLEJtX%-!rTk`s&kISx}KdCcJ*ozO^JyJE4j`S*=!a9A5(az-b9v2A) zhK`OCy=4li`e(Nmhiz1SeQxTncmMh9vr?C(b$?Pw!>+#BI(6zk2J9FT_aZ jDWrJ|0fPkw9&qnrl*!zvvZqK@2_)v}>gTe~DWM4fiN%TF literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.67.png b/tests_speculos/test-transaction-nft/nanos.67.png new file mode 100644 index 0000000000000000000000000000000000000000..05f27c3a1261e43d42b7e462d3f1e8f54f927ee8 GIT binary patch literal 649 zcmV;40(Sk0P)Qi3buy{@WDSA}D(rPjB|h)8y;2aL_u0nU4oM+X4$og* z69d9oc~+}VV0-D3wXhdxA!6kb8;nltlRDfrH`3_Q7Q*#daydM{mF#D4JoZV$Ilj~Y zbm_V51j!G8v+J)(+a^IPH5lq~FO6}48iP!v@W-noTy9VY z1~8=#ASvQI^OwUAc0hfl^29*=ho?*HfVXhHoXj%2Fd_>J3kwrLQxTx(s5>vY1dBGn zrN4D|W0?p+o$R$(J>{n%F9DzdKyhzLmOVJdW|ViN5ZV|ERRhGxG?eEORF)et7bwH@dRdAfg@EU& jYKxfbl1eNr{3HAV_0#cv`)$N600000NkvXXu0mjfy1gh5 literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.68.png b/tests_speculos/test-transaction-nft/nanos.68.png new file mode 100644 index 0000000000000000000000000000000000000000..5ebc566538c13f46cb76bcb35bd4c7f3819742a1 GIT binary patch literal 648 zcmV;30(bq1P) zRs{x(Bl-5Ye9qm)Q_Hv(C=ro)Pah1Y^+Iw%XG4f(A*)(i#ugh68xvX# zR!aynV7C)Ly}0I&7*PQm_gDfO^4us&7g!wu>ID%1_YMGyF0b~$d{mX^#Uc`ir>Y&r zRWA(YDq5_8w&W^aRwFI>-Gc*8TbQo%ufi+-6;sGCK^4??<-z-mL@p&2hfEcqgk;U| zywN%V;&3e9DlKE-$Xf;@?ao8P<;>xyvwZ@pRA0;38dH*1V32q)7~V%vt^U(<-3O4M5PGDA|A7x3L2Ujv*Cu-p;(p*Q~(f+W6pq z{RaMb3ugxjtlGht`u)g^2rb>U+t_+JZ-!l-k(=s%^zEW2k9u=8ZoU=z%b8Y@c2PcP z_m(nM_ZLc)TfJS>zqh(fb}^nH#rg71&d-$TGo?S)KH;)F>%_^`9<Zn@c>HH%tb|K|aE84l`Ca9{Y>YqLk?M<7Vd)78&qol`;+ E0A^=~jQ{`u literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.70.png b/tests_speculos/test-transaction-nft/nanos.70.png new file mode 100644 index 0000000000000000000000000000000000000000..41b0ce2b951314b34dcb1c5718b6dca8e065af5e GIT binary patch literal 472 zcmV;}0Vn>6P)Nkla)G~QywM^fN;3q|GwUxTS_&2HWWQZ%2Hya&d^U(>U=3@ze~Y&NS$K`S)I8> z2K-N^Q!+QwzD~)Qy=Wu4#-uNN26S9ls!sn&!(v9fZxKPaTw9Q`F4`Ehp-G)Z4Xy2W z)(~jk?t!bx7|S(=(mulN783L$KN6i-tYc4BmFdX(@wNcoY-bt65f^7^uH>}|RS ztiQl!n*acwM3J8i1kKPeRVCxrv$5p!AElCHEmOmc-A{HL6@IzozlQuaevxF&iHuag zhN|4xZkt4H@GpPV_$v~5qd?WBI&eAz9(C$Ok1~^yswFcblj3;)I`w1)_+H&TigE^i zJ+h)F$scR%~2boL?tm1)*x;j8YSetjuZ zOlA**%bnJ+-A22_E?0a%9$wr(+uP&tajX7&)2*N0Yuf42+%s8SpW3UsUGgX8FbdA@w|{p8t;rRnH3X@qE6br60-V}tMSx!&-*6%5%ki@!l&X_$kBS%?&Q7JN z_t(By2~Dfzt5fJPHSQ%~K3YFt8{JKZ{3VVhUp$OZhxBSzT+2?ksTh0vEL67B_>waqJuzMQVV4v`VAsS4G;)2St#_}TU* zn$tg4I z1)#5~t~Zbh$m%sMD@}-DyTYx>0RR910000000000kof}qe>LcCF21+`0000EpLbPNSZ?Rk z{xAwJP%X(2P#5~@c{`szUR5CtDJ|%J%|CvB)#P${@*#i1T{Tp1s`Ki4XbMl}B$~`D z^*UF;=r^*AYNe{JAr zT#I< zS9X7B0cxPwlQCM;moqa)t=$k%o6&(4)bl=bm5>aFR#_ zV_T4j{(qL+1q~oiC}$!Mf9RT_p0){DOG#FhYjgtiRFhG00000000000001h9A8SATkiagEVBRr002ovPDHLkV1it}%}f9Q literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.75.png b/tests_speculos/test-transaction-nft/nanos.75.png new file mode 100644 index 0000000000000000000000000000000000000000..52b06db91d6825dc858bbdf6384330dc370de38e GIT binary patch literal 512 zcmV+b0{{JqP)9$&a>M-t;_CUjSRLmnRm1-JJSnjBBfqT5;Az=j8exhE~(DGeQFQ8IoM};WSmX* zXTS#F^dV?)U#3o=)n0#_vK<|VWpDgtq{GJXYddRSKOHVU$Tr`Az1ZBNmmss-PTl_@ z3gKfMhTlE{uJAZAU&aWzYkkOMG9mQX&a99afuhb`I}IV|DN7>|UPMv<9d)liqUaIMiVeZ|YUd+CY-IF8>~K zzhRaAS`H?-=QM1i=Tu6{1-oD!M<5AJP1 literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanos.77.png b/tests_speculos/test-transaction-nft/nanos.77.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;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&_( O0000YMY+BBwOuz7f}N4U_y6zE4l|_v(E88416FzbJi;7T{80U{IKD^t)YDI=%Yk&K=jg z_gj~#Jcx9hzfCG??kus(8^!7)1WRVGdCFb$JN@sQkjjo%3SYUGrkg9hm3b#J`%k^Y z{B+Towf#N^W}iB`W#)(3ZXZRT$}af%dfiqFsY;IDhyTd!Pm;>1+0y*MNQ-rrquMW{ wsR6>ZhD8R#p&84X6(%B+~BW^XH|FWv^{{)b*r>mdKI;Vst0MuKv>;M1& literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.43.png b/tests_speculos/test-transaction-nft/nanox.43.png new file mode 100644 index 0000000000000000000000000000000000000000..a5eebf2fd5d947746fb37a9705f86a514bb2a30e GIT binary patch literal 469 zcmV;`0V@89P)CbaA{Beo0cvUV%Ijyptd?uhq-F;~!gdoTBcCMiOjQqg$x4v>*4Vu%C!kX#T zGE}KHj+*wpczlDbg-lu%B5#KfY8RPzm9r)Hsvx(_$TGv)oS8?>+4TgJnUxpQW==+J zq$gU8Q5`rMM@_$ry|18kwa4hQksVi>U9X@rMaQJ)LaisTV}n(ocebUBW(46UFy{PEY##V&+jqGZ+71M~>iL7-l%QL3E zxvOfXRAyD4(Y%V+XxxuxORGI-hPpjPqj%Ps!;bEoa(Xi^o9=+#{)FIu-XX7Ab2r80~%fF2XnEJ^W_*AF4&B z`j0V6P4yqf9DEXQp-;LgGIOvM$<@>$4X`Hr?&2JkXXz zB-{bsbrg@#9SpA|7)@n=SJ-$2dz(IU2c%G!+nG2AhHXIIeB-3=Z8~89Y`zj_zj!D) z*{FHwSlB_GtmjWj{2SVxgo~J3yEVOCUhra`54Jhg&%r$Dh4rrJD<)w8006w+;3eq5 z`|v%Ue-l2O1-tU2mh)aAZ8fx*mm}|8$=0!}q29`9ce!ow?h{`G9rS#9USGjmiIm&% z!fvQR6J*q^nH%HUNOplvVSwi;7L4%o-|1^V{j?b3jhEB000000000000000T;?Aa&CXE@cvv9- O0000{MKoyd07jMO#(4idAl%RJJ0Bs(`Y7AB$Rz+5ky0XR%jN zE4M2)r97IzEy&M7msW|`QEAclXHfZ0Lbjmplx(DCGbDG3vq!8ai8_Y*?@#9?T$Qgr z)lz<$lvK;5tH&M(-a6CwkaJdV>=)U}mx(#Zi$npl;&R)qaX>2^1jycF=ivz!jn5$o2CaZjG5)$M9!-tUgtQRm{Ub$@Ah z#YU>3UjeF>w$@o2^u~&vwJz_>_Gky0+k#4otwo{L{U2<;AF*Dwbz>WKr4=#qZ*N@H zLLc;?_1n=E;FrA9zm#!l&8l1AO37L4RVBWvZaO>VyYsm1yixzRb!F>Y4jiw?Pe5&< zf8Ed{(YI&wkFu_fzWbkzH=-G8a)H&3VOR0J_W4(qL3t)I=!DTjDYj3Lo z+g{fnv&l?(73^z(&GF@U29~W&wogF?A-~zwTYEWz&^P@($r)i~0outisrymsbZuj? zUu}AQtnX#>TDu?t00000015WptKK3Cj+(bOO{m%QG+treEad_SUdm@P6u{YP&UV-P zN>YTN3$lL`%Q{ZajNL;iWlmX=Ow~3`&a)?EDsyYy?<<0Is)cBQ=8L`lI~+HfP@3SL z%V)LWI|X-jfNkQns`H`%}0AhOve zf!142z33>6O{aP~boAe1t3b;#F#?pdgTN3Z%2Y-$q&-$cmz+SJ4H8u8G^D*n4b3b5 zed81lXFeVQBq5FHn3@_)Jr#Qkd0C4h(1s`O!0K`M2N{3@0000uihrvd_wDG|BFxHa z<_K{)b18I!VNapP11Nz3lD^IlmT z?47Oa&sa0FMyWXVR!Q96)A_$0pZR2MoIY=%T&>hE`77E9JD%KT_!_;UUjEYkr`Gl+ z>rR{gS?y4LbfY<6TlqEK`%3kXkNnsszVBTi1N&0tcl!bv=Fc=e5InhD(Z)z3C2NDo gVQyduA_JYzyq}haZ(1|sZzV|B)78&qol`;+07#*}+yDRo literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.48.png b/tests_speculos/test-transaction-nft/nanox.48.png new file mode 100644 index 0000000000000000000000000000000000000000..9badf1a4bce6d3b8da93968bf266534552779eb1 GIT binary patch literal 367 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|=-!ba4!+nDh2#F5e*qo`yt| zdo91^J9qzJR`ou8e8$bFXTSd>wfSCRx-10JRIs!CD}xh<;?j^?hQ~g6=Dpv!{c`c` z#NV#g^Xs=w|H`126kGb>*rc8*lPdY%2wwJ1gVa zhLE5AY4tDVf22QHwd7$2Ys#)!*H(4ranHYc`jv20?FYqW={v9R)<)*?&rdkLPsDJ^ zW7Fv3GSR}sMwS=BjRrHB&qtqKbv0n)?DgL381mJ)R)z4*} HQ$iB}w6dFn literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.49.png b/tests_speculos/test-transaction-nft/nanox.49.png new file mode 100644 index 0000000000000000000000000000000000000000..d1eb962cac4f32c7034717609e4e6e0874e6e85b GIT binary patch literal 956 zcmV;t14I0YP)#o^SgRZ_1-H-99)`$V0g`F5k1H3f0FX6xK8{|jQL&7JZUD%& z&X73(Q*2{`LVdNuQ%hXS(fq2zzr0>uGPj_6hmkL+)2sM2S=+tJ!7V5~@6sUNK$bTO z)#jmTlso|Hxvl*`8UZ!cgQ@F9zEmw2VBZ^!pWBh%g6KQxgoSr!e*m(2QWQl|yf3~Q zs@`wO6Btkpfuti(it=_1gbK9@uYEF(m~e6gB}+JDjsfIZ&9%up#iw}ukfn5nQgmS{ zV(b|sC{8nI)ILGYkT24Ei2XUfb0b3oaAy!(?-iyQq^20l223k?$F_dbxy$<`Sd-+W zqj7?}HklDh5aTu%Y$!EYNKl1XIaExC*M5O%zx8&eVZs2YbC8)S{PId9Gz@A(i?!b7 z{~{*e1ZuSt$$cTq;miZT?9|t9C6as>$(qrZnCo2{lua0?Oqgys;A~<>SQ^t4Fq2%_ ze;P`vEBjBT#^9CIF7!&AB-a=mC6->Pri~@-2gZ}EtbTOs1bQEd#VS!<^HICyh%wPo z>u%y0AYw?FJ;ZDHQ6IXc0X7}qqh zUS|0;(hF9tue?=qZAZO60Mb)Hb^1qaM$NthRG3{`HMJ&Es?}5xbeh+jn&=uSCy}Bk zilQir{|fb=^6IVHk{r~tAdpaG1#tWgomR0;>+tHv1$h>r`DyEZ2lwV*Yr2Tc5^aBsbAuzfij@E#JyoeB~pM~}9qfJqV566~h1H#d!x zJx30w6&Hwi0VKxtXC|j>z1h$HIjp#HMc6F3e4003aSpOh%2l=}Pidc}`d z2g^8h?|Wk;tcv5DQc9_N{ldsH$YW%LD}Jwss8m^d>W%eoAmzq}1yF@KS|ztFz@B^2 z-}Mka)j8_l6YW|Bl;`%k;P5KFU9)}qw9Fo0sm3n5tyh^WDBEhEwyZcaP z1*m(I37RTs@7|hQhLPiQk}A0~B_zrmg5D#Hot;*X=@&&BN6K*Dv~UPY&$|uijAO^- zC=qEiD_T-=1vD3JzH0HgfuBmbe=#AVQ{c>TOSCJc&f!=1VK!v~S4il(ipZFr<6H zhK?DT@gj48Jw|@=9DZwTemmQ6@7^VzwIaSrH_2pwn?|b1{$|v{ofw7gG)OXauoUS9 p0000000000000000001%`3L!3i(3%EFfafB002ovPDHLkV1f@{-@*U@ literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.51.png b/tests_speculos/test-transaction-nft/nanox.51.png new file mode 100644 index 0000000000000000000000000000000000000000..6282738092eceb6610f61b605ec478d24dcbd738 GIT binary patch literal 341 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|^K-ba4!+nDh2#v{17Gk3-|3>mz3@bd>NdXhv>FKG;}MYP9Y`C@aipy}Ev`J#w8G8pCpq2H-NC7@wHU zLNZi^s!?g(Sd)HLy6oaX9Y3aRH>WcI8va@Uww_u606^JhL^d9yn-h0`nFSFmkrS_~ zE#AL7vHMb+0j~L(9%THrWfv%-r+2q2KRMK%E#1(jNg0=shgJKj`0>?-}fpX_kt2W4!%~*kktjr77R<9-G?$L?&IiuvgAzUaOz+?qsemIm&G>;1c;f;6AI4 zS$0+9CLH%3UCsY1w@$|U$5332_YXr1-h?XjCXSPd!B%iD00000000000000000000 f00000z&5@C=ne=ajvO7E00000NkvXXu0mjfB4_Z4 literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.53.png b/tests_speculos/test-transaction-nft/nanox.53.png new file mode 100644 index 0000000000000000000000000000000000000000..bff4ff72cbd254541a899dcd315dc05f2d83a2e9 GIT binary patch literal 359 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|`hqba4!+nDh2#BHtkep0I(njFS8>Q#0CLyfe*yFlExrsk?U+y?V0t)&3ubUKMo`0nX;kMgOc0d%ZbTTRW}R z*m}{=4O4GU_f?B!JhbiBr9}ov*wfX|Wt~$(696jm BqdNcq literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.54.png b/tests_speculos/test-transaction-nft/nanox.54.png new file mode 100644 index 0000000000000000000000000000000000000000..c02f5ccbcf0b6ec66070447485920b78c64bdcf9 GIT binary patch literal 423 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|`Jiba4!+nDh2VbkSi29*4l~ zljm;uuex!1T}4}Bi-OQI!AsTqZp}zi{OGlw8>lPd-SK-S$7cMOa*_}_c=zqy=qlIp z=amy`=XOYZ`jA#p`Cp+$LCf&Y;Wy{KukpNN;xIfGGU=9%nciHZ+9a!PwqqsJvk%Gj zS4_F5acp+|qu3++r@7_+thnM8!L%yXzGXtcaqYbm`=tCOFCA!|bo|-dYq1Qa|984a z*abi>@&Dky$DKzPmLJVU zTpg;t@b9&S+cb?|SN)zm&A&(Iz*V0`wXZsxkL=*STO2>9vDa_cbf@EP`)6j$$KSkt zU6G<@U z`jF_i(?h0;w^sOxV6sK5>B*FON!6<<^@dp+gm+)?n{(KDVjM8QkU_#Md(BN6CP7Uf Px7x*ZY0sp)KsSa$`8 zdSP)1<*G0X=32E)3o_K@*(LAA9t9}RexwoA!E5&YkFtA~_gDc~ z)b5Wz0PLsjypdkq+ppcYa>sQ!hS&p})ksRzRdVxQoLMa4%|qwpW1Bf+A;# zxU&M>wW$Ww`|@hc)wL#W?{rP|p6&t|?V{}}_SD@NH~aU>BVHAAYrO7u^mBO;?R)M9 zsw_a{p*ug=Z%*-CtA7Vx#iV9&l&xm>EiD*-7{rfN=UfK6F5#qi=-d3ix=b?JA4W+v z*&mD=%tRNO$w@LbcogXc0000000000000000000U^9x!UrRc4Z6d(Wq002ovPDHLk FV1gaf)jR+I literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.56.png b/tests_speculos/test-transaction-nft/nanox.56.png new file mode 100644 index 0000000000000000000000000000000000000000..bcbf619c6402ae334ae5fb03bbfbb0326ca6033d GIT binary patch literal 516 zcmV+f0{i`mP)4 zzUGB4rSztUeR!*%wboke^Eij8sXu(ruE08aRclor@!^EsqX5NU-+=tgiI~N<9m@caPSbcf|1 zHr1N46=8QAlV$8?=h<1q0>gvOc-W2D@L%4r0ZaeWU77)z#UY#=eu1!3|Fb^>oFym| zA>+;r2b@?P)F-8jwG& zn3qEc%0x6scr(d9lo6tv$6$-Mpl`-zCv1!8^5*L>0Kj(SJO0~k-e`FNll;`)f}SFc z!v2q-?Sx~xQQQ~F#o|xyGliKOujQg$mZj*qEE+k=%GpDYSv`b${h{}()cayVcTTT= z;b!NQs8kzlROvDOf5~ZV#L{GX4>#`bb34~!Fxcp9?oYuq}g6?@^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-transaction-nft/nanox.59.png b/tests_speculos/test-transaction-nft/nanox.59.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-transaction-nft/nanox.60.png b/tests_speculos/test-transaction-nft/nanox.60.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&_( O0000pSZ(Y+=C<0N9u^c%J*x9`0bx1e>ilVm4V@Q?OF|~g zR_62G%^S5AFR}V3;^jDPpz zwb5ZWY#C1Oe!th~k)PG^?~@s$w=YP1ns?d8I;QMP=Q7L9Ojf7=ulu>{o%o-Y6h@FY z&u@F{ec}HXrMQ>*A0#$?*`Dd5`}b_mLC>;FVdQ&MBb@0E!BW!~g&Q literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.62.png b/tests_speculos/test-transaction-nft/nanox.62.png new file mode 100644 index 0000000000000000000000000000000000000000..a5eebf2fd5d947746fb37a9705f86a514bb2a30e GIT binary patch literal 469 zcmV;`0V@89P)CbaA{Beo0cvUV%Ijyptd?uhq-F;~!gdoTBcCMiOjQqg$x4v>*4Vu%C!kX#T zGE}KHj+*wpczlDbg-lu%B5#KfY8RPzm9r)Hsvx(_$TGv)oS8?>+4TgJnUxpQW==+J zq$gU8Q5`rMM@_$ry|18kwa4hQksVi>U9X@rMaQJ)LaisTV}n(ocebUBW(46UFy{PEY##V&+jqGZ+71M~>iL7-l%QL3E zxvOfXRAyD4(Y%V+XxxuxORGI-hPpjPqj%Ps!;bEoa(Xi^o9=+#{)FIu-XX7Ab2r80~%fF2XnEJ^W_*AF4&B z`j0V6P4yqf9DEXQp-;LgGIOvM$<@>$4X`Hr?&2JkXXz zB-{bsbrg@#9SpA|7)@n=SJ-$2dz(IU2c%G!+nG2AhHXIIeB-3=Z8~89Y`zj_zj!D) z*{FHwSlB_GtmjWj{2SVxgo~J3yEVOCUhra`54Jhg&%r$Dh4rrJD<)w8006w+;3eq5 z`|v%Ue-l2O1-tU2mh)aAZ8fx*mm}|8$=0!}q29`9ce!ow?h{`G9rS#9USGjmiIm&% z!fvQR6J*q^nH%HUNOplvVSwi;7L4%o-|1^V{j?b3jhEB000000000000000T;?Aa&CXE@cvv9- O0000{MKoyd07jMO#(4idAl%RJJ0Bs(`Y7AB$Rz+5ky0XR%jN zE4M2)r97IzEy&M7msW|`QEAclXHfZ0Lbjmplx(DCGbDG3vq!8ai8_Y*?@#9?T$Qgr z)lz<$lvK;5tH&M(-a6CwkaJdV>=)U}mx(#Zi$npl;&R)qaX>2^1jycF=ivz!jn5$o2CaZjG5)$M9!-tUgtQRm{Ub$@Ah z#YU>3UjeF>w$@o2^u~&vwJz_>_Gky0+k#4otwo{L{U2<;AF*Dwbz>WKr4=#qZ*N@H zLLc;?_1n=E;FrA9zm#!l&8l1AO37L4RVBWvZaO>VyYsm1yixzRb!F>Y4jiw?Pe5&< zf8Ed{(YI&wkFu_fzWbkzH=-G8a8I!VNapP11Nz3lD^IlmT z?47Oa&sa0FMyWXVR!Q96)A_$0pZR2MoIY=%T&>hE`77E9JD%KT_!_;UUjEYkr`Gl+ z>rR{gS?y4LbfY<6TlqEK`%3kXkNnsszVBTi1N&0tcl!bv=Fc=e5InhD(Z)z3C2NDo gVQyduA_JYzyq}haZ(1|sZzV|B)78&qol`;+07#*}+yDRo literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.66.png b/tests_speculos/test-transaction-nft/nanox.66.png new file mode 100644 index 0000000000000000000000000000000000000000..5481d5daf483cceb519fd0f4ebf5930d351d3b04 GIT binary patch literal 358 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|`hsba4!+nDh2#v{17Gk3-b~_*VEpm3K}@=~aGSRY^_N&oVdE7dTqw+^knESk-)7B2@eNyldtc7AZ;CI~mU8 z;Gg?T2oP!gOWqBi5#6Y!ZC% z(B}VB4Mrp5DaB5|r4M}k9G_`-VoLW1uM(!3zRRzFFW5A5RanRD|M}00>=y5zaaTe3 x@9hvL-!J)Yd^(2Qkx8=KTq+nrUPge1#Cx0F&!n{!Yn}!Pdb;|#taD0e0sy(jlY0OF literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.67.png b/tests_speculos/test-transaction-nft/nanox.67.png new file mode 100644 index 0000000000000000000000000000000000000000..ff4e54c6fe90e98c58da36c9503d7049f66c01e7 GIT binary patch literal 464 zcmV;>0WbcEP)8Dw(Q%N$Q;qpqU!|itI8>s1z*Wd(VlR75Im(e`+Pn*VFRN+jxOo{znb?cV zndwM%SnUH_f>X;RkBxxdb1EO$SLXy3)@GU6Jk|kq;H!{re;?Z72ArGOxYHevzq*{c>yzZK7Yr$p)k#_b>eemF491XExXT zO}z_=QS)j~{AQdT1c5H>M%=H$AH+B1`XMvq+xVe z<{gEUtVcjpm_nMUejKV(b+P?DX&W$Xm8~N7B|Hf4yrprHMRx|>P)v;x9UwKLG4xRKwk2~HZ*Hx4wu|L#$hVnMPiFcxY&Tr7$~OMU z>1i*odmU!vJM??U*WWH@6@>rZ4sr7RlJCZ+6WFT5+vLNi=UTxCbO{n@I9#o^SgRZ_1-H-99)`$V0g`F5k1H3f0FX6xK8{|jQL&7JZUD%& z&X73(Q*2{`LVdNuQ%hXS(fq2zzr0>uGPj_6hmkL+)2sM2S=+tJ!7V5~@6sUNK$bTO z)#jmTlso|Hxvl*`8UZ!cgQ@F9zEmw2VBZ^!pWBh%g6KQxgoSr!e*m(2QWQl|yf3~Q zs@`wO6Btkpfuti(it=_1gbK9@uYEF(m~e6gB}+JDjsfIZ&9%up#iw}ukfn5nQgmS{ zV(b|sC{8nI)ILGYkT24Ei2XUfb0b3oaAy!(?-iyQq^20l223k?$F_dbxy$<`Sd-+W zqj7?}HklDh5aTu%Y$!EYNKl1XIaExC*M5O%zx8&eVZs2YbC8)S{PId9Gz@A(i?!b7 z{~{*e1ZuSt$$cTq;miZT?9|t9C6as>$(qrZnCo2{lua0?Oqgys;A~<>SQ^t4Fq2%_ ze;P`vEBjBT#^9CIF7!&AB-a=mC6->Pri~@-2gZ}EtbTOs1bQEd#VS!<^HICyh%wPo z>u%y0AYw?FJ;ZDHQ6IXc0X7}qqh zUS|0;(hF9tue?=qZAZO60Mb)Hb^1qaM$NthRG3{`HMJ&Es?}5xbeh+jn&=uSCy}Bk zilQir{|fb=^6IVHk{r~tAdpaG1#tWgomR0;>+tHv1$h>r`DyEZ2lwV*Yr2Tc5^aBsbAuzfij@E#JyoeB~pM~}9qfJqV566~h1H#d!x zJx30w6&Hwi0VKxtXC|j>z1h$HIjp#HMc6F3e4003aSpOh%2l=}Pidc}`d z2g^8h?|Wk;tcv5DQc9_N{ldsH$YW%LD}Jwss8m^d>W%eoAmzq}1yF@KS|ztFz@B^2 z-}Mka)j8_l6YW|Bl;`%k;P5KFU9)}qw9Fo0sm3n5tyh^WDBEhEwyZcaP z1*m(I37RTs@7|hQhLPiQk}A0~B_zrmg5D#Hot;*X=@&&BN6K*Dv~UPY&$|uijAO^- zC=qEiD_T-=1vD3JzH0HgfuBmbe=#AVQ{c>TOSCJc&f!=1VK!v~S4il(ipZFr<6H zhK?DT@gj48Jw|@=9DZwTemmQ6@7^VzwIaSrH_2pwn?|b1{$|v{ofw7gG)OXauoUS9 p0000000000000000001%`3L!3i(3%EFfafB002ovPDHLkV1f@{-@*U@ literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.71.png b/tests_speculos/test-transaction-nft/nanox.71.png new file mode 100644 index 0000000000000000000000000000000000000000..6282738092eceb6610f61b605ec478d24dcbd738 GIT binary patch literal 341 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|^K-ba4!+nDh2#v{17Gk3-|3>mz3@bd>NdX90vdZ006*tz9!1bIm^qG(nPVR zw4&mUa4FDp%Xt1i-_s>7GIW-T62fY^JjG|Vf}C?o$zH|It)o|>99U^-K5yIU`BP8l zCi8cIMoyy{i4%K1J@2QKiZm(KNGAF+lzHZEo%sQ1+IPCCxuBd9s$23$)FUNkV>W0B zcbK1o7S(`e{It5>W0IF-|C1dlsGwuhj8-p%GQ&jk)$zOb$T|_SVLDp_aF|Z?FU+ct zgtAakDzzIm=~dfh6$5#Eo3z#J-T_eY*8;HgtrY+O>SKmJbzGi9dP5Aco`a2jB#oM+ zN4??|uh~v%T4_7No|xwuzmdn)nlMcD+c0+gjkO|lpDWsHbaqjUX8qA>$=Q}<(ZbXN z~<|U_!kUe@}Zwq>C@YrLpp5P^?JX$298Pg)MSrI zej7quxc8By@!#k=8SihS;cC3U8Dj7xWT7Xya56Di3ho5}00000000000000000000 d00013#t*s(24EDlYE=LL002ovPDHLkV1i(v^qc?y literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.73.png b/tests_speculos/test-transaction-nft/nanox.73.png new file mode 100644 index 0000000000000000000000000000000000000000..bff4ff72cbd254541a899dcd315dc05f2d83a2e9 GIT binary patch literal 359 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|`hqba4!+nDh2#BHtkep0I(njFS8>Q#0CLyfe*yFlExrsk?U+y?V0t)&3ubUKMo`0nX;kMgOc0d%ZbTTRW}R z*m}{=4O4GU_f?B!JhbiBr9}ov*wfX|Wt~$(696jm BqdNcq literal 0 HcmV?d00001 diff --git a/tests_speculos/test-transaction-nft/nanox.74.png b/tests_speculos/test-transaction-nft/nanox.74.png new file mode 100644 index 0000000000000000000000000000000000000000..c02f5ccbcf0b6ec66070447485920b78c64bdcf9 GIT binary patch literal 423 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|`Jiba4!+nDh2VbkSi29*4l~ zljm;uuex!1T}4}Bi-OQI!AsTqZp}zi{OGlw8>lPd-SK-S$7cMOa*_}_c=zqy=qlIp z=amy`=XOYZ`jA#p`Cp+$LCf&Y;Wy{KukpNN;xIfGGU=9%nciHZ+9a!PwqqsJvk%Gj zS4_F5acp+|qu3++r@7_+thnM8!L%yXzGXtcaqYbm`=tCOFCA!|bo|-dYq1Qa|984a z*abi>@&Dky$DKzPmLJVU zTpg;t@b9&S+cb?|SN)zm&A&(Iz*V0`wXZsxkL=*STO2>9vDa_cbf@EP`)6j$$KSkt zU6G<@U z`jF_i(?h0;w^sOxV6sK5>B*FON!6<<^@dp+gm+)?n{(KDVjM8QkU_#Md(BN6CP7Uf P&Cr z!Swm*Uh3UHtvdmAvYhhLnwSN1ZQ7;;IqK=zCGW)^6Hwm$NFk~luUYp$%I;a-V+CMQ zyTASbupd!*bs@ei&8;rZu#sBtfA#DQN%y*pVZ?@Ho)DN(`ibyASfoun$aM8iIHAU4 zxc7~q?&`bH;0e?I}@ACkdD_QhTjoSUv*_a0HKdmMd({_wh{ zN5<*ub?*^{oPhV&uimEW-N_t0_-A2ca~#KU9M816)9|r? z%2!_KQc6pG*oR&HthLr!@5ec%mj3jeeFE$3RjpP1A^#;m4v*MSGG=o&k;mH+*-+(~ zEpY^>4s`m{x~=YCbuY^r$!Kom`b&mt5mb;)gv;)2l0E`#z1G+W=bbCha@8tW?wc8| zNxuNk4CEU3UCAVtg)!==KmmM%Hrw6O9l zA2>7t007XVp5g2x-S8prs!1no;UKk9x|kvpyIMxna}yI*{q_J@L< zZQOY&c#5ffY{RP|7|9`$Pe;mT+(I6^HcgA!4jBD^FW}!C?ip(`gMR@@L0000000000000000Dv?b@?P)F-8jwG& zn3qEc%0x6scr(d9lo6tv$6$-Mpl`-zCv1!8^5*L>0Kj(SJO0~k-e`FNll;`)f}SFc z!v2q-?Sx~xQQQ~F#o|xyGliKOujQg$mZj*qEE+k=%GpDYSv`b${h{}()cayVcTTT= z;b!NQs8kzlROvDOf5~ZV#L{GX4>#`bb34~!Fxcp9?oYuq}g6?@^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-transaction-nft/nanox.79.png b/tests_speculos/test-transaction-nft/nanox.79.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 From 129125f6966cc89cc85c95e820bed0cba948f681 Mon Sep 17 00:00:00 2001 From: Robert Lukotka Date: Mon, 19 Dec 2022 14:11:31 +0100 Subject: [PATCH 3/4] Refactor NFT script constants --- app/src/parser.c | 115 ++++++++++++++++++++++++++-------------- app/src/script_parser.c | 22 ++++---- app/src/script_parser.h | 30 +++++++++++ 3 files changed, 117 insertions(+), 50 deletions(-) diff --git a/app/src/parser.c b/app/src/parser.c index 77281160..11071b9b 100644 --- a/app/src/parser.c +++ b/app/src/parser.c @@ -626,26 +626,47 @@ parser_error_t parser_getItem_internal(int8_t *displayIdx, else { const char storageString[] = "/storage/"; const char publicString[] = "/public/"; + const size_t storageStringLength = sizeof(storageString)-1; + const size_t publicStringLength = sizeof(publicString)-1; switch (parser_tx_obj.parsedScript.script_type) { - case SCRIPT_TYPE_NFT_SETUP_COLLECTION: + case SCRIPT_TYPE_NFT_SETUP_COLLECTION: { //validate basic assumptions on the script - if (parser_tx_obj.parsedScript.elements_count != 11) { + if (parser_tx_obj.parsedScript.elements_count != PARSED_ELEMENTS_NFT1_COUNT) { + return PARSER_UNEXPECTED_ERROR; + } + // storagePath should be preceeded with "/storage/" - double checking before displaying it + STATIC_ASSERT(PARSED_ELEMENTS_NFT1_STORAGE_PATH >= 1, "Script index error"); + bool enoughSpaceForStorageString = (parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_STORAGE_PATH].data + >= parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_STORAGE_PATH-1].data + storageStringLength); + if (!enoughSpaceForStorageString) { return PARSER_UNEXPECTED_ERROR; } - // storagePath should be preceeded with "/storage/" - if (parser_tx_obj.parsedScript.elements[4].data < parser_tx_obj.parsedScript.elements[3].data + (sizeof(storageString)-1) || - memcmp(parser_tx_obj.parsedScript.elements[4].data - (sizeof(storageString)-1), storageString, sizeof(storageString)-1)) { + if (memcmp(parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_STORAGE_PATH].data - storageStringLength, + storageString, storageStringLength)) { return PARSER_UNEXPECTED_ERROR; } - // publicCollectionContractName and publicCollectionName should be separated by a single '.' - if (parser_tx_obj.parsedScript.elements[7].data + parser_tx_obj.parsedScript.elements[7].length + 1 != - parser_tx_obj.parsedScript.elements[8].data || - parser_tx_obj.parsedScript.elements[7].data[parser_tx_obj.parsedScript.elements[7].length] != '.') { + // publicCollectionContractName and publicCollectionName should be separated by a single '.' - double checking before displaying it + STATIC_ASSERT(PARSED_ELEMENTS_NFT1_PUBLIC_COLLECTION_CONTRACT_NAME + 1 == PARSED_ELEMENTS_NFT1_PUBLIC_COLLECTION_NAME, + "Script index error"); + if (parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_PUBLIC_COLLECTION_CONTRACT_NAME].data + + parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_PUBLIC_COLLECTION_CONTRACT_NAME].length + 1 != + parser_tx_obj.parsedScript.elements[8].data) { return PARSER_UNEXPECTED_ERROR; } - // publicPath should be preceeded with "/public/" - if (parser_tx_obj.parsedScript.elements[9].data < parser_tx_obj.parsedScript.elements[8].data + (sizeof(publicString)-1) || - memcmp(parser_tx_obj.parsedScript.elements[9].data - (sizeof(publicString)-1), publicString, sizeof(publicString)-1)) { + size_t expectedDotIndex = parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_PUBLIC_COLLECTION_CONTRACT_NAME].length; + if (parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_PUBLIC_COLLECTION_CONTRACT_NAME].data[expectedDotIndex] != '.') { + return PARSER_UNEXPECTED_ERROR; + } + + // publicPath should be preceeded with "/public/" - double checking before displaying it + STATIC_ASSERT(PARSED_ELEMENTS_NFT1_PUBLIC_PATH >= 1, "Script index error"); + bool enoughSpaceForPublicString = (parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_PUBLIC_PATH].data + >= parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_PUBLIC_PATH-1].data + publicStringLength); + if (!enoughSpaceForPublicString) { + return PARSER_UNEXPECTED_ERROR; + } + if (memcmp(parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_PUBLIC_PATH].data - publicStringLength, + publicString, publicStringLength)) { return PARSER_UNEXPECTED_ERROR; } @@ -657,57 +678,71 @@ parser_error_t parser_getItem_internal(int8_t *displayIdx, SCREEN(true) { snprintf(outKey, outKeyLen, "Contract Name"); pageStringExt(outVal, outValLen, - (const char *) parser_tx_obj.parsedScript.elements[2].data, - parser_tx_obj.parsedScript.elements[2].length, + (const char *) parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_CONTRACT_NAME].data, + parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_CONTRACT_NAME].length, pageIdx, pageCount); return PARSER_OK; } SCREEN(true) { snprintf(outKey, outKeyLen, "Contract Address"); pageStringExt(outVal, outValLen, - (const char *) parser_tx_obj.parsedScript.elements[3].data, - parser_tx_obj.parsedScript.elements[3].length, + (const char *) parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_CONTRACT_ADDRESS].data, + parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_CONTRACT_ADDRESS].length, pageIdx, pageCount); return PARSER_OK; } SCREEN(true) { snprintf(outKey, outKeyLen, "Storage Path"); pageStringExt(outVal, outValLen, - (const char *) parser_tx_obj.parsedScript.elements[4].data - (sizeof(storageString)-1), - parser_tx_obj.parsedScript.elements[4].length + (sizeof(storageString)-1), + (const char *) parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_STORAGE_PATH].data - storageStringLength, + parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_STORAGE_PATH].length + storageStringLength, pageIdx, pageCount); return PARSER_OK; } SCREEN(true) { snprintf(outKey, outKeyLen, "Public Coll. Name"); pageStringExt(outVal, outValLen, - (const char *) parser_tx_obj.parsedScript.elements[7].data, - parser_tx_obj.parsedScript.elements[7].length + parser_tx_obj.parsedScript.elements[8].length + 1, + (const char *) parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_PUBLIC_COLLECTION_CONTRACT_NAME].data, + parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_PUBLIC_COLLECTION_CONTRACT_NAME].length + + parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_PUBLIC_COLLECTION_NAME].length + 1, pageIdx, pageCount); return PARSER_OK; } SCREEN(true) { snprintf(outKey, outKeyLen, "Public Path"); pageStringExt(outVal, outValLen, - (const char *) parser_tx_obj.parsedScript.elements[9].data - (sizeof(publicString)-1), - parser_tx_obj.parsedScript.elements[9].length + (sizeof(publicString)-1), + (const char *) parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_PUBLIC_PATH].data - publicStringLength, + parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT1_PUBLIC_PATH].length + publicStringLength, pageIdx, pageCount); return PARSER_OK; } break; - case SCRIPT_TYPE_NFT_TRANSFER: + } + case SCRIPT_TYPE_NFT_TRANSFER: { //validate basic assumptions on the script - if (parser_tx_obj.parsedScript.elements_count != 8) { + if (parser_tx_obj.parsedScript.elements_count != PARSED_ELEMENTS_NFT2_COUNT) { + return PARSER_UNEXPECTED_ERROR; + } + // storagePath should be preceeded with "/storage/" - double checking before displaying it + STATIC_ASSERT(PARSED_ELEMENTS_NFT2_STORAGE_PATH >= 1, "Script index error"); + bool enoughSpaceForStorageString = (parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT2_STORAGE_PATH].data + >= parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT2_STORAGE_PATH-1].data + storageStringLength); + if (!enoughSpaceForStorageString) { + return PARSER_UNEXPECTED_ERROR; + } + if (memcmp(parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT2_STORAGE_PATH].data - storageStringLength, + storageString, storageStringLength)) { return PARSER_UNEXPECTED_ERROR; } - // storagePath should be preceeded with "/storage/" - if (parser_tx_obj.parsedScript.elements[3].data < parser_tx_obj.parsedScript.elements[2].data + (sizeof(storageString)-1) || - memcmp(parser_tx_obj.parsedScript.elements[3].data - (sizeof(storageString)-1), storageString, sizeof(storageString)-1)) { + // publicPath should be preceeded with "/public/" - double checking before displaying it + STATIC_ASSERT(PARSED_ELEMENTS_NFT2_PUBLIC_PATH >= 1, "Script index error"); + bool enoughSpaceForPublicString = (parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT2_PUBLIC_PATH].data + >= parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT2_PUBLIC_PATH-1].data + publicStringLength); + if (!enoughSpaceForPublicString) { return PARSER_UNEXPECTED_ERROR; } - // publicPath should be preceeded with "/public/" - if (parser_tx_obj.parsedScript.elements[7].data < parser_tx_obj.parsedScript.elements[6].data + (sizeof(publicString)-1) || - memcmp(parser_tx_obj.parsedScript.elements[7].data - (sizeof(publicString)-1), publicString, sizeof(publicString)-1)) { + if (memcmp(parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT2_PUBLIC_PATH].data - publicStringLength, + publicString, publicStringLength)) { return PARSER_UNEXPECTED_ERROR; } @@ -719,37 +754,38 @@ parser_error_t parser_getItem_internal(int8_t *displayIdx, SCREEN(true) { snprintf(outKey, outKeyLen, "Contract Name"); pageStringExt(outVal, outValLen, - (const char *) parser_tx_obj.parsedScript.elements[1].data, - parser_tx_obj.parsedScript.elements[1].length, + (const char *) parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT2_CONTRACT_NAME].data, + parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT2_CONTRACT_NAME].length, pageIdx, pageCount); return PARSER_OK; } SCREEN(true) { snprintf(outKey, outKeyLen, "Contract Address"); pageStringExt(outVal, outValLen, - (const char *) parser_tx_obj.parsedScript.elements[2].data, - parser_tx_obj.parsedScript.elements[2].length, + (const char *) parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT2_CONTRACT_ADDRESS].data, + parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT2_CONTRACT_ADDRESS].length, pageIdx, pageCount); return PARSER_OK; } SCREEN(true) { snprintf(outKey, outKeyLen, "Storage Path"); pageStringExt(outVal, outValLen, - (const char *) parser_tx_obj.parsedScript.elements[3].data - (sizeof(storageString)-1), - parser_tx_obj.parsedScript.elements[3].length + (sizeof(storageString)-1), + (const char *) parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT2_STORAGE_PATH].data - storageStringLength, + parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT2_STORAGE_PATH].length + storageStringLength, pageIdx, pageCount); return PARSER_OK; } SCREEN(true) { snprintf(outKey, outKeyLen, "Public Path"); pageStringExt(outVal, outValLen, - (const char *) parser_tx_obj.parsedScript.elements[7].data - (sizeof(publicString)-1), - parser_tx_obj.parsedScript.elements[7].length + (sizeof(publicString)-1), + (const char *) parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT2_PUBLIC_PATH].data - publicStringLength, + parser_tx_obj.parsedScript.elements[PARSED_ELEMENTS_NFT2_PUBLIC_PATH].length + publicStringLength, pageIdx, pageCount); return PARSER_OK; } break; - case SCRIPT_TYPE_UNKNOWN: + } + case SCRIPT_TYPE_UNKNOWN: { SCREEN(true) { snprintf(outKey, outKeyLen, "Script hash"); pageHexString(outVal, outValLen, parser_tx_obj.hash.digest, sizeof(parser_tx_obj.hash.digest), pageIdx, pageCount); @@ -761,6 +797,7 @@ parser_error_t parser_getItem_internal(int8_t *displayIdx, return PARSER_OK; } break; + } default: return PARSER_UNEXPECTED_ERROR; } diff --git a/app/src/script_parser.c b/app/src/script_parser.c index 97fb7e77..94f689fa 100644 --- a/app/src/script_parser.c +++ b/app/src/script_parser.c @@ -81,7 +81,7 @@ bool parseScript(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLA } -// Elements : +// Elements (see enum in script_parser.h) : // 0 - NonFungibleToken address // 1 - MetadataViews address // 2 - contractName @@ -121,19 +121,19 @@ bool parseNFT1(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLATI return false; } - ELEMENT_MUST_BE_ADDRESS(parsedElements, 0); - ELEMENT_MUST_BE_ADDRESS(parsedElements, 1); + ELEMENT_MUST_BE_ADDRESS(parsedElements, PARSED_ELEMENTS_NFT1_NON_FUNGIBLE_TOKEN_ADDRESS); + ELEMENT_MUST_BE_ADDRESS(parsedElements, PARSED_ELEMENTS_NFT1_METADATA_VIEWS_ADDRESS); - ELEMENTS_MUST_BE_EQUAL(parsedElements, 2, 5); - ELEMENTS_MUST_BE_EQUAL(parsedElements, 4, 6); - ELEMENTS_MUST_BE_EQUAL(parsedElements, 4, 10); + ELEMENTS_MUST_BE_EQUAL(parsedElements, PARSED_ELEMENTS_NFT1_CONTRACT_NAME, PARSED_ELEMENTS_NFT1_CONTRACT_NAME2); + ELEMENTS_MUST_BE_EQUAL(parsedElements, PARSED_ELEMENTS_NFT1_STORAGE_PATH, PARSED_ELEMENTS_NFT1_STORAGE_PATH2); + ELEMENTS_MUST_BE_EQUAL(parsedElements, PARSED_ELEMENTS_NFT1_STORAGE_PATH, PARSED_ELEMENTS_NFT1_STORAGE_PATH3); parsedElements->script_type = SCRIPT_TYPE_NFT_SETUP_COLLECTION; return true; } -// Elements : +// Elements (see enum in script_parser.h) : // 0 - NonFungibleToken address // 1 - contractName // 2 - contractAddress @@ -180,11 +180,11 @@ bool parseNFT2(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLATI return false; } - ELEMENT_MUST_BE_ADDRESS(parsedElements, 0); + ELEMENT_MUST_BE_ADDRESS(parsedElements, PARSED_ELEMENTS_NFT2_NON_FUNGIBLE_TOKEN_ADDRESS); - ELEMENTS_MUST_BE_EQUAL(parsedElements, 1, 4); - ELEMENTS_MUST_BE_EQUAL(parsedElements, 1, 5); - ELEMENTS_MUST_BE_EQUAL(parsedElements, 3, 6); + ELEMENTS_MUST_BE_EQUAL(parsedElements, PARSED_ELEMENTS_NFT2_CONTRACT_NAME, PARSED_ELEMENTS_NFT2_CONTRACT_NAME2); + ELEMENTS_MUST_BE_EQUAL(parsedElements, PARSED_ELEMENTS_NFT2_CONTRACT_NAME, PARSED_ELEMENTS_NFT2_CONTRACT_NAME3); + ELEMENTS_MUST_BE_EQUAL(parsedElements, PARSED_ELEMENTS_NFT2_STORAGE_PATH, PARSED_ELEMENTS_NFT2_STORAGE_PATH2); parsedElements->script_type = SCRIPT_TYPE_NFT_TRANSFER; return true; diff --git a/app/src/script_parser.h b/app/src/script_parser.h index 0b9ed60f..42424ebc 100644 --- a/app/src/script_parser.h +++ b/app/src/script_parser.h @@ -29,8 +29,38 @@ typedef struct { bool parseScript(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLATILE *scriptToParse, size_t scriptToParseSize, const uint8_t *scriptTemplate, size_t scriptTemplateSize); +typedef enum { + PARSED_ELEMENTS_NFT1_NON_FUNGIBLE_TOKEN_ADDRESS = 0, + PARSED_ELEMENTS_NFT1_METADATA_VIEWS_ADDRESS = 1, + PARSED_ELEMENTS_NFT1_CONTRACT_NAME = 2, + PARSED_ELEMENTS_NFT1_CONTRACT_ADDRESS = 3, + PARSED_ELEMENTS_NFT1_STORAGE_PATH = 4, + PARSED_ELEMENTS_NFT1_CONTRACT_NAME2 = 5, + PARSED_ELEMENTS_NFT1_STORAGE_PATH2 = 6, + PARSED_ELEMENTS_NFT1_PUBLIC_COLLECTION_CONTRACT_NAME = 7, + PARSED_ELEMENTS_NFT1_PUBLIC_COLLECTION_NAME = 8, + PARSED_ELEMENTS_NFT1_PUBLIC_PATH = 9, + PARSED_ELEMENTS_NFT1_STORAGE_PATH3 = 10, + + PARSED_ELEMENTS_NFT1_COUNT = 11, +} parsed_elements_nft1_index_t; + + bool parseNFT1(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLATILE *scriptToParse, size_t scriptToParseSize); +typedef enum { + PARSED_ELEMENTS_NFT2_NON_FUNGIBLE_TOKEN_ADDRESS = 0, + PARSED_ELEMENTS_NFT2_CONTRACT_NAME = 1, + PARSED_ELEMENTS_NFT2_CONTRACT_ADDRESS = 2, + PARSED_ELEMENTS_NFT2_STORAGE_PATH = 3, + PARSED_ELEMENTS_NFT2_CONTRACT_NAME2 = 4, + PARSED_ELEMENTS_NFT2_CONTRACT_NAME3 = 5, + PARSED_ELEMENTS_NFT2_STORAGE_PATH2 = 6, + PARSED_ELEMENTS_NFT2_PUBLIC_PATH = 7, + + PARSED_ELEMENTS_NFT2_COUNT = 8, +} parsed_elements_nft2_index_t; + bool parseNFT2(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLATILE *scriptToParse, size_t scriptToParseSize); From d4132c77a7784daec013e08fbada77f1efb391cd Mon Sep 17 00:00:00 2001 From: Robert Lukotka Date: Mon, 19 Dec 2022 14:33:21 +0100 Subject: [PATCH 4/4] Review fixes --- app/src/script_parser.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/app/src/script_parser.c b/app/src/script_parser.c index 94f689fa..624782f5 100644 --- a/app/src/script_parser.c +++ b/app/src/script_parser.c @@ -41,15 +41,14 @@ bool parseScript(script_parsed_elements_t *parsedElements, const uint8_t NV_VOLA parsedElements->elements_count += 1; scriptRead += elementLen; templateRead += 1; - - continue; } - - if (templateChar != scriptToParse[scriptRead]) { - return false; + else { //character to match one in the script + if ((scriptRead >= scriptToParseSize) || (templateChar != scriptToParse[scriptRead])) { + return false; + } + scriptRead += 1; + templateRead += 1; } - scriptRead += 1; - templateRead += 1; } if (scriptRead != scriptToParseSize || templateRead != scriptTemplateSize) {