From 75820296ed0f9025f2d4d89c0798b35dd96bcad8 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 2 Jul 2019 00:27:06 +0200 Subject: [PATCH 01/78] adding target it to getVersion --- Makefile | 4 ++-- src/app_main.c | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 32faa76b..355be75f 100755 --- a/Makefile +++ b/Makefile @@ -24,8 +24,8 @@ include $(BOLOS_SDK)/Makefile.defines # Main app configuration APPNAME = "Cosmos" APPVERSION_M=1 -APPVERSION_N=5 -APPVERSION_P=3 +APPVERSION_N=6 +APPVERSION_P=0 APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path "44'/118'" diff --git a/src/app_main.c b/src/app_main.c index 051644a7..88a692cc 100644 --- a/src/app_main.c +++ b/src/app_main.c @@ -333,7 +333,12 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { G_io_apdu_buffer[3] = LEDGER_PATCH_VERSION; G_io_apdu_buffer[4] = !IS_UX_ALLOWED; - *tx += 5; + G_io_apdu_buffer[5] = (TARGET_ID >> 24) & 0xFF; + G_io_apdu_buffer[6] = (TARGET_ID >> 16) & 0xFF; + G_io_apdu_buffer[7] = (TARGET_ID >> 8) & 0xFF; + G_io_apdu_buffer[8] = (TARGET_ID >> 0) & 0xFF; + + *tx += 9; THROW(APDU_CODE_OK); break; } From af85410fb405bf16b938995a3bebd53818148b67 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 2 Jul 2019 01:43:27 +0200 Subject: [PATCH 02/78] cache pubkeys --- script.ld | 2 +- src/app_main.c | 192 ++++++++++++------------------------------------- src/crypto.c | 126 +++++++++++++++++++++++--------- src/crypto.h | 48 ++++++------- src/view.c | 57 ++++++++------- 5 files changed, 191 insertions(+), 234 deletions(-) diff --git a/script.ld b/script.ld index e0bba982..a104e6d3 100644 --- a/script.ld +++ b/script.ld @@ -30,7 +30,7 @@ MEMORY } PAGE_SIZE = 64; -STACK_SIZE = 860; +STACK_SIZE = 820; END_STACK = ORIGIN(SRAM) + LENGTH(SRAM); SECTIONS diff --git a/src/app_main.c b/src/app_main.c index 88a692cc..18b04660 100644 --- a/src/app_main.c +++ b/src/app_main.c @@ -102,34 +102,35 @@ unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) { return 0; } -bool extractBip32(uint8_t *depth, uint32_t path[10], uint32_t rx, uint32_t offset) { +#define UINT32_FROM_LE(p) ( (*(p+3) << 24) + (*(p+2) << 16) + (*(p+1) << 8) + (*(p+0))) + +void extractBip32(uint32_t rx, uint32_t offset) { if (rx < offset + 1) { - return 0; + THROW(APDU_CODE_DATA_INVALID); } - *depth = G_io_apdu_buffer[offset]; - const uint16_t req_offset = 4 * *depth + 1 + offset; + uint8_t depth = G_io_apdu_buffer[offset]; + if (depth != BIP32_LEN_DEFAULT) { + THROW(APDU_CODE_DATA_INVALID); + } - if (rx < req_offset || *depth > 10) { - return 0; + const uint16_t req_offset = offset + 1 + 4 * BIP32_LEN_DEFAULT; + if (rx < req_offset) { + THROW(APDU_CODE_DATA_INVALID); } - memcpy(path, G_io_apdu_buffer + offset + 1, *depth * 4); - return 1; -} + uint8_t *p = (uint8_t *) (G_io_apdu_buffer + offset + 1); -bool validateCosmosPath(uint8_t depth, uint32_t path[10]) { - // Only paths in the form 44'/118'/{account}'/0/{index} are supported - if (bip32_depth != 5) { - return 0; - } - if (path[0] != 0x8000002c || path[1] != 0x80000076 || path[3] != 0) { - return 0; + if (setBip32Path(UINT32_FROM_LE(p + 0), + UINT32_FROM_LE(p + 4), + UINT32_FROM_LE(p + 8), + UINT32_FROM_LE(p + 12), + UINT32_FROM_LE(p + 16)) != BIP32_NO_ERROR) { + THROW(APDU_CODE_DATA_INVALID); } - return 1; } -bool extractHRP(uint8_t *len, char *hrp, uint32_t rx, uint32_t offset) { +void extractHRP(uint8_t *len, char *hrp, uint32_t rx, uint32_t offset) { if (rx < offset + 1) { THROW(APDU_CODE_DATA_INVALID); } @@ -142,7 +143,6 @@ bool extractHRP(uint8_t *len, char *hrp, uint32_t rx, uint32_t offset) { memcpy(hrp, G_io_apdu_buffer + offset + 1, *len); hrp[*len] = 0; // zero terminate - return 1; } bool process_chunk(volatile uint32_t *tx, uint32_t rx, bool getBip32) { @@ -158,9 +158,7 @@ bool process_chunk(volatile uint32_t *tx, uint32_t rx, bool getBip32) { transaction_initialize(); transaction_reset(); if (getBip32) { - if (!extractBip32(&bip32_depth, bip32_path, rx, OFFSET_DATA)) { - THROW(APDU_CODE_DATA_INVALID); - } + extractBip32(rx, OFFSET_DATA); return packageIndex == packageCount; } } @@ -185,18 +183,13 @@ int16_t tx_getData(char *title, int16_t max_title_length, *chunk_count_out = 0; if (*page_count_out > 0) { - switch (current_sigtype) { - case SECP256K1: - snprintf(title, - max_title_length, - "SECP256K1 %02d/%02d", - page_index + 1, - *page_count_out); - break; - default: - snprintf(title, max_title_length, "INVALID!"); - break; - } + + // TODO: remove this title + snprintf(title, + max_title_length, + "%02d/%02d", + page_index + 1, + *page_count_out); INIT_QUERY(key, max_key_length, value, max_value_length, chunk_index) *chunk_count_out = tx_display_get_item(page_index); @@ -208,31 +201,13 @@ int16_t tx_getData(char *title, int16_t max_title_length, } void tx_accept_sign() { - // Generate keys - cx_ecfp_public_key_t publicKey; - cx_ecfp_private_key_t privateKey; - uint8_t privateKeyData[32]; - unsigned int length = 0; - int result = 0; - switch (current_sigtype) { - case SECP256K1: - os_perso_derive_node_bip32(CX_CURVE_256K1, - bip32_path, bip32_depth, - privateKeyData, NULL); - keys_secp256k1(&publicKey, &privateKey, privateKeyData); - memset(privateKeyData, 0, 32); - result = sign_secp256k1(transaction_get_buffer(), - transaction_get_buffer_length(), - G_io_apdu_buffer, - IO_APDU_BUFFER_SIZE, - &length, - &privateKey); - break; - default: - THROW(APDU_CODE_INS_NOT_SUPPORTED); - break; - } + int result = sign_secp256k1(transaction_get_buffer(), + transaction_get_buffer_length(), + G_io_apdu_buffer, + IO_APDU_BUFFER_SIZE, + &length); + if (result == 1) { set_code(G_io_apdu_buffer, length, APDU_CODE_OK); io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, length + 2); @@ -267,12 +242,12 @@ int16_t addr_getData(char *title, int16_t max_title_length, if (chunk_count_out) *chunk_count_out = 1; - snprintf(title, max_title_length, "Account %d", bip32_path[2] & 0x7FFFFFF); + snprintf(title, max_title_length, "Account %d", getBip32Account()); snprintf(key, max_key_length, "index %d", page_index); - bip32_path[bip32_depth - 1] = page_index; // get address from the current bip32_path - get_bech32_addr(value); + setBip32Index(page_index); + getBech32Addr(value); return 0; } @@ -285,12 +260,12 @@ void addr_accept() { #endif // Send pubkey uint8_t *pk = G_io_apdu_buffer; - get_pk_compressed(pk); + getPubKeyCompressed(pk); int pos = PK_COMPRESSED_LEN; // Convert pubkey to bech32 address char *bech32_out = (char *) (G_io_apdu_buffer + pos); - get_bech32_addr(bech32_out); + getBech32Addr(bech32_out); pos += strlen(bech32_out); set_code(G_io_apdu_buffer, pos, APDU_CODE_OK); @@ -345,17 +320,9 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { // INS_PUBLIC_KEY_SECP256K1 will be deprecated in the near future case INS_PUBLIC_KEY_SECP256K1: { - if (!extractBip32(&bip32_depth, bip32_path, rx, OFFSET_DATA)) { - THROW(APDU_CODE_DATA_INVALID); - } - - if (!validateCosmosPath(bip32_depth, bip32_path)) { - THROW(APDU_CODE_DATA_INVALID); - } - - cx_ecfp_public_key_t publicKey; - getPubKey(&publicKey); + extractBip32(rx, OFFSET_DATA); + updatePubKey(); os_memmove(G_io_apdu_buffer, publicKey.W, 65); *tx += 65; @@ -365,17 +332,8 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { case INS_GET_ADDR_SECP256K1: { // Parse arguments - if (!extractHRP(&bech32_hrp_len, bech32_hrp, rx, OFFSET_DATA)) { - THROW(APDU_CODE_DATA_INVALID); - } - - if (!extractBip32(&bip32_depth, bip32_path, rx, OFFSET_DATA + bech32_hrp_len + 1)) { - THROW(APDU_CODE_DATA_INVALID); - } - - if (!validateCosmosPath(bip32_depth, bip32_path)) { - THROW(APDU_CODE_DATA_INVALID); - } + extractHRP(&bech32_hrp_len, bech32_hrp, rx, OFFSET_DATA); + extractBip32(rx, OFFSET_DATA + 1 + bech32_hrp_len); view_set_handlers(addr_getData, addr_accept, addr_reject); view_addr_confirm(0); @@ -385,7 +343,6 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { } case INS_SIGN_SECP256K1: { - current_sigtype = SECP256K1; if (!process_chunk(tx, rx, true)) THROW(APDU_CODE_OK); @@ -406,62 +363,6 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { break; } -#ifdef TESTING_ENABLED - case INS_HASH_TEST: { - if (process_chunk(tx, rx, false)) { - uint8_t message_digest[CX_SHA256_SIZE]; - - cx_hash_sha256(transaction_get_buffer(), - transaction_get_buffer_length(), - message_digest, - CX_SHA256_SIZE); - - os_memmove(G_io_apdu_buffer, message_digest, CX_SHA256_SIZE); - *tx += 32; - } - THROW(APDU_CODE_OK); - } - break; - - case INS_PUBLIC_KEY_SECP256K1_TEST: { - // Generate key - cx_ecfp_public_key_t publicKey; - cx_ecfp_private_key_t privateKey; - keys_secp256k1(&publicKey, &privateKey, privateKeyDataTest ); - - os_memmove(G_io_apdu_buffer, publicKey.W, 65); - *tx += 65; - - THROW(APDU_CODE_OK); - } - break; - - case INS_SIGN_SECP256K1_TEST: { - if (process_chunk(tx, rx, false)) { - - unsigned int length = 0; - - // Generate keys - cx_ecfp_public_key_t publicKey; - cx_ecfp_private_key_t privateKey; - keys_secp256k1(&publicKey, &privateKey, privateKeyDataTest ); - - // Skip UI and validation - sign_secp256k1( - transaction_get_buffer(), - transaction_get_buffer_length(), - G_io_apdu_buffer, - IO_APDU_BUFFER_SIZE, - &length, - &privateKey); - - *tx += length; - } - THROW(APDU_CODE_OK); - } - break; -#endif - default: THROW(APDU_CODE_INS_NOT_SUPPORTED); } @@ -497,10 +398,10 @@ void handle_generic_apdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32 // Respond to get device info command uint8_t *p = G_io_apdu_buffer; // Target ID 4 bytes - p[0]=(TARGET_ID >> 24) & 0xFF; - p[1]=(TARGET_ID >> 16) & 0xFF; - p[2]=(TARGET_ID >> 8) & 0xFF; - p[3]=(TARGET_ID >> 0) & 0xFF; + p[0] = (TARGET_ID >> 24) & 0xFF; + p[1] = (TARGET_ID >> 16) & 0xFF; + p[2] = (TARGET_ID >> 8) & 0xFF; + p[3] = (TARGET_ID >> 0) & 0xFF; p += 4; // SE Version [length][non-terminated string] *p = os_version(p + 1, 64); @@ -521,6 +422,7 @@ void app_init() { io_seproxyhal_init(); USB_power(0); USB_power(1); + crypto_init(); view_idle(0); } diff --git a/src/crypto.c b/src/crypto.c index 457b2136..4379c41c 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -19,16 +19,64 @@ #include "apdu_codes.h" #include "zxmacros.h" -uint8_t bip32_depth; -uint32_t bip32_path[10]; -sigtype_t current_sigtype; +typedef struct { + unsigned int cached : 1; + unsigned int valid : 1; + uint32_t path[BIP32_LEN_DEFAULT]; +} bip32_t; +bip32_t bip32; uint8_t bech32_hrp_len; char bech32_hrp[MAX_BECH32_HRP_LEN + 1]; -void keys_secp256k1(cx_ecfp_public_key_t *publicKey, - cx_ecfp_private_key_t *privateKey, - const uint8_t privateKeyData[32]) { +cx_ecfp_public_key_t publicKey; + +void crypto_init() { + bip32.valid = 0; + bip32.cached = 0; +} + +int8_t setBip32Path(uint32_t path0, + uint32_t path1, + uint32_t path2, + uint32_t path3, + uint32_t path4) { + // Only paths in the form 44'/118'/{account}'/0/{index} are supported + bip32.valid = 0; + bip32.cached = 0; + + bip32.path[0] = path0; + bip32.path[1] = path1; + bip32.path[2] = path2; + bip32.path[3] = path3; + bip32.path[4] = path4; + + if (bip32.path[0] != BIP32_0_DEFAULT || + bip32.path[1] != BIP32_1_DEFAULT || + bip32.path[3] != BIP32_3_DEFAULT) { + return BIP32_INVALID_PATH; + } + + bip32.valid = 1; + return BIP32_NO_ERROR; +} + +int32_t getBip32Account() { + return (bip32.path[2] & 0x7FFFFFF); +} + +int32_t getBip32Index() { + return (bip32.path[4] & 0x7FFFFFF); +} + +void setBip32Index(uint32_t newIndex) { + bip32.cached = 0; + bip32.path[4] = newIndex; +} + +void keysSecp256k1(cx_ecfp_public_key_t *publicKey, + cx_ecfp_private_key_t *privateKey, + const uint8_t *privateKeyData) { cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, privateKey); cx_ecfp_init_public_key(CX_CURVE_256K1, NULL, 0, publicKey); cx_ecfp_generate_pair(CX_CURVE_256K1, publicKey, privateKey, 1); @@ -38,18 +86,28 @@ int sign_secp256k1(const uint8_t *message, unsigned int message_length, uint8_t *signature, unsigned int signature_capacity, - unsigned int *signature_length, - cx_ecfp_private_key_t *privateKey) { - uint8_t message_digest[CX_SHA256_SIZE]; - cx_hash_sha256(message, message_length, message_digest, CX_SHA256_SIZE); + unsigned int *signature_length) { + // Generate keys cx_ecfp_public_key_t publicKey; - cx_ecdsa_init_public_key(CX_CURVE_256K1, NULL, 0, &publicKey); - cx_ecfp_generate_pair(CX_CURVE_256K1, &publicKey, privateKey, 1); + cx_ecfp_private_key_t privateKey; + uint8_t privateKeyData[32]; + + os_perso_derive_node_bip32(CX_CURVE_256K1, + bip32.path, + BIP32_LEN_DEFAULT, + privateKeyData, NULL); + keysSecp256k1(&publicKey, &privateKey, privateKeyData); + memset(privateKeyData, 0, 32); + // Hash + uint8_t message_digest[CX_SHA256_SIZE]; + cx_hash_sha256(message, message_length, message_digest, CX_SHA256_SIZE); + + // Sign unsigned int info = 0; *signature_length = cx_ecdsa_sign( - privateKey, + &privateKey, CX_RND_RFC6979 | CX_LAST, CX_SHA256, message_digest, @@ -73,19 +131,22 @@ int sign_secp256k1(const uint8_t *message, #endif } -void getPubKey(cx_ecfp_public_key_t *publicKey) { - cx_ecfp_private_key_t privateKey; - uint8_t privateKeyData[32]; - - // Generate keys - os_perso_derive_node_bip32(CX_CURVE_256K1, - bip32_path, - bip32_depth, - privateKeyData, NULL); - - keys_secp256k1(publicKey, &privateKey, privateKeyData); - memset(privateKeyData, 0, sizeof(privateKeyData)); - memset(&privateKey, 0, sizeof(privateKey)); +void updatePubKey() { + if (!bip32.cached) { + cx_ecfp_private_key_t privateKey; + uint8_t privateKeyData[32]; + + // Generate keys + os_perso_derive_node_bip32(CX_CURVE_256K1, + bip32.path, + BIP32_LEN_DEFAULT, + privateKeyData, NULL); + + keysSecp256k1(&publicKey, &privateKey, privateKeyData); + memset(privateKeyData, 0, sizeof(privateKeyData)); + memset(&privateKey, 0, sizeof(privateKey)); + bip32.cached = 1; + } } void ripemd160_32(uint8_t *out, uint8_t *in) { @@ -94,18 +155,15 @@ void ripemd160_32(uint8_t *out, uint8_t *in) { cx_hash(&rip160.header, CX_LAST, in, CX_SHA256_SIZE, out, CX_RIPEMD160_SIZE); } -void get_pk_compressed(uint8_t *pkc) { - cx_ecfp_public_key_t publicKey; - // Modify the last part of the path - getPubKey(&publicKey); - // "Compress" public key in place - publicKey.W[0] = publicKey.W[64] & 1 ? 0x03 : 0x02; +void getPubKeyCompressed(uint8_t *pkc) { + updatePubKey(); + publicKey.W[0] = publicKey.W[64] & 1 ? 0x03 : 0x02; // "Compress" public key in place memcpy(pkc, publicKey.W, PK_COMPRESSED_LEN); } -void get_bech32_addr(char *bech32_addr) { +void getBech32Addr(char *bech32_addr) { uint8_t tmp[PK_COMPRESSED_LEN]; - get_pk_compressed(tmp); + getPubKeyCompressed(tmp); uint8_t hashed_pk[CX_RIPEMD160_SIZE]; cx_hash_sha256(tmp, PK_COMPRESSED_LEN, tmp, CX_SHA256_SIZE); diff --git a/src/crypto.h b/src/crypto.h index 7188d087..4897b608 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -15,6 +15,7 @@ * limitations under the License. ********************************************************************************/ #pragma once + #include #include "os.h" #include "cx.h" @@ -22,41 +23,38 @@ #define MAX_BECH32_HRP_LEN 83 #define PK_COMPRESSED_LEN 33 -#define BIP32_LEN_DEFAULT 0 -#define BIP32_0_DEFAULT (0x80000000 | 44) -#define BIP32_1_DEFAULT (0x80000000 | 118) +#define BIP32_LEN_DEFAULT 5 +#define BIP32_0_DEFAULT (0x80000000 | 0x2c) +#define BIP32_1_DEFAULT (0x80000000 | 0x76) #define BIP32_2_DEFAULT (0x80000000 | 0) #define BIP32_3_DEFAULT (0) #define BIP32_4_DEFAULT (0) -#define BIP32_ACCOUNT (bip32_path[2] & 0x7FFFFFF) -#define BIP32_INDEX (bip32_path[4] & 0x7FFFFFF) - -typedef enum { - SECP256K1 = 0, - ED25519 = 1 -} sigtype_t; - -extern uint8_t bip32_depth; -extern uint32_t bip32_path[10]; -extern sigtype_t current_sigtype; +#define BIP32_NO_ERROR 0 +#define BIP32_INVALID_LENGTH -1 +#define BIP32_INVALID_PATH -2 extern char bech32_hrp[MAX_BECH32_HRP_LEN + 1]; extern uint8_t bech32_hrp_len; +extern cx_ecfp_public_key_t publicKey; + +void crypto_init(); -void keys_secp256k1(cx_ecfp_public_key_t *publicKey, - cx_ecfp_private_key_t *privateKey, - const uint8_t privateKeyData[32]); +int32_t getBip32Account(); +int32_t getBip32Index(); +void setBip32Index(uint32_t newIndex); +int8_t setBip32Path(uint32_t path0, + uint32_t path1, + uint32_t path2, + uint32_t path3, + uint32_t path4); + +void updatePubKey(); +void getPubKeyCompressed(uint8_t *pkc); +void getBech32Addr(char *bech32_addr); int sign_secp256k1(const uint8_t *message, unsigned int message_length, uint8_t *signature, unsigned int signature_capacity, - unsigned int *signature_length, - cx_ecfp_private_key_t *privateKey); - -void getPubKey(cx_ecfp_public_key_t *publicKey); - -void get_pk_compressed(uint8_t *pkc); - -void get_bech32_addr(char *bech32_addr); + unsigned int *signature_length); diff --git a/src/view.c b/src/view.c index a1b8b722..1ea4836f 100644 --- a/src/view.c +++ b/src/view.c @@ -167,27 +167,27 @@ static const bagl_element_t view_addr_choose[] = { (const char *) viewctl.dataKey), #if defined(TARGET_NANOX) - UI_Icon(UIID_ICONLEFT1, 2, 28, 4, 7, BAGL_GLYPH_ICON_LEFT), - UI_Icon(UIID_ICONRIGHT1, 122, 28, 4, 7, BAGL_GLYPH_ICON_RIGHT), - UI_Icon(UIID_ICONLEFT2, 2, 28, 4, 7, BAGL_GLYPH_ICON_LEFT), - UI_Icon(UIID_ICONRIGHT2, 122, 28, 4, 7, BAGL_GLYPH_ICON_RIGHT), - - UI_Icon(UIID_MARKER1, 0, 0, 7, 7, ((const char*)&C_digit_dot)), - UI_Icon(UIID_MARKER2, 0, 9, 7, 7, ((const char*)&C_digit_dot)), - - UI_LabelLine(UIID_LABEL+2, 0, 9 + UI_11PX * 2, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[0]), - UI_LabelLine(UIID_LABEL+3, 0, 9 + UI_11PX * 3, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[1]), - UI_LabelLine(UIID_LABEL+4, 0, 9 + UI_11PX * 4, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[2]), - UI_LabelLine(UIID_LABEL+5, 0, 9 + UI_11PX * 5, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[3]), +UI_Icon(UIID_ICONLEFT1, 2, 28, 4, 7, BAGL_GLYPH_ICON_LEFT), +UI_Icon(UIID_ICONRIGHT1, 122, 28, 4, 7, BAGL_GLYPH_ICON_RIGHT), +UI_Icon(UIID_ICONLEFT2, 2, 28, 4, 7, BAGL_GLYPH_ICON_LEFT), +UI_Icon(UIID_ICONRIGHT2, 122, 28, 4, 7, BAGL_GLYPH_ICON_RIGHT), + +UI_Icon(UIID_MARKER1, 0, 0, 7, 7, ((const char*)&C_digit_dot)), +UI_Icon(UIID_MARKER2, 0, 9, 7, 7, ((const char*)&C_digit_dot)), + +UI_LabelLine(UIID_LABEL+2, 0, 9 + UI_11PX * 2, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[0]), +UI_LabelLine(UIID_LABEL+3, 0, 9 + UI_11PX * 3, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[1]), +UI_LabelLine(UIID_LABEL+4, 0, 9 + UI_11PX * 4, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[2]), +UI_LabelLine(UIID_LABEL+5, 0, 9 + UI_11PX * 5, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[3]), #else - UI_Icon(UIID_ICONREJECT, 0, 0, 7, 7, BAGL_GLYPH_ICON_CROSS), - UI_Icon(UIID_ICONACCEPT, 128 - 7, 0, 7, 7, BAGL_GLYPH_ICON_CHECK), - UI_Icon(UIID_ICONLEFT1, 0, 0, 7, 7, BAGL_GLYPH_ICON_LEFT), - UI_Icon(UIID_ICONRIGHT1, 128 - 7, 0, 7, 7, BAGL_GLYPH_ICON_RIGHT), - UI_Icon(UIID_ICONLEFT2, 0, 9, 7, 7, BAGL_GLYPH_ICON_LEFT), - UI_Icon(UIID_ICONRIGHT2, 128 - 7, 9, 7, 7, BAGL_GLYPH_ICON_RIGHT), - - UI_LabelLineScrolling(UIID_LABELSCROLL, 16, 30, 96, 11, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValue), + UI_Icon(UIID_ICONREJECT, 0, 0, 7, 7, BAGL_GLYPH_ICON_CROSS), + UI_Icon(UIID_ICONACCEPT, 128 - 7, 0, 7, 7, BAGL_GLYPH_ICON_CHECK), + UI_Icon(UIID_ICONLEFT1, 0, 0, 7, 7, BAGL_GLYPH_ICON_LEFT), + UI_Icon(UIID_ICONRIGHT1, 128 - 7, 0, 7, 7, BAGL_GLYPH_ICON_RIGHT), + UI_Icon(UIID_ICONLEFT2, 0, 9, 7, 7, BAGL_GLYPH_ICON_LEFT), + UI_Icon(UIID_ICONRIGHT2, 128 - 7, 9, 7, 7, BAGL_GLYPH_ICON_RIGHT), + + UI_LabelLineScrolling(UIID_LABELSCROLL, 16, 30, 96, 11, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValue), #endif }; @@ -385,13 +385,12 @@ void view_addr_choose_update() { view_addr_choose_data.status.mode == VIEW_ADDR_MODE_CONFIRM) { print_value("This is a very long string that needs to be scrolled otherwise it does not fit"); - bip32_depth = 5; - bip32_path[0] = BIP32_0_DEFAULT; - bip32_path[1] = BIP32_1_DEFAULT; - bip32_path[2] = 0x80000000 | view_addr_choose_data.account; - bip32_path[3] = BIP32_3_DEFAULT; - bip32_path[4] = view_addr_choose_data.index; - get_bech32_addr(viewctl.dataValue); + setBip32Path(BIP32_0_DEFAULT, + BIP32_1_DEFAULT, + 0x80000000 | view_addr_choose_data.account, + BIP32_3_DEFAULT, + view_addr_choose_data.index); + getBech32Addr(viewctl.dataValue); } viewctl_dataValue_split(); @@ -417,8 +416,8 @@ void view_addr_choose_show(unsigned int _) { void view_addr_confirm(unsigned int _) { view_addr_choose_data.status.mode = VIEW_ADDR_MODE_CONFIRM; - view_addr_choose_data.account = BIP32_ACCOUNT; - view_addr_choose_data.index = BIP32_INDEX; + view_addr_choose_data.account = getBip32Account(); + view_addr_choose_data.index = getBip32Index(); view_addr_choose_update(); #if defined(TARGET_NANOS) From b46201d0c152e3c05d896ba7a1f66155a8b495f9 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Thu, 29 Aug 2019 08:13:44 +0200 Subject: [PATCH 03/78] Adding heartbeats / bumping to 1.6.1 --- Makefile | 2 +- src/crypto.c | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 355be75f..26d96dd0 100755 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ include $(BOLOS_SDK)/Makefile.defines APPNAME = "Cosmos" APPVERSION_M=1 APPVERSION_N=6 -APPVERSION_P=0 +APPVERSION_P=1 APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path "44'/118'" diff --git a/src/crypto.c b/src/crypto.c index 4379c41c..54a4e3d9 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -93,6 +93,10 @@ int sign_secp256k1(const uint8_t *message, cx_ecfp_private_key_t privateKey; uint8_t privateKeyData[32]; + ///////// + io_seproxyhal_io_heartbeat(); + ///////// + os_perso_derive_node_bip32(CX_CURVE_256K1, bip32.path, BIP32_LEN_DEFAULT, @@ -100,10 +104,18 @@ int sign_secp256k1(const uint8_t *message, keysSecp256k1(&publicKey, &privateKey, privateKeyData); memset(privateKeyData, 0, 32); + ///////// + io_seproxyhal_io_heartbeat(); + ///////// + // Hash uint8_t message_digest[CX_SHA256_SIZE]; cx_hash_sha256(message, message_length, message_digest, CX_SHA256_SIZE); + ///////// + io_seproxyhal_io_heartbeat(); + ///////// + // Sign unsigned int info = 0; *signature_length = cx_ecdsa_sign( @@ -116,6 +128,10 @@ int sign_secp256k1(const uint8_t *message, signature_capacity, &info); + ///////// + io_seproxyhal_io_heartbeat(); + ///////// + os_memset(&privateKey, 0, sizeof(privateKey)); #ifdef TESTING_ENABLED return cx_ecdsa_verify( From 25e5f8b2b9ac971e35e028a5d9091268f84ba853 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Thu, 29 Aug 2019 09:33:02 +0200 Subject: [PATCH 04/78] extending stack size --- script.ld | 14 +++++++------- src/lib/transaction.c | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/script.ld b/script.ld index a104e6d3..7eae328e 100644 --- a/script.ld +++ b/script.ld @@ -30,7 +30,7 @@ MEMORY } PAGE_SIZE = 64; -STACK_SIZE = 820; +STACK_SIZE = 848; END_STACK = ORIGIN(SRAM) + LENGTH(SRAM); SECTIONS @@ -52,7 +52,7 @@ SECTIONS /* ensure main is always @ 0xC0D00000 */ *(.boot*) - + /* place the other code and rodata defined BUT nvram variables that are displaced in a r/w area */ *(.text*) *(.rodata) @@ -60,14 +60,14 @@ SECTIONS *(.rodata.N[^_]*) . = ALIGN(4); - + /* all code placed */ _etext = .; . = ALIGN(PAGE_SIZE); _nvram_data = .; - + /* NVM data (ex-filesystem) */ *(.bss.N_* .rodata.N_*) @@ -76,13 +76,13 @@ SECTIONS PROVIDE(N_install_parameters = .); _envram = .; _nvram_data_size = _envram - _nvram_data; - + } > FLASH = 0x00 .data (NOLOAD): { . = ALIGN(4); - + /** * Place RAM initialized variables */ @@ -165,5 +165,5 @@ SECTIONS .debug_funcnames 0 : { *(.debug_funcnames) } .debug_typenames 0 : { *(.debug_typenames) } .debug_varnames 0 : { *(.debug_varnames) } - + } diff --git a/src/lib/transaction.c b/src/lib/transaction.c index 86235ae6..0f3b3c47 100644 --- a/src/lib/transaction.c +++ b/src/lib/transaction.c @@ -28,7 +28,7 @@ #define RAM_BUFFER_SIZE 8192 #define FLASH_BUFFER_SIZE 16384 #elif defined(TARGET_NANOS) - #define RAM_BUFFER_SIZE 416 + #define RAM_BUFFER_SIZE 384 #define FLASH_BUFFER_SIZE 8192 #endif From 740e6d924887cd0eba4cd28fee4dd61175743650 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 3 Sep 2019 14:28:47 +0200 Subject: [PATCH 05/78] introducing actions --- src/actions.c | 50 ++++++++++++++++++++++ src/actions.h | 29 +++++++++++++ src/app_main.c | 52 ++++++++--------------- src/lib/cosmos.h | 33 +++++++++++++++ src/{ => lib}/crypto.c | 94 +++++++++++++++++++++++++++++------------- src/{ => lib}/crypto.h | 38 ++++++++++------- src/view.c | 7 +++- 7 files changed, 223 insertions(+), 80 deletions(-) create mode 100644 src/actions.c create mode 100644 src/actions.h create mode 100644 src/lib/cosmos.h rename src/{ => lib}/crypto.c (68%) rename src/{ => lib}/crypto.h (70%) diff --git a/src/actions.c b/src/actions.c new file mode 100644 index 00000000..e61681f0 --- /dev/null +++ b/src/actions.c @@ -0,0 +1,50 @@ +/******************************************************************************* +* (c) 2016 Ledger +* (c) 2019 ZondaX GmbH +* +* 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 "actions.h" +#include "lib/crypto.h" +#include "transaction.h" +#include "apdu_codes.h" +#include + +uint8_t app_sign() { + uint8_t *signature = G_io_apdu_buffer; + const uint8_t *message = transaction_get_buffer(); + const uint16_t messageLength = transaction_get_buffer_length(); + + return crypto_sign(signature, IO_APDU_BUFFER_SIZE - 2, message, messageLength); +} + +void app_set_hrp(char *p) { + crypto_set_hrp(p); +} + +uint8_t app_fill_address() { + // Put data directly in the apdu buffer + return crypto_fillAddress(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE); +} + +void app_reply_address() { + const uint8_t replyLen = app_fill_address(); + set_code(G_io_apdu_buffer, replyLen, APDU_CODE_OK); + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, replyLen + 2); +} + +void app_reply_error() { + set_code(G_io_apdu_buffer, 0, APDU_CODE_DATA_INVALID); + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); +} diff --git a/src/actions.h b/src/actions.h new file mode 100644 index 00000000..1ec06a38 --- /dev/null +++ b/src/actions.h @@ -0,0 +1,29 @@ +/******************************************************************************* +* (c) 2016 Ledger +* (c) 2019 ZondaX GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +#pragma once + +#include + +uint8_t app_sign(); + +void app_set_hrp(char *p); + +uint8_t app_fill_address(); + +void app_reply_address(); + +void app_reply_error(); diff --git a/src/app_main.c b/src/app_main.c index 18b04660..7058f369 100644 --- a/src/app_main.c +++ b/src/app_main.c @@ -24,10 +24,12 @@ #include #include +#include "actions.h" + #include "lib/transaction.h" #include "lib/tx_display.h" #include "view.h" -#include "crypto.h" +#include "lib/crypto.h" #ifdef TESTING_ENABLED // Generate using always the same private data @@ -59,9 +61,9 @@ unsigned char io_event(unsigned char channel) { case SEPROXYHAL_TAG_TICKER_EVENT: { // UX_TICKER_EVENT(G_io_seproxyhal_spi_buffer, { - if (UX_ALLOWED) { - UX_REDISPLAY(); - } + if (UX_ALLOWED) { + UX_REDISPLAY(); + } }); break; } @@ -102,9 +104,9 @@ unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) { return 0; } +void extractBip32(uint32_t rx, uint32_t offset) { #define UINT32_FROM_LE(p) ( (*(p+3) << 24) + (*(p+2) << 16) + (*(p+1) << 8) + (*(p+0))) -void extractBip32(uint32_t rx, uint32_t offset) { if (rx < offset + 1) { THROW(APDU_CODE_DATA_INVALID); } @@ -119,7 +121,7 @@ void extractBip32(uint32_t rx, uint32_t offset) { THROW(APDU_CODE_DATA_INVALID); } - uint8_t *p = (uint8_t *) (G_io_apdu_buffer + offset + 1); + uint8_t *p = (uint8_t * )(G_io_apdu_buffer + offset + 1); if (setBip32Path(UINT32_FROM_LE(p + 0), UINT32_FROM_LE(p + 4), @@ -130,21 +132,6 @@ void extractBip32(uint32_t rx, uint32_t offset) { } } -void extractHRP(uint8_t *len, char *hrp, uint32_t rx, uint32_t offset) { - if (rx < offset + 1) { - THROW(APDU_CODE_DATA_INVALID); - } - - *len = G_io_apdu_buffer[offset]; - - if (*len == 0 || *len > MAX_BECH32_HRP_LEN) { - THROW(APDU_CODE_DATA_INVALID); - } - - memcpy(hrp, G_io_apdu_buffer + offset + 1, *len); - hrp[*len] = 0; // zero terminate -} - bool process_chunk(volatile uint32_t *tx, uint32_t rx, bool getBip32) { int packageIndex = G_io_apdu_buffer[OFFSET_PCK_INDEX]; int packageCount = G_io_apdu_buffer[OFFSET_PCK_COUNT]; @@ -201,22 +188,17 @@ int16_t tx_getData(char *title, int16_t max_title_length, } void tx_accept_sign() { - unsigned int length = 0; - int result = sign_secp256k1(transaction_get_buffer(), - transaction_get_buffer_length(), - G_io_apdu_buffer, - IO_APDU_BUFFER_SIZE, - &length); - - if (result == 1) { - set_code(G_io_apdu_buffer, length, APDU_CODE_OK); - io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, length + 2); - view_idle(0); - } else { + uint8_t length = app_sign(); + + if (length == 0) { set_code(G_io_apdu_buffer, length, APDU_CODE_SIGN_VERIFY_ERROR); io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, length + 2); view_idle(0); } + + set_code(G_io_apdu_buffer, length, APDU_CODE_OK); + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, length + 2); + view_idle(0); } void tx_reject() { @@ -332,8 +314,8 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { case INS_GET_ADDR_SECP256K1: { // Parse arguments - extractHRP(&bech32_hrp_len, bech32_hrp, rx, OFFSET_DATA); - extractBip32(rx, OFFSET_DATA + 1 + bech32_hrp_len); + uint8_t len = extractHRP(rx, OFFSET_DATA); + extractBip32(rx, OFFSET_DATA + 1 + len); view_set_handlers(addr_getData, addr_accept, addr_reject); view_addr_confirm(0); diff --git a/src/lib/cosmos.h b/src/lib/cosmos.h new file mode 100644 index 00000000..1094ea05 --- /dev/null +++ b/src/lib/cosmos.h @@ -0,0 +1,33 @@ +/******************************************************************************* +* (c) 2019 ZondaX GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define BIP32_0_DEFAULT (0x80000000 | 0x2c) +#define BIP32_1_DEFAULT (0x80000000 | 0x76) +#define BIP32_2_DEFAULT (0x80000000 | 0) +#define BIP32_3_DEFAULT (0) +#define BIP32_4_DEFAULT (0) + +#ifdef __cplusplus +} +#endif diff --git a/src/crypto.c b/src/lib/crypto.c similarity index 68% rename from src/crypto.c rename to src/lib/crypto.c index 54a4e3d9..ad064bed 100644 --- a/src/crypto.c +++ b/src/lib/crypto.c @@ -18,6 +18,7 @@ #include "apdu_codes.h" #include "zxmacros.h" +#include "cosmos.h" typedef struct { unsigned int cached : 1; @@ -74,6 +75,25 @@ void setBip32Index(uint32_t newIndex) { bip32.path[4] = newIndex; } +uint8_t extractHRP(uint32_t rx, uint32_t offset) { + MEMSET(bech32_hrp, 0, MAX_BECH32_HRP_LEN); + + if (rx < offset + 1) { + THROW(APDU_CODE_DATA_INVALID); + } + + bech32_hrp_len = G_io_apdu_buffer[offset]; + + if (bech32_hrp_len == 0 || bech32_hrp_len > MAX_BECH32_HRP_LEN) { + THROW(APDU_CODE_DATA_INVALID); + } + + memcpy(bech32_hrp, G_io_apdu_buffer + offset + 1, bech32_hrp_len); + bech32_hrp[bech32_hrp_len] = 0; // zero terminate + + return bech32_hrp_len; +} + void keysSecp256k1(cx_ecfp_public_key_t *publicKey, cx_ecfp_private_key_t *privateKey, const uint8_t *privateKeyData) { @@ -82,12 +102,7 @@ void keysSecp256k1(cx_ecfp_public_key_t *publicKey, cx_ecfp_generate_pair(CX_CURVE_256K1, publicKey, privateKey, 1); } -int sign_secp256k1(const uint8_t *message, - unsigned int message_length, - uint8_t *signature, - unsigned int signature_capacity, - unsigned int *signature_length) { - +uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t *message, uint16_t messageLen) { // Generate keys cx_ecfp_public_key_t publicKey; cx_ecfp_private_key_t privateKey; @@ -110,7 +125,7 @@ int sign_secp256k1(const uint8_t *message, // Hash uint8_t message_digest[CX_SHA256_SIZE]; - cx_hash_sha256(message, message_length, message_digest, CX_SHA256_SIZE); + cx_hash_sha256(message, messageLen, message_digest, CX_SHA256_SIZE); ///////// io_seproxyhal_io_heartbeat(); @@ -118,33 +133,22 @@ int sign_secp256k1(const uint8_t *message, // Sign unsigned int info = 0; - *signature_length = cx_ecdsa_sign( - &privateKey, - CX_RND_RFC6979 | CX_LAST, - CX_SHA256, - message_digest, - CX_SHA256_SIZE, - signature, - signature_capacity, - &info); + int signatureLength = cx_ecdsa_sign( + &privateKey, + CX_RND_RFC6979 | CX_LAST, + CX_SHA256, + message_digest, + CX_SHA256_SIZE, + signature, + signatureMaxlen, + &info); ///////// io_seproxyhal_io_heartbeat(); ///////// os_memset(&privateKey, 0, sizeof(privateKey)); -#ifdef TESTING_ENABLED - return cx_ecdsa_verify( - &publicKey, - CX_LAST, - CX_SHA256, - message_digest, - CX_SHA256_SIZE, - signature, - *signature_length); -#else - return 1; -#endif + return signatureLength; } void updatePubKey() { @@ -187,3 +191,37 @@ void getBech32Addr(char *bech32_addr) { bech32EncodeFromBytes(bech32_addr, bech32_hrp, hashed_pk, CX_RIPEMD160_SIZE); } + +/////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////// + +void crypto_set_hrp(char *p) { + bech32_hrp_len = strlen(p); + if (bech32_hrp_len < MAX_BECH32_HRP_LEN) { + strcpy(bech32_hrp, p); + } +} + +uint16_t crypto_fillAddress(uint8_t *buffer, uint16_t buffer_len) { +//// if (buffer_len < ED25519_PK_LEN + 30) { +//// return 0; +//// } +//// +//// // extract pubkey (first 32 bytes) +//// crypto_extractPublicKey(bip32Path, buffer); +//// +//// char tmp[IOV_PK_PREFIX_LEN + ED25519_PK_LEN]; +//// strcpy(tmp, IOV_PK_PREFIX); +//// MEMCPY(tmp + IOV_PK_PREFIX_LEN, buffer, ED25519_PK_LEN); +//// +//// // +//// uint8_t hash[CX_SHA256_SIZE]; +//// cx_hash_sha256((uint8_t *) tmp, IOV_PK_PREFIX_LEN + ED25519_PK_LEN, +//// hash, CX_SHA256_SIZE); +//// +//// char *addr = (char *) (buffer + ED25519_PK_LEN); +//// bech32EncodeFromBytes(addr, hrp, hash, 20); +// return ED25519_PK_LEN + strlen(addr); + return 0; +} diff --git a/src/crypto.h b/src/lib/crypto.h similarity index 70% rename from src/crypto.h rename to src/lib/crypto.h index 4897b608..32581dee 100644 --- a/src/crypto.h +++ b/src/lib/crypto.h @@ -16,26 +16,21 @@ ********************************************************************************/ #pragma once -#include -#include "os.h" -#include "cx.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif #define MAX_BECH32_HRP_LEN 83 #define PK_COMPRESSED_LEN 33 #define BIP32_LEN_DEFAULT 5 -#define BIP32_0_DEFAULT (0x80000000 | 0x2c) -#define BIP32_1_DEFAULT (0x80000000 | 0x76) -#define BIP32_2_DEFAULT (0x80000000 | 0) -#define BIP32_3_DEFAULT (0) -#define BIP32_4_DEFAULT (0) #define BIP32_NO_ERROR 0 #define BIP32_INVALID_LENGTH -1 #define BIP32_INVALID_PATH -2 -extern char bech32_hrp[MAX_BECH32_HRP_LEN + 1]; -extern uint8_t bech32_hrp_len; extern cx_ecfp_public_key_t publicKey; void crypto_init(); @@ -53,8 +48,21 @@ void updatePubKey(); void getPubKeyCompressed(uint8_t *pkc); void getBech32Addr(char *bech32_addr); -int sign_secp256k1(const uint8_t *message, - unsigned int message_length, - uint8_t *signature, - unsigned int signature_capacity, - unsigned int *signature_length); +////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////// + +extern uint32_t bip32Path[BIP32_LEN_DEFAULT]; +extern char *hrp; + +uint8_t extractHRP(uint32_t rx, uint32_t offset); + +void crypto_set_hrp(char *p); + +uint16_t crypto_fillAddress(uint8_t *buffer, uint16_t buffer_len); + +uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t *message, uint16_t messageLen); + +#ifdef __cplusplus +} +#endif diff --git a/src/view.c b/src/view.c index 1ea4836f..75e842a6 100644 --- a/src/view.c +++ b/src/view.c @@ -22,7 +22,8 @@ #include "glyphs.h" #include "bagl.h" #include "zxmacros.h" -#include "crypto.h" +#include "lib/crypto.h" +#include "cosmos.h" #include #include @@ -405,7 +406,9 @@ void view_addr_choose_show(unsigned int _) { view_addr_choose_data.status.mode = VIEW_ADDR_MODE_ACCOUNT; view_addr_choose_data.account = 0; view_addr_choose_data.index = 0; - strcpy(bech32_hrp, "cosmos"); + + crypto_set_hrp("cosmos"); + ehAccept = show_idle_menu; ehReject = NULL; From 1cdc254ae6729d6f8484facee4e43e3386b994ea Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 3 Sep 2019 15:59:44 +0200 Subject: [PATCH 06/78] fixing actions --- Makefile | 6 +- src/app_main.c | 25 ++---- src/app_main.h | 11 ++- src/lib/crypto.c | 220 ++++++++++++++++++++++------------------------- src/lib/crypto.h | 4 +- src/view.c | 9 ++ src/view.h | 2 + 7 files changed, 133 insertions(+), 144 deletions(-) diff --git a/Makefile b/Makefile index 26d96dd0..3b0c4201 100755 --- a/Makefile +++ b/Makefile @@ -23,9 +23,9 @@ include $(BOLOS_SDK)/Makefile.defines # Main app configuration APPNAME = "Cosmos" -APPVERSION_M=1 -APPVERSION_N=6 -APPVERSION_P=1 +APPVERSION_M=2 +APPVERSION_N=0 +APPVERSION_P=0 APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path "44'/118'" diff --git a/src/app_main.c b/src/app_main.c index 7058f369..ba0feec8 100644 --- a/src/app_main.c +++ b/src/app_main.c @@ -22,7 +22,6 @@ #include #include -#include #include "actions.h" @@ -300,27 +299,21 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { break; } - // INS_PUBLIC_KEY_SECP256K1 will be deprecated in the near future - case INS_PUBLIC_KEY_SECP256K1: { - extractBip32(rx, OFFSET_DATA); - - updatePubKey(); - os_memmove(G_io_apdu_buffer, publicKey.W, 65); - *tx += 65; - - THROW(APDU_CODE_OK); - break; - } - case INS_GET_ADDR_SECP256K1: { // Parse arguments + uint8_t requireConfirmation = G_io_apdu_buffer[OFFSET_P1]; uint8_t len = extractHRP(rx, OFFSET_DATA); extractBip32(rx, OFFSET_DATA + 1 + len); - view_set_handlers(addr_getData, addr_accept, addr_reject); - view_addr_confirm(0); + if (requireConfirmation) { + app_fill_address(); + view_address_show(); + *flags |= IO_ASYNCH_REPLY; + break; + } - *flags |= IO_ASYNCH_REPLY; + *tx = app_fill_address(); + THROW(APDU_CODE_OK); break; } diff --git a/src/app_main.h b/src/app_main.h index 291bc5a9..c8d30a05 100644 --- a/src/app_main.h +++ b/src/app_main.h @@ -23,14 +23,17 @@ #define OFFSET_CLA 0 #define OFFSET_INS 1 //< Instruction offset -#define OFFSET_PCK_INDEX 2 //< Package index offset -#define OFFSET_PCK_COUNT 3 //< Package count offset +#define OFFSET_P1 2 //< P1 +#define OFFSET_P2 3 //< P2 +#define OFFSET_DATA_LEN 4 //< Data Length #define OFFSET_DATA 5 //< Data offset +#define OFFSET_PCK_INDEX OFFSET_P1 //< Package index offset +#define OFFSET_PCK_COUNT OFFSET_P2 //< Package count offset +#define APDU_MIN_LENGTH 5 + #define INS_GET_VERSION 0 -#define INS_PUBLIC_KEY_SECP256K1 1 // It will be deprecated in the near future #define INS_SIGN_SECP256K1 2 -//#define INS_SHOW_ADDR_SECP256K1 3 DEPRECATED #define INS_GET_ADDR_SECP256K1 4 #ifdef TESTING_ENABLED diff --git a/src/lib/crypto.c b/src/lib/crypto.c index ad064bed..f727093c 100644 --- a/src/lib/crypto.c +++ b/src/lib/crypto.c @@ -15,6 +15,7 @@ * limitations under the License. ********************************************************************************/ #include "crypto.h" +#include #include "apdu_codes.h" #include "zxmacros.h" @@ -30,13 +31,94 @@ bip32_t bip32; uint8_t bech32_hrp_len; char bech32_hrp[MAX_BECH32_HRP_LEN + 1]; -cx_ecfp_public_key_t publicKey; - void crypto_init() { bip32.valid = 0; bip32.cached = 0; } +#define SAFE_HEARTBEAT(X) io_seproxyhal_io_heartbeat(); X; io_seproxyhal_io_heartbeat(); + +#if defined(TARGET_NANOS) || defined(TARGET_NANOX) +void crypto_extractPublicKey(uint32_t bip32Path[BIP32_LEN_DEFAULT], uint8_t *pubKey){ + cx_ecfp_public_key_t cx_publicKey; + cx_ecfp_private_key_t cx_privateKey; + uint8_t privateKeyData[32]; + + SAFE_HEARTBEAT(os_perso_derive_node_bip32(CX_CURVE_256K1, + bip32.path, + BIP32_LEN_DEFAULT, + privateKeyData, NULL)); + + ////////////////////// + SAFE_HEARTBEAT(cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &cx_privateKey)); + SAFE_HEARTBEAT(cx_ecfp_init_public_key(CX_CURVE_256K1, NULL, 0, &cx_publicKey)); + SAFE_HEARTBEAT(cx_ecfp_generate_pair(CX_CURVE_256K1, &cx_publicKey, &cx_privateKey, 1)); + + MEMSET(&cx_privateKey, 0, sizeof(cx_privateKey)); + MEMSET(privateKeyData, 0, 32); + + // Format pubkey + for (int i = 0; i < 32; i++) { + pubKey[i] = cx_publicKey.W[64 - i]; + } + cx_publicKey.W[0] = cx_publicKey.W[64] & 1 ? 0x03 : 0x02; // "Compress" public key in place + if ((cx_publicKey.W[32] & 1) != 0) { + pubKey[31] |= 0x80; + } + ////////////////////// + memcpy(pubKey, cx_publicKey.W, PK_COMPRESSED_LEN); +} + +uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t *message, uint16_t messageLen) { + // Hash + uint8_t message_digest[CX_SHA256_SIZE]; + SAFE_HEARTBEAT(cx_hash_sha256(message, messageLen, message_digest, CX_SHA256_SIZE)); + + // Generate keys + cx_ecfp_private_key_t cx_privateKey; + uint8_t privateKeyData[32]; + SAFE_HEARTBEAT(os_perso_derive_node_bip32(CX_CURVE_256K1, + bip32.path, + BIP32_LEN_DEFAULT, + privateKeyData, NULL)); + + io_seproxyhal_io_heartbeat(); + SAFE_HEARTBEAT(cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &cx_privateKey)); + + // Sign + unsigned int info = 0; + SAFE_HEARTBEAT( + int signatureLength = cx_ecdsa_sign(&cx_privateKey, + CX_RND_RFC6979 | CX_LAST, + CX_SHA256, + message_digest, + CX_SHA256_SIZE, + signature, + signatureMaxlen, + &info)) + + MEMSET(&cx_privateKey, 0, sizeof(cx_privateKey)); + MEMSET(privateKeyData, 0, 32); + + return signatureLength; +} +#else + +void crypto_extractPublicKey(uint32_t path[BIP32_LEN_DEFAULT], uint8_t *pubKey) { + // Empty version for non-Ledger devices + MEMSET(pubKey, 0, 32); +} + +uint16_t crypto_sign(uint8_t *signature, + uint16_t signatureMaxlen, + const uint8_t *message, + uint16_t messageLen) { + // Empty version for non-Ledger devices + return 0; +} + +#endif + int8_t setBip32Path(uint32_t path0, uint32_t path1, uint32_t path2, @@ -94,108 +176,12 @@ uint8_t extractHRP(uint32_t rx, uint32_t offset) { return bech32_hrp_len; } -void keysSecp256k1(cx_ecfp_public_key_t *publicKey, - cx_ecfp_private_key_t *privateKey, - const uint8_t *privateKeyData) { - cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, privateKey); - cx_ecfp_init_public_key(CX_CURVE_256K1, NULL, 0, publicKey); - cx_ecfp_generate_pair(CX_CURVE_256K1, publicKey, privateKey, 1); -} - -uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t *message, uint16_t messageLen) { - // Generate keys - cx_ecfp_public_key_t publicKey; - cx_ecfp_private_key_t privateKey; - uint8_t privateKeyData[32]; - - ///////// - io_seproxyhal_io_heartbeat(); - ///////// - - os_perso_derive_node_bip32(CX_CURVE_256K1, - bip32.path, - BIP32_LEN_DEFAULT, - privateKeyData, NULL); - keysSecp256k1(&publicKey, &privateKey, privateKeyData); - memset(privateKeyData, 0, 32); - - ///////// - io_seproxyhal_io_heartbeat(); - ///////// - - // Hash - uint8_t message_digest[CX_SHA256_SIZE]; - cx_hash_sha256(message, messageLen, message_digest, CX_SHA256_SIZE); - - ///////// - io_seproxyhal_io_heartbeat(); - ///////// - - // Sign - unsigned int info = 0; - int signatureLength = cx_ecdsa_sign( - &privateKey, - CX_RND_RFC6979 | CX_LAST, - CX_SHA256, - message_digest, - CX_SHA256_SIZE, - signature, - signatureMaxlen, - &info); - - ///////// - io_seproxyhal_io_heartbeat(); - ///////// - - os_memset(&privateKey, 0, sizeof(privateKey)); - return signatureLength; -} - -void updatePubKey() { - if (!bip32.cached) { - cx_ecfp_private_key_t privateKey; - uint8_t privateKeyData[32]; - - // Generate keys - os_perso_derive_node_bip32(CX_CURVE_256K1, - bip32.path, - BIP32_LEN_DEFAULT, - privateKeyData, NULL); - - keysSecp256k1(&publicKey, &privateKey, privateKeyData); - memset(privateKeyData, 0, sizeof(privateKeyData)); - memset(&privateKey, 0, sizeof(privateKey)); - bip32.cached = 1; - } -} - void ripemd160_32(uint8_t *out, uint8_t *in) { cx_ripemd160_t rip160; cx_ripemd160_init(&rip160); cx_hash(&rip160.header, CX_LAST, in, CX_SHA256_SIZE, out, CX_RIPEMD160_SIZE); } -void getPubKeyCompressed(uint8_t *pkc) { - updatePubKey(); - publicKey.W[0] = publicKey.W[64] & 1 ? 0x03 : 0x02; // "Compress" public key in place - memcpy(pkc, publicKey.W, PK_COMPRESSED_LEN); -} - -void getBech32Addr(char *bech32_addr) { - uint8_t tmp[PK_COMPRESSED_LEN]; - getPubKeyCompressed(tmp); - - uint8_t hashed_pk[CX_RIPEMD160_SIZE]; - cx_hash_sha256(tmp, PK_COMPRESSED_LEN, tmp, CX_SHA256_SIZE); - ripemd160_32(hashed_pk, tmp); - - bech32EncodeFromBytes(bech32_addr, bech32_hrp, hashed_pk, CX_RIPEMD160_SIZE); -} - -/////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////// - void crypto_set_hrp(char *p) { bech32_hrp_len = strlen(p); if (bech32_hrp_len < MAX_BECH32_HRP_LEN) { @@ -204,24 +190,22 @@ void crypto_set_hrp(char *p) { } uint16_t crypto_fillAddress(uint8_t *buffer, uint16_t buffer_len) { -//// if (buffer_len < ED25519_PK_LEN + 30) { -//// return 0; -//// } -//// -//// // extract pubkey (first 32 bytes) -//// crypto_extractPublicKey(bip32Path, buffer); -//// -//// char tmp[IOV_PK_PREFIX_LEN + ED25519_PK_LEN]; -//// strcpy(tmp, IOV_PK_PREFIX); -//// MEMCPY(tmp + IOV_PK_PREFIX_LEN, buffer, ED25519_PK_LEN); -//// -//// // -//// uint8_t hash[CX_SHA256_SIZE]; -//// cx_hash_sha256((uint8_t *) tmp, IOV_PK_PREFIX_LEN + ED25519_PK_LEN, -//// hash, CX_SHA256_SIZE); -//// -//// char *addr = (char *) (buffer + ED25519_PK_LEN); -//// bech32EncodeFromBytes(addr, hrp, hash, 20); -// return ED25519_PK_LEN + strlen(addr); - return 0; + if (buffer_len < PUBKEY_LEN + 50) { + return 0; + } + + // extract pubkey + crypto_extractPublicKey(bip32Path, buffer); + + // Hash it + uint8_t hashed1_pk[CX_SHA256_SIZE]; + cx_hash_sha256(buffer, PK_COMPRESSED_LEN, hashed1_pk, CX_SHA256_SIZE); + + uint8_t hashed2_pk[CX_RIPEMD160_SIZE]; + ripemd160_32(hashed2_pk, hashed1_pk); + + char *addr = (char *) (buffer + PUBKEY_LEN); + bech32EncodeFromBytes(bech32_addr, bech32_hrp, hashed2_pk, CX_RIPEMD160_SIZE); + + return PUBKEY_LEN + strlen(addr); } diff --git a/src/lib/crypto.h b/src/lib/crypto.h index 32581dee..3a0052e4 100644 --- a/src/lib/crypto.h +++ b/src/lib/crypto.h @@ -26,13 +26,12 @@ extern "C" { #define PK_COMPRESSED_LEN 33 #define BIP32_LEN_DEFAULT 5 +#define PUBKEY_LEN 33 #define BIP32_NO_ERROR 0 #define BIP32_INVALID_LENGTH -1 #define BIP32_INVALID_PATH -2 -extern cx_ecfp_public_key_t publicKey; - void crypto_init(); int32_t getBip32Account(); @@ -44,7 +43,6 @@ int8_t setBip32Path(uint32_t path0, uint32_t path3, uint32_t path4); -void updatePubKey(); void getPubKeyCompressed(uint8_t *pkc); void getBech32Addr(char *bech32_addr); diff --git a/src/view.c b/src/view.c index 75e842a6..d37c831b 100644 --- a/src/view.c +++ b/src/view.c @@ -453,3 +453,12 @@ void view_set_handlers(viewctl_delegate_getData func_getData, ehAccept = func_accept; ehReject = func_reject; } + +const char *address; +void view_address_show() { + // Address has been placed in the output buffer + address = (char *) (G_io_apdu_buffer + PUBKEY_LEN); + + // FIXME: + //view_address_show_impl(); +} diff --git a/src/view.h b/src/view.h index 051b92ea..b5674300 100644 --- a/src/view.h +++ b/src/view.h @@ -42,3 +42,5 @@ void view_tx_show(unsigned int start_page); /// view_addr_confirm (show/accept public key + address request) void view_addr_confirm(unsigned int _); + +void view_address_show(); From e5d7b957de824fc4ea6157069c38b299e1f93125 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 3 Sep 2019 16:22:21 +0200 Subject: [PATCH 07/78] refactor bip32 path handling --- src/app_main.c | 73 ++------------ src/lib/cosmos.h | 2 + src/lib/crypto.c | 56 +---------- src/lib/crypto.h | 29 +----- src/view.c | 257 ----------------------------------------------- 5 files changed, 16 insertions(+), 401 deletions(-) diff --git a/src/app_main.c b/src/app_main.c index ba0feec8..6db3f392 100644 --- a/src/app_main.c +++ b/src/app_main.c @@ -24,6 +24,7 @@ #include #include "actions.h" +#include "cosmos.h" #include "lib/transaction.h" #include "lib/tx_display.h" @@ -105,8 +106,7 @@ unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) { void extractBip32(uint32_t rx, uint32_t offset) { #define UINT32_FROM_LE(p) ( (*(p+3) << 24) + (*(p+2) << 16) + (*(p+1) << 8) + (*(p+0))) - - if (rx < offset + 1) { + if ((rx - offset) < 1 + 4 * BIP32_LEN_DEFAULT) { THROW(APDU_CODE_DATA_INVALID); } @@ -115,18 +115,12 @@ void extractBip32(uint32_t rx, uint32_t offset) { THROW(APDU_CODE_DATA_INVALID); } - const uint16_t req_offset = offset + 1 + 4 * BIP32_LEN_DEFAULT; - if (rx < req_offset) { - THROW(APDU_CODE_DATA_INVALID); - } - uint8_t *p = (uint8_t * )(G_io_apdu_buffer + offset + 1); + memcpy(bip32Path, p, 4 * BIP32_LEN_DEFAULT); - if (setBip32Path(UINT32_FROM_LE(p + 0), - UINT32_FROM_LE(p + 4), - UINT32_FROM_LE(p + 8), - UINT32_FROM_LE(p + 12), - UINT32_FROM_LE(p + 16)) != BIP32_NO_ERROR) { + if (bip32Path[0] != BIP32_0_DEFAULT || + bip32Path[1] != BIP32_1_DEFAULT || + bip32Path[3] != BIP32_3_DEFAULT) { THROW(APDU_CODE_DATA_INVALID); } } @@ -208,60 +202,6 @@ void tx_reject() { //endregion -//region View Address Handlers - -int16_t addr_getData(char *title, int16_t max_title_length, - char *key, int16_t max_key_length, - char *value, int16_t max_value_length, - int16_t page_index, - int16_t chunk_index, - int16_t *page_count_out, - int16_t *chunk_count_out) { - - if (page_count_out) - *page_count_out = 1; - if (chunk_count_out) - *chunk_count_out = 1; - - snprintf(title, max_title_length, "Account %d", getBip32Account()); - snprintf(key, max_key_length, "index %d", page_index); - - // get address from the current bip32_path - setBip32Index(page_index); - getBech32Addr(value); - return 0; -} - -void addr_accept() { -#if defined(TARGET_NANOS) - print_key("Returning"); - print_value("Address..."); - view_status(); - UX_WAIT(); -#endif - // Send pubkey - uint8_t *pk = G_io_apdu_buffer; - getPubKeyCompressed(pk); - int pos = PK_COMPRESSED_LEN; - - // Convert pubkey to bech32 address - char *bech32_out = (char *) (G_io_apdu_buffer + pos); - getBech32Addr(bech32_out); - pos += strlen(bech32_out); - - set_code(G_io_apdu_buffer, pos, APDU_CODE_OK); - io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, pos + 2); - view_idle(0); -} - -void addr_reject() { - set_code(G_io_apdu_buffer, 0, APDU_CODE_COMMAND_NOT_ALLOWED); - io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); - view_idle(0); -} - -//endregion - void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { uint16_t sw = 0; @@ -397,7 +337,6 @@ void app_init() { io_seproxyhal_init(); USB_power(0); USB_power(1); - crypto_init(); view_idle(0); } diff --git a/src/lib/cosmos.h b/src/lib/cosmos.h index 1094ea05..f52d9f5b 100644 --- a/src/lib/cosmos.h +++ b/src/lib/cosmos.h @@ -22,6 +22,8 @@ extern "C" { #include #include +#define BIP32_LEN_DEFAULT 5 + #define BIP32_0_DEFAULT (0x80000000 | 0x2c) #define BIP32_1_DEFAULT (0x80000000 | 0x76) #define BIP32_2_DEFAULT (0x80000000 | 0) diff --git a/src/lib/crypto.c b/src/lib/crypto.c index f727093c..e19c1ede 100644 --- a/src/lib/crypto.c +++ b/src/lib/crypto.c @@ -21,21 +21,11 @@ #include "zxmacros.h" #include "cosmos.h" -typedef struct { - unsigned int cached : 1; - unsigned int valid : 1; - uint32_t path[BIP32_LEN_DEFAULT]; -} bip32_t; +uint32_t bip32Path[BIP32_LEN_DEFAULT]; -bip32_t bip32; uint8_t bech32_hrp_len; char bech32_hrp[MAX_BECH32_HRP_LEN + 1]; -void crypto_init() { - bip32.valid = 0; - bip32.cached = 0; -} - #define SAFE_HEARTBEAT(X) io_seproxyhal_io_heartbeat(); X; io_seproxyhal_io_heartbeat(); #if defined(TARGET_NANOS) || defined(TARGET_NANOX) @@ -45,7 +35,7 @@ void crypto_extractPublicKey(uint32_t bip32Path[BIP32_LEN_DEFAULT], uint8_t *pub uint8_t privateKeyData[32]; SAFE_HEARTBEAT(os_perso_derive_node_bip32(CX_CURVE_256K1, - bip32.path, + bip32Path, BIP32_LEN_DEFAULT, privateKeyData, NULL)); @@ -78,7 +68,7 @@ uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t cx_ecfp_private_key_t cx_privateKey; uint8_t privateKeyData[32]; SAFE_HEARTBEAT(os_perso_derive_node_bip32(CX_CURVE_256K1, - bip32.path, + bip32Path, BIP32_LEN_DEFAULT, privateKeyData, NULL)); @@ -119,44 +109,6 @@ uint16_t crypto_sign(uint8_t *signature, #endif -int8_t setBip32Path(uint32_t path0, - uint32_t path1, - uint32_t path2, - uint32_t path3, - uint32_t path4) { - // Only paths in the form 44'/118'/{account}'/0/{index} are supported - bip32.valid = 0; - bip32.cached = 0; - - bip32.path[0] = path0; - bip32.path[1] = path1; - bip32.path[2] = path2; - bip32.path[3] = path3; - bip32.path[4] = path4; - - if (bip32.path[0] != BIP32_0_DEFAULT || - bip32.path[1] != BIP32_1_DEFAULT || - bip32.path[3] != BIP32_3_DEFAULT) { - return BIP32_INVALID_PATH; - } - - bip32.valid = 1; - return BIP32_NO_ERROR; -} - -int32_t getBip32Account() { - return (bip32.path[2] & 0x7FFFFFF); -} - -int32_t getBip32Index() { - return (bip32.path[4] & 0x7FFFFFF); -} - -void setBip32Index(uint32_t newIndex) { - bip32.cached = 0; - bip32.path[4] = newIndex; -} - uint8_t extractHRP(uint32_t rx, uint32_t offset) { MEMSET(bech32_hrp, 0, MAX_BECH32_HRP_LEN); @@ -205,7 +157,7 @@ uint16_t crypto_fillAddress(uint8_t *buffer, uint16_t buffer_len) { ripemd160_32(hashed2_pk, hashed1_pk); char *addr = (char *) (buffer + PUBKEY_LEN); - bech32EncodeFromBytes(bech32_addr, bech32_hrp, hashed2_pk, CX_RIPEMD160_SIZE); + bech32EncodeFromBytes(addr, bech32_hrp, hashed2_pk, CX_RIPEMD160_SIZE); return PUBKEY_LEN + strlen(addr); } diff --git a/src/lib/crypto.h b/src/lib/crypto.h index 3a0052e4..7e507176 100644 --- a/src/lib/crypto.h +++ b/src/lib/crypto.h @@ -17,6 +17,7 @@ #pragma once #include +#include "cosmos.h" #ifdef __cplusplus extern "C" { @@ -25,34 +26,12 @@ extern "C" { #define MAX_BECH32_HRP_LEN 83 #define PK_COMPRESSED_LEN 33 -#define BIP32_LEN_DEFAULT 5 -#define PUBKEY_LEN 33 - -#define BIP32_NO_ERROR 0 -#define BIP32_INVALID_LENGTH -1 -#define BIP32_INVALID_PATH -2 - -void crypto_init(); - -int32_t getBip32Account(); -int32_t getBip32Index(); -void setBip32Index(uint32_t newIndex); -int8_t setBip32Path(uint32_t path0, - uint32_t path1, - uint32_t path2, - uint32_t path3, - uint32_t path4); - -void getPubKeyCompressed(uint8_t *pkc); -void getBech32Addr(char *bech32_addr); - -////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////// - extern uint32_t bip32Path[BIP32_LEN_DEFAULT]; extern char *hrp; +#define BIP32_LEN_DEFAULT 5 +#define PUBKEY_LEN 33 + uint8_t extractHRP(uint32_t rx, uint32_t offset); void crypto_set_hrp(char *p); diff --git a/src/view.c b/src/view.c index d37c831b..ef2ead73 100644 --- a/src/view.c +++ b/src/view.c @@ -66,25 +66,6 @@ void show_idle_menu() { void view_tx_menu(unsigned int _); -void view_addr_choose_show(unsigned int _); - -void view_addr_choose_refresh(); - -void view_addr_choose_update(); - -struct { - // modes - // 0 - select account - // 1 - select index - struct { - unsigned int mode: 4; - unsigned int marker_blink: 1; - } status; - uint32_t account; - uint32_t index; -} view_addr_choose_data; - - #if defined(TARGET_NANOX) #include "ux.h" @@ -96,7 +77,6 @@ UX_FLOW_DEF_NOCB(ux_idle_flow_1_step, pbb, { &C_icon_app, "Tendermint", "Cosmos #else UX_FLOW_DEF_NOCB(ux_idle_flow_1_step, pbb, { &C_icon_app, "Tendermint", "Cosmos", }); #endif -UX_FLOW_DEF_VALID(ux_idle_flow_2_step, pb, view_addr_choose_show(0), { &C_icon_eye, "Show Address",}); UX_FLOW_DEF_NOCB(ux_idle_flow_3_step, bn, { "Version", APPVERSION, }); UX_FLOW_DEF_VALID(ux_idle_flow_4_step, pb, os_sched_exit(-1), { &C_icon_dashboard, "Quit",}); const ux_flow_step_t *const ux_idle_flow [] = { @@ -146,7 +126,6 @@ const ux_menu_entry_t menu_main[] = { #else {NULL, NULL, 0, &C_icon_app, "Tendermint", "Cosmos", 33, 12}, #endif - {NULL, view_addr_choose_show, 0, NULL, "Show Address", NULL, 0, 0}, {NULL, NULL, 0, NULL, "v"APPVERSION, NULL, 0, 0}, {NULL, os_sched_exit, 0, &C_icon_dashboard, "Quit app", NULL, 50, 29}, UX_MENU_END @@ -159,180 +138,6 @@ const ux_menu_entry_t menu_status[] = { #endif -static const bagl_element_t view_addr_choose[] = { - UI_FillRectangle(0, 0, 0, UI_SCREEN_WIDTH, UI_SCREEN_HEIGHT, 0x000000, 0xFFFFFF), - - UI_LabelLine(UIID_LABEL + 0, 0, 9 + UI_11PX * 0, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, - (const char *) viewctl.title), - UI_LabelLine(UIID_LABEL + 1, 0, 9 + UI_11PX * 1, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, - (const char *) viewctl.dataKey), - -#if defined(TARGET_NANOX) -UI_Icon(UIID_ICONLEFT1, 2, 28, 4, 7, BAGL_GLYPH_ICON_LEFT), -UI_Icon(UIID_ICONRIGHT1, 122, 28, 4, 7, BAGL_GLYPH_ICON_RIGHT), -UI_Icon(UIID_ICONLEFT2, 2, 28, 4, 7, BAGL_GLYPH_ICON_LEFT), -UI_Icon(UIID_ICONRIGHT2, 122, 28, 4, 7, BAGL_GLYPH_ICON_RIGHT), - -UI_Icon(UIID_MARKER1, 0, 0, 7, 7, ((const char*)&C_digit_dot)), -UI_Icon(UIID_MARKER2, 0, 9, 7, 7, ((const char*)&C_digit_dot)), - -UI_LabelLine(UIID_LABEL+2, 0, 9 + UI_11PX * 2, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[0]), -UI_LabelLine(UIID_LABEL+3, 0, 9 + UI_11PX * 3, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[1]), -UI_LabelLine(UIID_LABEL+4, 0, 9 + UI_11PX * 4, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[2]), -UI_LabelLine(UIID_LABEL+5, 0, 9 + UI_11PX * 5, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[3]), -#else - UI_Icon(UIID_ICONREJECT, 0, 0, 7, 7, BAGL_GLYPH_ICON_CROSS), - UI_Icon(UIID_ICONACCEPT, 128 - 7, 0, 7, 7, BAGL_GLYPH_ICON_CHECK), - UI_Icon(UIID_ICONLEFT1, 0, 0, 7, 7, BAGL_GLYPH_ICON_LEFT), - UI_Icon(UIID_ICONRIGHT1, 128 - 7, 0, 7, 7, BAGL_GLYPH_ICON_RIGHT), - UI_Icon(UIID_ICONLEFT2, 0, 9, 7, 7, BAGL_GLYPH_ICON_LEFT), - UI_Icon(UIID_ICONRIGHT2, 128 - 7, 9, 7, 7, BAGL_GLYPH_ICON_RIGHT), - - UI_LabelLineScrolling(UIID_LABELSCROLL, 16, 30, 96, 11, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValue), -#endif -}; - -const bagl_element_t *view_addr_choose_prepro(const bagl_element_t *element) { - switch (element->component.userid) { - case UIID_MARKER1: - if (view_addr_choose_data.status.mode != VIEW_ADDR_MODE_ACCOUNT) - return NULL; - break; - case UIID_MARKER2: - if (view_addr_choose_data.status.mode != VIEW_ADDR_MODE_INDEX) - return NULL; - break; - - case UIID_ICONLEFT1: - case UIID_ICONRIGHT1: - if (view_addr_choose_data.status.mode != VIEW_ADDR_MODE_ACCOUNT) - return NULL; - break; - - case UIID_ICONLEFT2: - case UIID_ICONRIGHT2: - if (view_addr_choose_data.status.mode != VIEW_ADDR_MODE_INDEX) - return NULL; - break; - - case UIID_ICONACCEPT: - if (view_addr_choose_data.status.mode != VIEW_ADDR_MODE_CONFIRM && - view_addr_choose_data.status.mode != VIEW_ADDR_MODE_SHOW) - return NULL; - break; - - case UIID_ICONREJECT: - if (view_addr_choose_data.status.mode != VIEW_ADDR_MODE_CONFIRM) - return NULL; - break; - - case UIID_LABELSCROLL: - UX_CALLBACK_SET_INTERVAL(MAX(3000, 1000 + bagl_label_roundtrip_duration_ms(element, 7))); - break; - } - return element; -} - -uint32_t bip32_field_add(uint32_t field, int16_t value) { - return (field + value) & 0x7FFFFFFF; -} - -static unsigned int view_addr_choose_button(unsigned int button_mask, unsigned int button_mask_counter) { - switch (button_mask) { - case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: - // Press both to accept / switch mode - switch (view_addr_choose_data.status.mode) { - case VIEW_ADDR_MODE_ACCOUNT: - view_addr_choose_data.status.mode = VIEW_ADDR_MODE_INDEX; - break; - case VIEW_ADDR_MODE_INDEX: - view_addr_choose_data.status.mode = VIEW_ADDR_MODE_SHOW; - break; - default: - return 0; - } - break; - - case BUTTON_EVT_RELEASED | BUTTON_LEFT: - // Press left -> previous element - switch (view_addr_choose_data.status.mode) { - case VIEW_ADDR_MODE_ACCOUNT: - view_addr_choose_data.account = bip32_field_add(view_addr_choose_data.account, -1); - break; - case VIEW_ADDR_MODE_INDEX: - view_addr_choose_data.index = bip32_field_add(view_addr_choose_data.index, -1); - break; - case VIEW_ADDR_MODE_SHOW: - // DO NOTHING -#if defined(TARGET_NANOX) - show_idle_menu(); -#endif - return 0; - case VIEW_ADDR_MODE_CONFIRM: - reject(0); - return 0; - default: - return 0; - } - break; - - case BUTTON_EVT_RELEASED | BUTTON_RIGHT: - // Press right -> next element - switch (view_addr_choose_data.status.mode) { - case VIEW_ADDR_MODE_ACCOUNT: - view_addr_choose_data.account = bip32_field_add(view_addr_choose_data.account, +1); - break; - case VIEW_ADDR_MODE_INDEX: - view_addr_choose_data.index = bip32_field_add(view_addr_choose_data.index, +1); - break; - case VIEW_ADDR_MODE_SHOW: - show_idle_menu(); - return 0; - case VIEW_ADDR_MODE_CONFIRM: - accept(0); - return 0; - default: - return 0; - } - break; - - case BUTTON_EVT_FAST | BUTTON_LEFT: - // Hold left -> previous element (fast) - switch (view_addr_choose_data.status.mode) { - case VIEW_ADDR_MODE_ACCOUNT: - view_addr_choose_data.account = bip32_field_add(view_addr_choose_data.account, -10); - break; - case VIEW_ADDR_MODE_INDEX: - view_addr_choose_data.index = bip32_field_add(view_addr_choose_data.index, -10); - break; - default: - return 0; - } - break; - - case BUTTON_EVT_FAST | BUTTON_RIGHT: - // Press right -> next element (fast) - switch (view_addr_choose_data.status.mode) { - case VIEW_ADDR_MODE_ACCOUNT: - view_addr_choose_data.account = bip32_field_add(view_addr_choose_data.account, +10); - break; - case VIEW_ADDR_MODE_INDEX: - view_addr_choose_data.index = bip32_field_add(view_addr_choose_data.index, +10); - break; - default: - return 0; - } - break; - - default: - return 0; - } - - view_addr_choose_update(); - view_addr_choose_refresh(); - return 0; -} - //////////////////////////////// //////////////////////////////// //////////////////////////////// @@ -371,68 +176,6 @@ void view_tx_show(unsigned int start_page) { } -void view_addr_choose_update() { - print_title("Account %u", view_addr_choose_data.account); - print_key("Index %u", view_addr_choose_data.index); - - print_value("..."); - if (view_addr_choose_data.status.mode == VIEW_ADDR_MODE_SHOW) { - print_value("....?...."); - UX_DISPLAY(view_addr_choose, view_addr_choose_prepro); - UX_WAIT(); - } - - if (view_addr_choose_data.status.mode == VIEW_ADDR_MODE_SHOW || - view_addr_choose_data.status.mode == VIEW_ADDR_MODE_CONFIRM) { - print_value("This is a very long string that needs to be scrolled otherwise it does not fit"); - - setBip32Path(BIP32_0_DEFAULT, - BIP32_1_DEFAULT, - 0x80000000 | view_addr_choose_data.account, - BIP32_3_DEFAULT, - view_addr_choose_data.index); - getBech32Addr(viewctl.dataValue); - } - - viewctl_dataValue_split(); -} - -void view_addr_choose_refresh() { - UX_DISPLAY(view_addr_choose, view_addr_choose_prepro); -} - -void view_addr_choose_show(unsigned int _) { - // Initialize show view - view_addr_choose_data.status.mode = VIEW_ADDR_MODE_ACCOUNT; - view_addr_choose_data.account = 0; - view_addr_choose_data.index = 0; - - crypto_set_hrp("cosmos"); - - ehAccept = show_idle_menu; - ehReject = NULL; - - // Now show view - view_addr_choose_update(); - view_addr_choose_refresh(); -} - -void view_addr_confirm(unsigned int _) { - view_addr_choose_data.status.mode = VIEW_ADDR_MODE_CONFIRM; - view_addr_choose_data.account = getBip32Account(); - view_addr_choose_data.index = getBip32Index(); - view_addr_choose_update(); - -#if defined(TARGET_NANOS) - view_addr_choose_refresh(); -#elif defined(TARGET_NANOX) - if(G_ux.stack_count == 0) { - ux_stack_push(); - } - ux_flow_init(0, ux_addr_flow, NULL); -#endif -} - void view_tx_menu(unsigned int unused) { UNUSED(unused); From 790e93ee16022569a32941acf7ae05847c589d7a Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 3 Sep 2019 16:34:54 +0200 Subject: [PATCH 08/78] refactor view_{sign_idle} --- src/app_main.c | 45 ++++++++++++++++----------------------------- src/main.c | 2 +- src/view.c | 31 ++++++++++++++++--------------- src/view.h | 6 +++--- 4 files changed, 36 insertions(+), 48 deletions(-) diff --git a/src/app_main.c b/src/app_main.c index 6db3f392..917f1383 100644 --- a/src/app_main.c +++ b/src/app_main.c @@ -1,6 +1,6 @@ /******************************************************************************* +* (c) 2018, 2019 ZondaX GmbH * (c) 2016 Ledger -* (c) 2018 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,26 +21,13 @@ #include #include -#include - +#include "view.h" #include "actions.h" -#include "cosmos.h" - #include "lib/transaction.h" -#include "lib/tx_display.h" -#include "view.h" #include "lib/crypto.h" - -#ifdef TESTING_ENABLED -// Generate using always the same private data -// to allow for reproducible results -const uint8_t privateKeyDataTest[] = { - 0x75, 0x56, 0x0e, 0x4d, 0xde, 0xa0, 0x63, 0x05, - 0xc3, 0x6e, 0x2e, 0xb5, 0xf7, 0x2a, 0xca, 0x71, - 0x2d, 0x13, 0x4c, 0xc2, 0xa0, 0x59, 0xbf, 0xe8, - 0x7e, 0x9b, 0x5d, 0x55, 0xbf, 0x81, 0x3b, 0xd4 -}; -#endif +#include "cosmos.h" +#include +#include "lib/tx_display.h" unsigned char G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B]; @@ -105,7 +92,6 @@ unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) { } void extractBip32(uint32_t rx, uint32_t offset) { -#define UINT32_FROM_LE(p) ( (*(p+3) << 24) + (*(p+2) << 16) + (*(p+1) << 8) + (*(p+0))) if ((rx - offset) < 1 + 4 * BIP32_LEN_DEFAULT) { THROW(APDU_CODE_DATA_INVALID); } @@ -137,6 +123,7 @@ bool process_chunk(volatile uint32_t *tx, uint32_t rx, bool getBip32) { if (packageIndex == 1) { transaction_initialize(); transaction_reset(); + if (getBip32) { extractBip32(rx, OFFSET_DATA); return packageIndex == packageCount; @@ -186,18 +173,18 @@ void tx_accept_sign() { if (length == 0) { set_code(G_io_apdu_buffer, length, APDU_CODE_SIGN_VERIFY_ERROR); io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, length + 2); - view_idle(0); + view_idle_show(0); } set_code(G_io_apdu_buffer, length, APDU_CODE_OK); io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, length + 2); - view_idle(0); + view_idle_show(0); } void tx_reject() { set_code(G_io_apdu_buffer, 0, APDU_CODE_COMMAND_NOT_ALLOWED); io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); - view_idle(0); + view_idle_show(0); } //endregion @@ -213,7 +200,7 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { THROW(APDU_CODE_CLA_NOT_SUPPORTED); } - if (rx < 5) { + if (rx < APDU_MIN_LENGTH) { THROW(APDU_CODE_WRONG_LENGTH); } @@ -240,11 +227,11 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { } case INS_GET_ADDR_SECP256K1: { - // Parse arguments - uint8_t requireConfirmation = G_io_apdu_buffer[OFFSET_P1]; uint8_t len = extractHRP(rx, OFFSET_DATA); extractBip32(rx, OFFSET_DATA + 1 + len); + uint8_t requireConfirmation = G_io_apdu_buffer[OFFSET_P1]; + if (requireConfirmation) { app_fill_address(); view_address_show(); @@ -266,14 +253,14 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { int error_msg_length = strlen(error_msg); os_memmove(G_io_apdu_buffer, error_msg, error_msg_length); *tx += (error_msg_length); - THROW(APDU_CODE_BAD_KEY_HANDLE); + THROW(APDU_CODE_DATA_INVALID); } +// FIXME: tx_display_index_root(); - view_set_handlers(tx_getData, tx_accept_sign, tx_reject); - view_tx_show(0); + view_sign_show(); *flags |= IO_ASYNCH_REPLY; break; } @@ -337,7 +324,7 @@ void app_init() { io_seproxyhal_init(); USB_power(0); USB_power(1); - view_idle(0); + view_idle_show(0); } #pragma clang diagnostic push diff --git a/src/main.c b/src/main.c index 541f648a..834bfc7a 100644 --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,6 @@ /******************************************************************************* * (c) 2016 Ledger -* (c) 2018 ZondaX GmbH +* (c) 2018, 2019 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/view.c b/src/view.c index ef2ead73..2d07815a 100644 --- a/src/view.c +++ b/src/view.c @@ -61,7 +61,7 @@ void reject(unsigned int _) { } void show_idle_menu() { - view_idle(0); + view_idle_show(0); } void view_tx_menu(unsigned int _); @@ -114,26 +114,26 @@ const ux_flow_step_t *const ux_addr_flow [] = { ux_state_t ux; const ux_menu_entry_t menu_transaction_info[] = { - {NULL, view_tx_show, 0, NULL, "View transaction", NULL, 0, 0}, - {NULL, accept, 0, NULL, "Sign transaction", NULL, 0, 0}, - {NULL, reject, 0, &C_icon_crossmark, "Reject", NULL, 60, 40}, - UX_MENU_END + {NULL, view_sign_show, 0, NULL, "View transaction", NULL, 0, 0}, + {NULL, accept, 0, NULL, "Sign transaction", NULL, 0, 0}, + {NULL, reject, 0, &C_icon_crossmark, "Reject", NULL, 60, 40}, + UX_MENU_END }; const ux_menu_entry_t menu_main[] = { #ifdef TESTING_ENABLED - {NULL, NULL, 0, &C_icon_app, "Tendermint", "Cosmos TEST!", 33, 12}, + {NULL, NULL, 0, &C_icon_app, "Tendermint", "Cosmos TEST!", 33, 12}, #else - {NULL, NULL, 0, &C_icon_app, "Tendermint", "Cosmos", 33, 12}, + {NULL, NULL, 0, &C_icon_app, "Tendermint", "Cosmos", 33, 12}, #endif - {NULL, NULL, 0, NULL, "v"APPVERSION, NULL, 0, 0}, - {NULL, os_sched_exit, 0, &C_icon_dashboard, "Quit app", NULL, 50, 29}, - UX_MENU_END + {NULL, NULL, 0, NULL, "v"APPVERSION, NULL, 0, 0}, + {NULL, os_sched_exit, 0, &C_icon_dashboard, "Quit app", NULL, 50, 29}, + UX_MENU_END }; const ux_menu_entry_t menu_status[] = { - {NULL, NULL, 0, &C_icon_app, viewctl.dataKey, viewctl.dataValue, 33, 12}, - UX_MENU_END + {NULL, NULL, 0, &C_icon_app, viewctl.dataKey, viewctl.dataValue, 33, 12}, + UX_MENU_END }; #endif @@ -150,7 +150,7 @@ void view_init(void) { UX_INIT(); } -void view_idle(unsigned int ignored) { +void view_idle_show(unsigned int ignored) { #if defined(TARGET_NANOS) UX_MENU_DISPLAY(0, menu_main, NULL); #elif defined(TARGET_NANOX) @@ -167,9 +167,9 @@ void view_status(unsigned int ignored) { #endif } -void view_tx_show(unsigned int start_page) { +void view_sign_show(unsigned int ignored) { if (ehGetData == NULL) { return; } - viewexpl_start(start_page, + viewexpl_start(0, ehGetData, NULL, view_tx_menu); @@ -198,6 +198,7 @@ void view_set_handlers(viewctl_delegate_getData func_getData, } const char *address; + void view_address_show() { // Address has been placed in the output buffer address = (char *) (G_io_apdu_buffer + PUBKEY_LEN); diff --git a/src/view.h b/src/view.h index b5674300..874547f1 100644 --- a/src/view.h +++ b/src/view.h @@ -31,14 +31,14 @@ void view_set_handlers(viewctl_delegate_getData func_getData, /// view_init (initializes UI) void view_init(void); -/// view_idle (idle view - main menu + status) -void view_idle(unsigned int ignored); +/// view_idle_show (idle view - main menu + status) +void view_idle_show(unsigned int ignored); /// view_status void view_status(); /// view_tx_show (show/review transaction view) -void view_tx_show(unsigned int start_page); +void view_sign_show(); /// view_addr_confirm (show/accept public key + address request) void view_addr_confirm(unsigned int _); From 493380e72b50ab6546f9c0d5d402ceaa42ffbacf Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 3 Sep 2019 16:39:55 +0200 Subject: [PATCH 09/78] reorganizing txs --- src/actions.c | 6 +++--- src/app_main.c | 10 +++++----- src/lib/crypto.h | 8 ++++---- src/{lib/transaction.c => tx.c} | 28 ++++++++++++++-------------- src/{lib/transaction.h => tx.h} | 31 +++++++++++++++++++++++-------- src/view.h | 10 +++++++++- 6 files changed, 58 insertions(+), 35 deletions(-) rename src/{lib/transaction.c => tx.c} (81%) rename src/{lib/transaction.h => tx.h} (64%) diff --git a/src/actions.c b/src/actions.c index e61681f0..e6bf493f 100644 --- a/src/actions.c +++ b/src/actions.c @@ -17,14 +17,14 @@ #include "actions.h" #include "lib/crypto.h" -#include "transaction.h" +#include "tx.h" #include "apdu_codes.h" #include uint8_t app_sign() { uint8_t *signature = G_io_apdu_buffer; - const uint8_t *message = transaction_get_buffer(); - const uint16_t messageLength = transaction_get_buffer_length(); + const uint8_t *message = tx_get_buffer(); + const uint16_t messageLength = tx_get_buffer_length(); return crypto_sign(signature, IO_APDU_BUFFER_SIZE - 2, message, messageLength); } diff --git a/src/app_main.c b/src/app_main.c index 917f1383..2664144a 100644 --- a/src/app_main.c +++ b/src/app_main.c @@ -23,7 +23,7 @@ #include "view.h" #include "actions.h" -#include "lib/transaction.h" +#include "tx.h" #include "lib/crypto.h" #include "cosmos.h" #include @@ -121,8 +121,8 @@ bool process_chunk(volatile uint32_t *tx, uint32_t rx, bool getBip32) { } if (packageIndex == 1) { - transaction_initialize(); - transaction_reset(); + tx_initialize(); + tx_reset(); if (getBip32) { extractBip32(rx, OFFSET_DATA); @@ -130,7 +130,7 @@ bool process_chunk(volatile uint32_t *tx, uint32_t rx, bool getBip32) { } } - if (transaction_append(&(G_io_apdu_buffer[offset]), rx - offset) != rx - offset) { + if (tx_append(&(G_io_apdu_buffer[offset]), rx - offset) != rx - offset) { THROW(APDU_CODE_OUTPUT_BUFFER_TOO_SMALL); } @@ -248,7 +248,7 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { if (!process_chunk(tx, rx, true)) THROW(APDU_CODE_OK); - const char *error_msg = transaction_parse(); + const char *error_msg = tx_parse(); if (error_msg != NULL) { int error_msg_length = strlen(error_msg); os_memmove(G_io_apdu_buffer, error_msg, error_msg_length); diff --git a/src/lib/crypto.h b/src/lib/crypto.h index 7e507176..3eb70d36 100644 --- a/src/lib/crypto.h +++ b/src/lib/crypto.h @@ -1,6 +1,5 @@ /******************************************************************************* -* (c) 2016 Ledger -* (c) 2018 ZondaX GmbH +* (c) 2019 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. ********************************************************************************/ + #pragma once #include @@ -29,7 +29,6 @@ extern "C" { extern uint32_t bip32Path[BIP32_LEN_DEFAULT]; extern char *hrp; -#define BIP32_LEN_DEFAULT 5 #define PUBKEY_LEN 33 uint8_t extractHRP(uint32_t rx, uint32_t offset); @@ -38,7 +37,8 @@ void crypto_set_hrp(char *p); uint16_t crypto_fillAddress(uint8_t *buffer, uint16_t buffer_len); -uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t *message, uint16_t messageLen); +uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, + const uint8_t *message, uint16_t messageLen); #ifdef __cplusplus } diff --git a/src/lib/transaction.c b/src/tx.c similarity index 81% rename from src/lib/transaction.c rename to src/tx.c index 0f3b3c47..7f31ac47 100644 --- a/src/lib/transaction.c +++ b/src/tx.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) ZondaX GmbH +* (c) 2019 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,15 @@ * limitations under the License. ********************************************************************************/ -#include "transaction.h" +#include "tx.h" #include "apdu_codes.h" #include "buffering.h" -#include "json_parser.h" -#include "tx_validate.h" -#include "tx_parser.h" +#include "lib/json_parser.h" +#include "lib/tx_validate.h" +#include "lib/tx_parser.h" // TODO: Remove this dependency -#include "../view.h" +#include "view.h" #if defined(TARGET_NANOX) #define RAM_BUFFER_SIZE 8192 @@ -51,7 +51,7 @@ storage_t const N_appdata_impl __attribute__ ((aligned(64))); parsed_json_t parsed_transaction; -void transaction_initialize() { +void tx_initialize() { buffering_init( ram_buffer, sizeof(ram_buffer), @@ -60,25 +60,25 @@ void transaction_initialize() { ); } -void transaction_reset() { +void tx_reset() { buffering_reset(); } -uint32_t transaction_append(unsigned char *buffer, uint32_t length) { +uint32_t tx_append(unsigned char *buffer, uint32_t length) { return buffering_append(buffer, length); } -uint32_t transaction_get_buffer_length() { +uint32_t tx_get_buffer_length() { return buffering_get_buffer()->pos; } -uint8_t *transaction_get_buffer() { +uint8_t *tx_get_buffer() { return buffering_get_buffer()->data; } -const char* transaction_parse() { - const char *transaction_buffer = (const char *) transaction_get_buffer(); - const char* error_msg = json_parse_s(&parsed_transaction, transaction_buffer, transaction_get_buffer_length()); +const char* tx_parse() { + const char *transaction_buffer = (const char *) tx_get_buffer(); + const char* error_msg = json_parse_s(&parsed_transaction, transaction_buffer, tx_get_buffer_length()); if (error_msg != NULL) { return error_msg; } diff --git a/src/lib/transaction.h b/src/tx.h similarity index 64% rename from src/lib/transaction.h rename to src/tx.h index 0ccf023b..bb96dec1 100644 --- a/src/lib/transaction.h +++ b/src/tx.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) ZondaX GmbH +* (c) 2019 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,28 +16,43 @@ #pragma once #include "os.h" +#include "cosmos.h" -void transaction_initialize(); +typedef enum { + tx_no_error = 0, + tx_no_data = 1, +} tx_error_t; + +void tx_initialize(); /// Clears the transaction buffer -void transaction_reset(); +void tx_reset(); /// Appends buffer to the end of the current transaction buffer /// Transaction buffer will grow until it reaches the maximum allowed size /// \param buffer /// \param length /// \return It returns an error message if the buffer is too small. -uint32_t transaction_append(unsigned char *buffer, uint32_t length); +uint32_t tx_append(unsigned char *buffer, uint32_t length); /// Returns size of the raw json transaction buffer /// \return -uint32_t transaction_get_buffer_length(); +uint32_t tx_get_buffer_length(); /// Returns the raw json transaction buffer /// \return -uint8_t *transaction_get_buffer(); +uint8_t *tx_get_buffer(); -/// Parse json message stored in transaction buffer +/// Parse message stored in transaction buffer /// This function should be called as soon as full buffer data is loaded. /// \return It returns NULL if json is valid or error message otherwise. -const char *transaction_parse(); +const char *tx_parse(); + +/// Return the number of items in the transaction +uint8_t tx_getNumItems(); + +/// Gets an specific item from the transaction (including paging) +tx_error_t tx_getItem(int8_t displayIdx, + char *outKey, uint16_t outKeyLen, + char *outValue, uint16_t outValueLen, + uint8_t pageIdx, uint8_t *pageCount); diff --git a/src/view.h b/src/view.h index 874547f1..0f75728b 100644 --- a/src/view.h +++ b/src/view.h @@ -1,6 +1,6 @@ /******************************************************************************* +* (c) 2018,2019 ZondaX GmbH * (c) 2016 Ledger -* (c) 2018 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,16 @@ ********************************************************************************/ #pragma once +#include + +#if defined(LEDGER_SPECIFIC) +#include "bolos_target.h" +#if defined(BOLOS_SDK) #include "os.h" #include "cx.h" +#endif +#endif + #include "view_common.h" #include "view_expl.h" From 4773cb6f38be5095b53c55484e273734b5fc882e Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 3 Sep 2019 16:45:09 +0200 Subject: [PATCH 10/78] splitting S and X --- src/view_internal.h | 104 +++++++++++++++++++ src/view_s.c | 243 ++++++++++++++++++++++++++++++++++++++++++++ src/view_x.c | 219 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 566 insertions(+) create mode 100644 src/view_internal.h create mode 100644 src/view_s.c create mode 100644 src/view_x.c diff --git a/src/view_internal.h b/src/view_internal.h new file mode 100644 index 00000000..e53cfb0b --- /dev/null +++ b/src/view_internal.h @@ -0,0 +1,104 @@ +/******************************************************************************* +* (c) 2019 ZondaX GmbH +* (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +#pragma once + +#include + +#define MENU_MAIN_APP_LINE1 "Tendermint" + +#ifdef TESTING_ENABLED +#define MENU_MAIN_APP_LINE2 "Cosmos TEST!" +#else +#define MENU_MAIN_APP_LINE2 "Cosmos" +#endif + +#if defined(TARGET_NANOX) +#define MAX_CHARS_PER_KEY_LINE 64 +#define MAX_CHARS_PER_VALUE1_LINE 4096 +#define MAX_CHARS_HEXMESSAGE 160 +#define CUR_FLOW G_ux.flow_stack[G_ux.stack_count-1] +#else +#define MAX_CHARS_PER_KEY_LINE (32+1) +#define MAX_CHARS_PER_VALUE_LINE (18) +#define MAX_CHARS_PER_VALUE1_LINE (2*MAX_CHARS_PER_VALUE_LINE+1) +#define MAX_CHARS_PER_VALUE2_LINE (MAX_CHARS_PER_VALUE_LINE+1) +#define MAX_CHARS_HEXMESSAGE 40 +#endif + +extern const char *address; + +typedef struct { + char key[MAX_CHARS_PER_KEY_LINE]; + char value[MAX_CHARS_PER_VALUE1_LINE]; +#if defined(TARGET_NANOS) + char value2[MAX_CHARS_PER_VALUE2_LINE]; +#endif + int8_t idx; + int8_t pageIdx; + uint8_t pageCount; +} view_t; + +extern view_t viewdata; + +typedef enum { + view_no_error = 0, + view_no_data = 1, + view_error_detected = 2 +} view_error_t; + +#define print_title(...) snprintf(viewdata.title, sizeof(viewdata.title), __VA_ARGS__) +#define print_key(...) snprintf(viewdata.key, sizeof(viewdata.key), __VA_ARGS__); +#define print_value(...) snprintf(viewdata.value, sizeof(viewdata.value), __VA_ARGS__); + +#if defined(TARGET_NANOS) +#define print_value2(...) snprintf(viewdata.value2, sizeof(viewdata.value2), __VA_ARGS__); +#endif + +void splitValueField(); + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// + +void view_idle_show_impl(); + +void view_address_show_impl(); + +void view_error_show_impl(); + +void view_sign_show_impl(); + +void h_address_accept(unsigned int _); + +void h_error_accept(unsigned int _); + +void h_sign_accept(unsigned int _); + +void h_sign_reject(unsigned int _); + +void h_review_init(); + +void h_review_increase(); + +void h_review_decrease(); + +view_error_t h_review_update_data(); diff --git a/src/view_s.c b/src/view_s.c new file mode 100644 index 00000000..f87fafd7 --- /dev/null +++ b/src/view_s.c @@ -0,0 +1,243 @@ +/******************************************************************************* +* (c) 2018, 2019 ZondaX GmbH +* (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#include "view.h" +#include "view_internal.h" +#include "actions.h" +#include "apdu_codes.h" +#include "glyphs.h" +#include "bagl.h" +#include "zxmacros.h" +#include "view_templates.h" +#include "tx.h" + +#include +#include + +#if defined(TARGET_NANOS) + +void h_review_button_left(); +void h_review_button_right(); +void view_review_show(); +void view_sign_show_s(); + +ux_state_t ux; + +const ux_menu_entry_t menu_main[] = { + {NULL, NULL, 0, &C_icon_app, MENU_MAIN_APP_LINE1, MENU_MAIN_APP_LINE2, 33, 12}, + {NULL, NULL, 0, NULL, "v"APPVERSION, NULL, 0, 0}, + {NULL, os_sched_exit, 0, &C_icon_dashboard, "Quit", NULL, 50, 29}, + UX_MENU_END +}; + +static const bagl_element_t view_address[] = { + UI_FillRectangle(0, 0, 0, UI_SCREEN_WIDTH, UI_SCREEN_HEIGHT, 0x000000, 0xFFFFFF), + UI_Icon(0, 128 - 7, 0, 7, 7, BAGL_GLYPH_ICON_CHECK), + UI_LabelLine(UIID_LABEL + 0, 0, 8, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, viewdata.key), + UI_LabelLine(UIID_LABEL + 0, 0, 19, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, viewdata.value), + UI_LabelLineScrolling(UIID_LABELSCROLL, 0, 30, 128, UI_11PX, UI_WHITE, UI_BLACK, viewdata.value2), +}; + +void h_review(unsigned int _) { UNUSED(_); view_sign_show_impl(); } + +const ux_menu_entry_t menu_sign[] = { + {NULL, h_review, 0, NULL, "View transaction", NULL, 0, 0}, + {NULL, h_sign_accept, 0, NULL, "Sign transaction", NULL, 0, 0}, + {NULL, h_sign_reject, 0, &C_icon_back, "Reject", NULL, 60, 40}, + UX_MENU_END +}; + +static const bagl_element_t view_review[] = { + UI_BACKGROUND_LEFT_RIGHT_ICONS, + UI_LabelLine(UIID_LABEL + 0, 0, 8, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, viewdata.key), + UI_LabelLine(UIID_LABEL + 1, 0, 19, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, viewdata.value), + UI_LabelLine(UIID_LABEL + 2, 0, 30, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, viewdata.value2), +}; + +static const bagl_element_t view_error[] = { + UI_FillRectangle(0, 0, 0, UI_SCREEN_WIDTH, UI_SCREEN_HEIGHT, 0x000000, 0xFFFFFF), + UI_Icon(0, 128 - 7, 0, 7, 7, BAGL_GLYPH_ICON_CHECK), + UI_LabelLine(UIID_LABEL + 0, 0, 8, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, viewdata.key), + UI_LabelLine(UIID_LABEL + 0, 0, 19, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, viewdata.value), + UI_LabelLineScrolling(UIID_LABELSCROLL, 0, 30, 128, UI_11PX, UI_WHITE, UI_BLACK, viewdata.value2), +}; + +static unsigned int view_address_button(unsigned int button_mask, unsigned int button_mask_counter) { + switch (button_mask) { + case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: + case BUTTON_EVT_RELEASED | BUTTON_LEFT: + break; + case BUTTON_EVT_RELEASED | BUTTON_RIGHT: + h_address_accept(0); + break; + } + return 0; +} + +static unsigned int view_error_button(unsigned int button_mask, unsigned int button_mask_counter) { + switch (button_mask) { + case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: + case BUTTON_EVT_RELEASED | BUTTON_LEFT: + break; + case BUTTON_EVT_RELEASED | BUTTON_RIGHT: + h_error_accept(0); + break; + } + return 0; +} + +static unsigned int view_review_button(unsigned int button_mask, unsigned int button_mask_counter) { + switch (button_mask) { + case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: + // Press both left and right buttons to quit + view_sign_show_s(); + break; + case BUTTON_EVT_RELEASED | BUTTON_LEFT: + // Press left to progress to the previous element + h_review_button_left(); + break; + + case BUTTON_EVT_RELEASED | BUTTON_RIGHT: + // Press right to progress to the next element + h_review_button_right(); + break; + } + return 0; +} + +const bagl_element_t *view_prepro(const bagl_element_t *element) { + switch (element->component.userid) { + case UIID_ICONLEFT: + case UIID_ICONRIGHT: + UX_CALLBACK_SET_INTERVAL(2000); + break; + case UIID_LABELSCROLL: + UX_CALLBACK_SET_INTERVAL( + MAX(3000, 1000 + bagl_label_roundtrip_duration_ms(element, 7)) + ); + break; + } + return element; +} + +void h_review_button_left() { + h_review_decrease(); + + view_error_t err = h_review_update_data(); + switch(err) { + case view_no_error: + view_review_show(); + break; + case view_no_data: + view_sign_show_s(); + break; + case view_error_detected: + default: + view_error_show(); + break; + } + + UX_WAIT(); +} + +void h_review_button_right() { + h_review_increase(); + + view_error_t err = h_review_update_data(); + + switch(err) { + case view_no_error: + view_review_show(); + break; + case view_no_data: + view_sign_show_s(); + break; + case view_error_detected: + default: + view_error_show(); + break; + } + + UX_WAIT(); +} + +void splitValueField() { + print_value2(""); + uint16_t vlen = strlen(viewdata.value); + if (vlen > MAX_CHARS_PER_VALUE2_LINE - 1) { + strcpy(viewdata.value2, viewdata.value + MAX_CHARS_PER_VALUE_LINE); + viewdata.value[MAX_CHARS_PER_VALUE_LINE] = 0; + } +} + +////////////////////////// +////////////////////////// +////////////////////////// +////////////////////////// +////////////////////////// + +void view_idle_show_impl() { + UX_MENU_DISPLAY(0, menu_main, NULL); +} + +void view_address_show_impl() { +#define KEYCHUNKSIZE 12 + // move first part to key + MEMSET(viewdata.key, 0, MAX_CHARS_PER_KEY_LINE); + MEMCPY(viewdata.key, address, KEYCHUNKSIZE); + + // move the remainder to + snprintf(viewdata.value, + MAX_CHARS_PER_VALUE1_LINE, "%s", + address + KEYCHUNKSIZE); + + splitValueField(); + + UX_DISPLAY(view_address, view_prepro); +} + +void view_error_show_impl() { + UX_DISPLAY(view_error, view_prepro); +} + +void view_sign_show_impl() { + h_review_init(); + + view_error_t err = h_review_update_data(); + switch(err) { + case view_no_error: + view_review_show(); + break; + case view_no_data: + view_sign_show_s(); + break; + case view_error_detected: + default: + view_error_show(); + break; + } +} + +void view_sign_show_s(void){ + UX_MENU_DISPLAY(0, menu_sign, NULL); +} + +void view_review_show() { + UX_DISPLAY(view_review, view_prepro); +} + +#endif diff --git a/src/view_x.c b/src/view_x.c new file mode 100644 index 00000000..0279ecac --- /dev/null +++ b/src/view_x.c @@ -0,0 +1,219 @@ +/******************************************************************************* +* (c) 2018, 2019 ZondaX GmbH +* (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#include "view.h" +#include "view_internal.h" +#include "actions.h" +#include "apdu_codes.h" +#include "glyphs.h" +#include "bagl.h" +#include "zxmacros.h" +#include "view_templates.h" +#include "tx.h" + +#include +#include + +#if defined(TARGET_NANOX) + +void h_review_loop_start(); +void h_review_loop_inside(); +void h_review_loop_end(); + +#include "ux.h" +ux_state_t G_ux; +bolos_ux_params_t G_ux_params; +uint8_t flow_inside_loop; + +UX_FLOW_DEF_NOCB(ux_idle_flow_1_step, pbb, { &C_icon_app, MENU_MAIN_APP_LINE1, MENU_MAIN_APP_LINE2,}); +UX_FLOW_DEF_NOCB(ux_idle_flow_3_step, bn, { "Version", APPVERSION, }); +UX_FLOW_DEF_VALID(ux_idle_flow_4_step, pb, os_sched_exit(-1), { &C_icon_dashboard, "Quit",}); +const ux_flow_step_t *const ux_idle_flow [] = { + &ux_idle_flow_1_step, + &ux_idle_flow_3_step, + &ux_idle_flow_4_step, + FLOW_END_STEP, +}; + +/////////// + +UX_STEP_NOCB(ux_addr_flow_1_step, bnnn_paging, { .title = viewdata.key, .text = viewdata.value, }); +UX_STEP_VALID(ux_addr_flow_2_step, pb, h_address_accept(0), { &C_icon_validate_14, "Ok"}); + +UX_FLOW( + ux_addr_flow, + &ux_addr_flow_1_step, + &ux_addr_flow_2_step +); + +/////////// + +UX_STEP_NOCB(ux_error_flow_1_step, bnnn_paging, { .title = viewdata.key, .text = viewdata.value, }); +UX_STEP_VALID(ux_error_flow_2_step, pb, h_error_accept(0), { &C_icon_validate_14, "Ok"}); + +UX_FLOW( + ux_error_flow, + &ux_error_flow_1_step, + &ux_error_flow_2_step +); + +/////////// +UX_STEP_NOCB(ux_sign_flow_1_step, pbb, { &C_icon_eye, "View", "Transaction" }); + +UX_STEP_INIT(ux_sign_flow_2_start_step, NULL, NULL, { h_review_loop_start(); }); +UX_STEP_NOCB_INIT(ux_sign_flow_2_step, bnnn_paging, { h_review_loop_inside(); }, { .title = viewdata.key, .text = viewdata.value, }); +UX_STEP_INIT(ux_sign_flow_2_end_step, NULL, NULL, { h_review_loop_end(); }); + +UX_STEP_VALID(ux_sign_flow_3_step, pbb, h_sign_accept(0), { &C_icon_validate_14, "Sign", "Transaction" }); +UX_STEP_VALID(ux_sign_flow_4_step, pbb, h_sign_reject(0), { &C_icon_crossmark, "Reject", "Transaction" }); +const ux_flow_step_t *const ux_sign_flow[] = { + &ux_sign_flow_1_step, + &ux_sign_flow_2_start_step, + &ux_sign_flow_2_step, + &ux_sign_flow_2_end_step, + &ux_sign_flow_3_step, + &ux_sign_flow_4_step, + FLOW_END_STEP, +}; + +////////////////////////// +////////////////////////// +////////////////////////// +////////////////////////// +////////////////////////// + +void h_review_loop_start() { + if (flow_inside_loop) { + // coming from right + h_review_decrease(); + if (viewdata.idx<0) { + // exit to the left + flow_inside_loop = 0; + ux_flow_prev(); + return; + } + } else { + // coming from left + h_review_init(); + } + + view_error_t err = h_review_update_data(); + switch(err) { + case view_no_error: + case view_no_data: + break; + case view_error_detected: + default: + view_error_show(); + break; + } + + ux_flow_next(); +} + +void h_review_loop_inside() { + flow_inside_loop = 1; +} + +void h_review_loop_end() { + if (flow_inside_loop) { + // coming from left + h_review_increase(); + view_error_t err = h_review_update_data(); + + switch(err) { + case view_no_error: + ux_layout_bnnn_paging_reset(); + break; + case view_no_data: { + flow_inside_loop = 0; + ux_flow_next(); + return; + } + case view_error_detected: + default: + view_error_show(); + break; + } + } else { + // coming from right + h_review_decrease(); + view_error_t err = h_review_update_data(); + + switch(err) { + case view_no_error: + case view_no_data: + break; + case view_error_detected: + default: + view_error_show(); + break; + } + } + + // move to prev flow but trick paging to show first page + CUR_FLOW.prev_index = CUR_FLOW.index-2; + CUR_FLOW.index--; + ux_flow_relayout(); +} + +void splitValueField() {} + +////////////////////////// +////////////////////////// +////////////////////////// +////////////////////////// +////////////////////////// + +void view_idle_show_impl() { + if(G_ux.stack_count == 0) { + ux_stack_push(); + } + ux_flow_init(0, ux_idle_flow, NULL); +} + +void view_address_show_impl() { + snprintf(viewdata.key, MAX_CHARS_PER_KEY_LINE, "Confirm address"); + snprintf(viewdata.value, MAX_CHARS_PER_VALUE1_LINE, "%s", address); + + ux_layout_bnnn_paging_reset(); + if(G_ux.stack_count == 0) { + ux_stack_push(); + } + ux_flow_init(0, ux_addr_flow, NULL); +} + +void view_error_show_impl() { + ux_layout_bnnn_paging_reset(); + if(G_ux.stack_count == 0) { + ux_stack_push(); + } + ux_flow_init(0, ux_error_flow, NULL); +} + +void view_sign_show_impl(){ + h_review_init(); + h_review_decrease(); + //// + flow_inside_loop = 0; + if(G_ux.stack_count == 0) { + ux_stack_push(); + } + ux_flow_init(0, ux_sign_flow, NULL); +} + +#endif From bbfb1f6db356ca40f7b14fa9756ab3f3ac3eb51d Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 3 Sep 2019 17:52:22 +0200 Subject: [PATCH 11/78] replacing old views --- src/app_main.c | 71 +------- src/lib/json_parser.c | 1 - src/lib/parser.c | 111 ++++++++++++ src/lib/parser.h | 46 +++++ src/{view_expl.h => lib/parser_impl.h} | 39 ++-- src/tx.c | 79 ++++++--- src/view.c | 237 +++++++++---------------- src/view.h | 26 +-- src/view_common.c | 205 --------------------- src/view_common.h | 118 ------------ src/view_expl.c | 197 -------------------- 11 files changed, 338 insertions(+), 792 deletions(-) create mode 100644 src/lib/parser.c create mode 100644 src/lib/parser.h rename src/{view_expl.h => lib/parser_impl.h} (56%) delete mode 100644 src/view_common.c delete mode 100644 src/view_common.h delete mode 100644 src/view_expl.c diff --git a/src/app_main.c b/src/app_main.c index 2664144a..8b57711e 100644 --- a/src/app_main.c +++ b/src/app_main.c @@ -26,8 +26,7 @@ #include "tx.h" #include "lib/crypto.h" #include "cosmos.h" -#include -#include "lib/tx_display.h" +#include "zxmacros.h" unsigned char G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B]; @@ -111,7 +110,7 @@ void extractBip32(uint32_t rx, uint32_t offset) { } } -bool process_chunk(volatile uint32_t *tx, uint32_t rx, bool getBip32) { +bool process_chunk(volatile uint32_t *tx, uint32_t rx) { int packageIndex = G_io_apdu_buffer[OFFSET_PCK_INDEX]; int packageCount = G_io_apdu_buffer[OFFSET_PCK_COUNT]; @@ -124,10 +123,9 @@ bool process_chunk(volatile uint32_t *tx, uint32_t rx, bool getBip32) { tx_initialize(); tx_reset(); - if (getBip32) { - extractBip32(rx, OFFSET_DATA); - return packageIndex == packageCount; - } + extractBip32(rx, OFFSET_DATA); + + return packageIndex == packageCount; } if (tx_append(&(G_io_apdu_buffer[offset]), rx - offset) != rx - offset) { @@ -137,58 +135,6 @@ bool process_chunk(volatile uint32_t *tx, uint32_t rx, bool getBip32) { return packageIndex == packageCount; } -//region View Transaction Handlers - -int16_t tx_getData(char *title, int16_t max_title_length, - char *key, int16_t max_key_length, - char *value, int16_t max_value_length, - int16_t page_index, - int16_t chunk_index, - int16_t *page_count_out, - int16_t *chunk_count_out) { - *page_count_out = tx_display_num_pages(); - *chunk_count_out = 0; - - if (*page_count_out > 0) { - - // TODO: remove this title - snprintf(title, - max_title_length, - "%02d/%02d", - page_index + 1, - *page_count_out); - - INIT_QUERY(key, max_key_length, value, max_value_length, chunk_index) - *chunk_count_out = tx_display_get_item(page_index); - - tx_display_make_friendly(); - } - - return *chunk_count_out; -} - -void tx_accept_sign() { - uint8_t length = app_sign(); - - if (length == 0) { - set_code(G_io_apdu_buffer, length, APDU_CODE_SIGN_VERIFY_ERROR); - io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, length + 2); - view_idle_show(0); - } - - set_code(G_io_apdu_buffer, length, APDU_CODE_OK); - io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, length + 2); - view_idle_show(0); -} - -void tx_reject() { - set_code(G_io_apdu_buffer, 0, APDU_CODE_COMMAND_NOT_ALLOWED); - io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); - view_idle_show(0); -} - -//endregion - void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { uint16_t sw = 0; @@ -245,10 +191,11 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { } case INS_SIGN_SECP256K1: { - if (!process_chunk(tx, rx, true)) + if (!process_chunk(tx, rx)) THROW(APDU_CODE_OK); const char *error_msg = tx_parse(); + if (error_msg != NULL) { int error_msg_length = strlen(error_msg); os_memmove(G_io_apdu_buffer, error_msg, error_msg_length); @@ -256,10 +203,6 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { THROW(APDU_CODE_DATA_INVALID); } -// FIXME: - tx_display_index_root(); - view_set_handlers(tx_getData, tx_accept_sign, tx_reject); - view_sign_show(); *flags |= IO_ASYNCH_REPLY; break; diff --git a/src/lib/json_parser.c b/src/lib/json_parser.c index 5874d887..5672390b 100644 --- a/src/lib/json_parser.c +++ b/src/lib/json_parser.c @@ -30,7 +30,6 @@ void reset_parsed_json(parsed_json_t *parser_data) { const char *json_parse(parsed_json_t *parsed_json, const char *transaction) { return json_parse_s(parsed_json, transaction, strlen(transaction)); - } const char *json_parse_s(parsed_json_t *parsed_json, diff --git a/src/lib/parser.c b/src/lib/parser.c new file mode 100644 index 00000000..7e7e45fb --- /dev/null +++ b/src/lib/parser.c @@ -0,0 +1,111 @@ +/******************************************************************************* +* (c) 2019 ZondaX GmbH +* +* 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 +#include +#include "parser.h" +#include "cosmos.h" + +#include "lib/json_parser.h" +#include "lib/tx_validate.h" +#include "lib/tx_parser.h" +#include "lib/tx_display.h" + +#include "lib/parser_impl.h" +#include "view_internal.h" + +parsed_json_t parsed_transaction; +const char *lastErrorMessage = NULL; + +parser_error_t parser_parse(parser_context_t *ctx, + uint8_t *data, + uint16_t dataLen) { + + lastErrorMessage = json_parse_s(&parsed_transaction, (const char *) data, dataLen); + if (lastErrorMessage != NULL) { + return parser_extended_error; + } + lastErrorMessage = json_validate(&parsed_transaction, (const char *) data); + if (lastErrorMessage != NULL) { + return parser_extended_error; + } + + parsing_context_t context; + context.tx = (const char *) data; + context.max_chars_per_key_line = MAX_CHARS_PER_KEY_LINE; + context.max_chars_per_value_line = MAX_CHARS_PER_VALUE_LINE; + context.parsed_tx = &parsed_transaction; + + set_parsing_context(context); + tx_display_index_root(); + + return parser_ok; +} + +const char *parser_getErrorDescription(parser_error_t err) { + switch (err) { + case parser_ok: + return "No error"; + case parser_no_data: + return "No more data"; + case parser_extended_error: + if (lastErrorMessage != NULL) + return lastErrorMessage; + return "Unknown message"; + case parser_unexpected_buffer_end: + return "Unexpected buffer end"; + case parser_unexpected_wire_type: + return "Unexpected wire type"; + case parser_unexpected_version: + return "Unexpected version"; + case parser_unexpected_characters: + return "Unexpected characters"; + case parser_unexpected_field: + return "Unexpected field"; + case parser_duplicated_field: + return "Unexpected duplicated field"; + case parser_unexpected_chain: + return "Unexpected chain"; + default: + return "Unrecognized error code"; + } +} + +parser_error_t parser_validate() { + return parser_ok; +} + +uint8_t parser_getNumItems(parser_context_t *ctx) { + return tx_display_num_pages(); +} + +parser_error_t parser_getItem(parser_context_t *ctx, + int8_t displayIdx, + char *outKey, uint16_t outKeyLen, + char *outValue, uint16_t outValueLen, + uint8_t pageIdx, uint8_t *pageCount) { + + snprintf(outKey, outKeyLen, "?"); + snprintf(outValue, outValueLen, "?"); + + parser_error_t err = parser_ok; + + INIT_QUERY(outKey, outKeyLen, outValue, outValueLen, displayIdx) + * pageCount = tx_display_get_item(pageIdx); + tx_display_make_friendly(); + + return err; +} diff --git a/src/lib/parser.h b/src/lib/parser.h new file mode 100644 index 00000000..2d88a7bb --- /dev/null +++ b/src/lib/parser.h @@ -0,0 +1,46 @@ +/******************************************************************************* +* (c) 2019 ZondaX GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "parser_impl.h" + +const char *parser_getErrorDescription(parser_error_t err); + +//// parses a tx buffer +parser_error_t parser_parse(parser_context_t *ctx, + uint8_t *data, uint16_t dataLen); + +//// verifies tx fields +parser_error_t parser_validate(); + +//// returns the number of items in the current parsing context +uint8_t parser_getNumItems(parser_context_t *ctx); + +// retrieves a readable output for each field / page +parser_error_t parser_getItem(parser_context_t *ctx, + int8_t displayIdx, + char *outKey, uint16_t outKeyLen, + char *outValue, uint16_t outValueLen, + uint8_t pageIdx, uint8_t *pageCount); + +#ifdef __cplusplus +} +#endif diff --git a/src/view_expl.h b/src/lib/parser_impl.h similarity index 56% rename from src/view_expl.h rename to src/lib/parser_impl.h index 279625d3..083544a1 100644 --- a/src/view_expl.h +++ b/src/lib/parser_impl.h @@ -1,6 +1,5 @@ /******************************************************************************* -* (c) 2016 Ledger -* (c) 2018, 2019 ZondaX GmbH +* (c) 2019 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +15,30 @@ ********************************************************************************/ #pragma once -#include "os.h" -#include "cx.h" -#include "view_common.h" +#ifdef __cplusplus +extern "C" { +#endif -// Initialize and show control -void viewexpl_start( - int start_page, - viewctl_delegate_getData delegate_update, - viewctl_delegate_ready delegate_ready, - viewctl_delegate_exit delegate_exit - ); +#include +#include + +typedef enum { + parser_ok = 0, + parser_no_data = 1, + parser_extended_error = 2, + parser_unexpected_buffer_end = 3, + parser_unexpected_wire_type = 4, + parser_unexpected_version = 5, + parser_unexpected_characters = 6, + parser_unexpected_field = 7, + parser_duplicated_field = 8, + parser_value_out_of_range = 9, + parser_unexpected_chain = 10, +} parser_error_t; + +typedef struct { +} parser_context_t; + +#ifdef __cplusplus +} +#endif diff --git a/src/tx.c b/src/tx.c index 7f31ac47..28cb4653 100644 --- a/src/tx.c +++ b/src/tx.c @@ -17,19 +17,15 @@ #include "tx.h" #include "apdu_codes.h" #include "buffering.h" -#include "lib/json_parser.h" -#include "lib/tx_validate.h" -#include "lib/tx_parser.h" - -// TODO: Remove this dependency -#include "view.h" +#include "lib/parser.h" +#include #if defined(TARGET_NANOX) - #define RAM_BUFFER_SIZE 8192 - #define FLASH_BUFFER_SIZE 16384 +#define RAM_BUFFER_SIZE 8192 +#define FLASH_BUFFER_SIZE 16384 #elif defined(TARGET_NANOS) - #define RAM_BUFFER_SIZE 384 - #define FLASH_BUFFER_SIZE 8192 +#define RAM_BUFFER_SIZE 384 +#define FLASH_BUFFER_SIZE 8192 #endif // Ram @@ -49,7 +45,7 @@ storage_t const N_appdata_impl __attribute__ ((aligned(64))); #define N_appdata (*(volatile storage_t *)PIC(&N_appdata_impl)) #endif -parsed_json_t parsed_transaction; +parser_context_t ctx_parsed_tx; void tx_initialize() { buffering_init( @@ -76,23 +72,54 @@ uint8_t *tx_get_buffer() { return buffering_get_buffer()->data; } -const char* tx_parse() { - const char *transaction_buffer = (const char *) tx_get_buffer(); - const char* error_msg = json_parse_s(&parsed_transaction, transaction_buffer, tx_get_buffer_length()); - if (error_msg != NULL) { - return error_msg; - } - error_msg = json_validate(&parsed_transaction, transaction_buffer); - if (error_msg != NULL) { - return error_msg; +const char *tx_parse() { + uint8_t err = parser_parse( + &ctx_parsed_tx, + tx_get_buffer(), + tx_get_buffer_length()); + + if (err != parser_ok) { + return parser_getErrorDescription(err); } - parsing_context_t context; - context.tx = transaction_buffer; - context.max_chars_per_key_line = MAX_CHARS_PER_KEY_LINE; - context.max_chars_per_value_line = MAX_CHARS_PER_VALUE_LINE; - context.parsed_tx = &parsed_transaction; + err = parser_validate(); + if (err != parser_ok) { + return parser_getErrorDescription(err); + } - set_parsing_context(context); return NULL; } + +uint8_t tx_getNumItems() { + return parser_getNumItems(&ctx_parsed_tx); +} + +tx_error_t tx_getItem(int8_t displayIdx, + char *outKey, uint16_t outKeyLen, + char *outValue, uint16_t outValueLen, + uint8_t pageIdx, uint8_t *pageCount) { + tx_error_t err = tx_no_error; + + err = (tx_error_t) parser_getItem(&ctx_parsed_tx, + displayIdx, + outKey, outKeyLen, + outValue, outValueLen, + pageIdx, pageCount); + + if (*pageCount > 1) { + uint8_t keyLen = strlen(outKey); + if (keyLen < outKeyLen) { + // FIXME: Improve Key Trimming + snprintf(outKey + keyLen, outKeyLen - keyLen, " [%d/%d]", pageIdx + 1, *pageCount); + } + } + + // Convert error codes + if (err == parser_no_data) + return tx_no_data; + + if (err == parser_ok) + return tx_no_error; + + return err; +} diff --git a/src/view.c b/src/view.c index 2d07815a..0d174695 100644 --- a/src/view.c +++ b/src/view.c @@ -1,6 +1,6 @@ /******************************************************************************* -* (c) 2016 Ledger * (c) 2018, 2019 ZondaX GmbH +* (c) 2016 Ledger * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,193 +16,130 @@ ********************************************************************************/ #include "view.h" -#include "view_templates.h" -#include "view_expl.h" +#include "view_internal.h" +#include "actions.h" +#include "apdu_codes.h" #include "glyphs.h" #include "bagl.h" #include "zxmacros.h" -#include "lib/crypto.h" -#include "cosmos.h" +#include "view_templates.h" +#include "tx.h" #include #include -viewctl_delegate_getData ehGetData = NULL; -viewctl_delegate_accept ehAccept = NULL; -viewctl_delegate_reject ehReject = NULL; - -#define MAX_VAL(a, b) ( (a)>(b) ? (a) : (b) ) -#define MIN_VAL(a, b) ( (a)<(b) ? (a) : (b) ) - -#define VIEW_ADDR_MODE_ACCOUNT 0 -#define VIEW_ADDR_MODE_INDEX 1 -#define VIEW_ADDR_MODE_SHOW 2 -#define VIEW_ADDR_MODE_CONFIRM 3 - -#define UIID_ICONACCEPT 0x50 -#define UIID_ICONREJECT 0x51 -#define UIID_ICONLEFT1 0x52 -#define UIID_ICONRIGHT1 0x53 -#define UIID_ICONLEFT2 0x54 -#define UIID_ICONRIGHT2 0x55 - -#define UIID_MARKER1 0x60 -#define UIID_MARKER2 0x61 +view_t viewdata; +const char *address; -void accept(unsigned int _) { +void h_address_accept(unsigned int _) { UNUSED(_); - if (ehAccept != NULL) ehAccept(); + view_idle_show(0); + UX_WAIT(); + app_reply_address(); } -void reject(unsigned int _) { +void h_error_accept(unsigned int _) { UNUSED(_); - if (ehReject != NULL) ehReject(); + view_idle_show(0); + UX_WAIT(); + app_reply_address(); } -void show_idle_menu() { +void h_sign_accept(unsigned int _) { + UNUSED(_); + + const uint8_t replyLen = app_sign(); + view_idle_show(0); + UX_WAIT(); + + set_code(G_io_apdu_buffer, replyLen, APDU_CODE_OK); + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, replyLen + 2); } -void view_tx_menu(unsigned int _); - -#if defined(TARGET_NANOX) - -#include "ux.h" -ux_state_t G_ux; -bolos_ux_params_t G_ux_params; - -#ifdef TESTING_ENABLED -UX_FLOW_DEF_NOCB(ux_idle_flow_1_step, pbb, { &C_icon_app, "Tendermint", "Cosmos TEST!", }); -#else -UX_FLOW_DEF_NOCB(ux_idle_flow_1_step, pbb, { &C_icon_app, "Tendermint", "Cosmos", }); -#endif -UX_FLOW_DEF_NOCB(ux_idle_flow_3_step, bn, { "Version", APPVERSION, }); -UX_FLOW_DEF_VALID(ux_idle_flow_4_step, pb, os_sched_exit(-1), { &C_icon_dashboard, "Quit",}); -const ux_flow_step_t *const ux_idle_flow [] = { - &ux_idle_flow_1_step, - &ux_idle_flow_2_step, - &ux_idle_flow_3_step, - &ux_idle_flow_4_step, - FLOW_END_STEP, -}; - -UX_FLOW_DEF_VALID(ux_tx_flow_1_step, pbb, view_tx_show(0), { &C_icon_eye, "Review", "Transaction" }); -UX_FLOW_DEF_VALID(ux_tx_flow_2_step, pbb, accept(0), { &C_icon_validate_14, "Sign", "Transaction" }); -UX_FLOW_DEF_VALID(ux_tx_flow_3_step, pbb, reject(0), { &C_icon_crossmark, "Reject", "Transaction" }); -const ux_flow_step_t *const ux_tx_flow [] = { - &ux_tx_flow_1_step, - &ux_tx_flow_2_step, - &ux_tx_flow_3_step, - FLOW_END_STEP, -}; - -UX_FLOW_DEF_NOCB(ux_addr_flow_1_step, bnn, { "Address Request", viewctl.title, viewctl.dataKey}); -UX_FLOW_DEF_NOCB(ux_addr_flow_2_step, bnnn_paging, { .title = "Address", .text = viewctl.dataValue }); -UX_FLOW_DEF_VALID(ux_addr_flow_3_step, pb, accept(0), { &C_icon_validate_14, "Reply", }); -UX_FLOW_DEF_VALID(ux_addr_flow_4_step, pb, reject(0), { &C_icon_crossmark, "Reject", }); -const ux_flow_step_t *const ux_addr_flow [] = { - &ux_addr_flow_1_step, - &ux_addr_flow_2_step, - &ux_addr_flow_3_step, - &ux_addr_flow_4_step, - FLOW_END_STEP, -}; -#else - -// Nano S -ux_state_t ux; - -const ux_menu_entry_t menu_transaction_info[] = { - {NULL, view_sign_show, 0, NULL, "View transaction", NULL, 0, 0}, - {NULL, accept, 0, NULL, "Sign transaction", NULL, 0, 0}, - {NULL, reject, 0, &C_icon_crossmark, "Reject", NULL, 60, 40}, - UX_MENU_END -}; - -const ux_menu_entry_t menu_main[] = { -#ifdef TESTING_ENABLED - {NULL, NULL, 0, &C_icon_app, "Tendermint", "Cosmos TEST!", 33, 12}, -#else - {NULL, NULL, 0, &C_icon_app, "Tendermint", "Cosmos", 33, 12}, -#endif - {NULL, NULL, 0, NULL, "v"APPVERSION, NULL, 0, 0}, - {NULL, os_sched_exit, 0, &C_icon_dashboard, "Quit app", NULL, 50, 29}, - UX_MENU_END -}; - -const ux_menu_entry_t menu_status[] = { - {NULL, NULL, 0, &C_icon_app, viewctl.dataKey, viewctl.dataValue, 33, 12}, - UX_MENU_END -}; - -#endif - -//////////////////////////////// -//////////////////////////////// -//////////////////////////////// +void h_sign_reject(unsigned int _) { + UNUSED(_); + view_idle_show(0); + UX_WAIT(); -void io_seproxyhal_display(const bagl_element_t *element) { - io_seproxyhal_display_default((bagl_element_t *) element); + set_code(G_io_apdu_buffer, 0, APDU_CODE_COMMAND_NOT_ALLOWED); + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); } -void view_init(void) { - UX_INIT(); +void h_review_init() { + viewdata.idx = 0; + viewdata.pageIdx = 0; + viewdata.pageCount = 1; } -void view_idle_show(unsigned int ignored) { -#if defined(TARGET_NANOS) - UX_MENU_DISPLAY(0, menu_main, NULL); -#elif defined(TARGET_NANOX) - if(G_ux.stack_count == 0) { - ux_stack_push(); +void h_review_increase() { + viewdata.pageIdx++; + if (viewdata.pageIdx >= viewdata.pageCount) { + viewdata.idx++; + viewdata.pageIdx = 0; } - ux_flow_init(0, ux_idle_flow, NULL); -#endif } -void view_status(unsigned int ignored) { -#if defined(TARGET_NANOS) - UX_MENU_DISPLAY(0, menu_status, NULL); -#endif +void h_review_decrease() { + viewdata.pageIdx--; + if (viewdata.pageIdx < 0) { + viewdata.idx--; + viewdata.pageIdx = 0; + } } -void view_sign_show(unsigned int ignored) { - if (ehGetData == NULL) { return; } - viewexpl_start(0, - ehGetData, - NULL, - view_tx_menu); +view_error_t h_review_update_data() { + tx_error_t err = tx_no_error; -} + do { + err = tx_getItem(viewdata.idx, + viewdata.key, MAX_CHARS_PER_KEY_LINE, + viewdata.value, MAX_CHARS_PER_VALUE1_LINE, + viewdata.pageIdx, &viewdata.pageCount); -void view_tx_menu(unsigned int unused) { - UNUSED(unused); + if (err == tx_no_data) { + return view_no_data; + } -#if defined(TARGET_NANOS) - UX_MENU_DISPLAY(0, menu_transaction_info, NULL); -#elif defined(TARGET_NANOX) - if(G_ux.stack_count == 0) { - ux_stack_push(); + if (viewdata.pageCount == 0) { + h_review_increase(); + } + } while (viewdata.pageCount == 0); + + if (err != tx_no_error) { + return view_error_detected; } - ux_flow_init(0, ux_tx_flow, NULL); -#endif + + splitValueField(); + return view_no_error; } -void view_set_handlers(viewctl_delegate_getData func_getData, - viewctl_delegate_accept func_accept, - viewctl_delegate_reject func_reject) { - ehGetData = func_getData; - ehAccept = func_accept; - ehReject = func_reject; +void io_seproxyhal_display(const bagl_element_t *element) { + io_seproxyhal_display_default((bagl_element_t *) element); } -const char *address; +void view_init(void) { + UX_INIT(); +} + +void view_idle_show(unsigned int ignored) { + view_idle_show_impl(); +} void view_address_show() { // Address has been placed in the output buffer - address = (char *) (G_io_apdu_buffer + PUBKEY_LEN); + address = (char *) (G_io_apdu_buffer + 32); + view_address_show_impl(); +} + +void view_error_show() { + snprintf(viewdata.key, MAX_CHARS_PER_KEY_LINE, "ERROR"); + snprintf(viewdata.value, MAX_CHARS_PER_VALUE1_LINE, "SHOWING DATA"); + splitValueField(); + view_error_show_impl(); +} - // FIXME: - //view_address_show_impl(); +void view_sign_show() { + view_sign_show_impl(); } diff --git a/src/view.h b/src/view.h index 0f75728b..4853a8fe 100644 --- a/src/view.h +++ b/src/view.h @@ -26,29 +26,17 @@ #endif #endif -#include "view_common.h" -#include "view_expl.h" - -//------ Event handlers -/// view_set_handlers -void view_set_handlers(viewctl_delegate_getData func_getData, - viewctl_delegate_accept func_accept, - viewctl_delegate_reject func_reject); - -//------ Common functions /// view_init (initializes UI) -void view_init(void); +void view_init(); /// view_idle_show (idle view - main menu + status) void view_idle_show(unsigned int ignored); -/// view_status -void view_status(); - -/// view_tx_show (show/review transaction view) -void view_sign_show(); - -/// view_addr_confirm (show/accept public key + address request) -void view_addr_confirm(unsigned int _); +/// view_error (error view) +void view_error_show(); +// shows address in the screen void view_address_show(); + +// Shows review screen + later sign menu +void view_sign_show(); diff --git a/src/view_common.c b/src/view_common.c deleted file mode 100644 index 3ba520bb..00000000 --- a/src/view_common.c +++ /dev/null @@ -1,205 +0,0 @@ -/******************************************************************************* -* (c) 2016 Ledger -* (c) 2018, 2019 ZondaX GmbH -* -* 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 "view.h" -#include "view_templates.h" -#include "view_expl.h" - -#include "glyphs.h" -#include "bagl.h" -#include "zxmacros.h" - -#include -#include - -void viewctl_display_page(); - -const char *dblClickInfo = "DBL-CLICK TO VIEW"; - -viewctl_s viewctl; - -viewctl_delegate_getData viewctl_ehGetData = NULL; -viewctl_delegate_ready viewctl_ehReady = NULL; -viewctl_delegate_exit viewctl_ehExit = NULL; -viewctl_delegate_display_ux viewctl_display_ux = NULL; - -//------ External functions - -void viewctl_start(int start_page, - viewctl_delegate_getData func_getData, - viewctl_delegate_ready ehReady, - viewctl_delegate_exit ehExit, - viewctl_delegate_display_ux func_display_ux) { - // set handlers - viewctl_ehGetData = func_getData; - viewctl_ehReady = ehReady; - viewctl_ehExit = ehExit; - viewctl_display_ux = func_display_ux; - - // initialize variables - viewctl.scrolling_mode = PENDING; - viewctl.detailsCurrentPage = start_page; - viewctl.chunksIndex = 0; - viewctl.chunksCount = 1; - - viewctl_display_page(); - if (viewctl_ehReady != NULL) { - viewctl_ehReady(0); - } -} - -//------ Event handlers - -const bagl_element_t *ui_view_info_prepro(const bagl_element_t *element) { - - switch (element->component.userid) { - case 0x01: - UX_CALLBACK_SET_INTERVAL(2000); - break; - case 0x02: - UX_CALLBACK_SET_INTERVAL(MAX(3000, 1000 + bagl_label_roundtrip_duration_ms(element, 7))); - break; - } - return element; -} - -void submenu_left() { - viewctl.chunksIndex--; - viewctl.scrolling_mode = PENDING; - viewctl_display_page(); -} - -void submenu_right() { - viewctl.chunksIndex++; - viewctl.scrolling_mode = PENDING; - viewctl_display_page(); -} - -void menu_left() { - viewctl.scrolling_mode = PENDING; - viewctl.chunksIndex = 0; - viewctl.chunksCount = 1; - if (viewctl.detailsCurrentPage > 0) { - viewctl.detailsCurrentPage--; - viewctl_display_page(); - } else { - viewctl_ehExit(0); - } -} - -void menu_right() { - viewctl.scrolling_mode = PENDING; - viewctl.chunksIndex = 0; - viewctl.chunksCount = 1; - if (viewctl.detailsCurrentPage < viewctl.detailsPageCount - 1) { - viewctl.detailsCurrentPage++; - viewctl_display_page(); - } else { - viewctl_ehExit(0); - } -} - -void viewctl_crop_key() { - int offset = strlen((char *) viewctl.dataKey) - MAX_SCREEN_LINE_WIDTH; - if (offset > 0) { - char *start = (char *) viewctl.dataKey; - for (;;) { - *start = start[offset]; - if (*start++ == '\0') - break; - } - } -} - -void viewctl_dataValue_split() { -#if defined(TARGET_NANOX) - const int dataValueLen = strlen(viewctl.dataValue); - - int offset = 0; - for (int i = 0; i < MAX_SCREEN_NUM_LINES; i++) { - viewctl.dataValueChunk[i][0] = 0; // clean/terminate strings - if (offset < dataValueLen) { - snprintf((char *) viewctl.dataValueChunk[i], MAX_SCREEN_LINE_WIDTH, "%s", viewctl.dataValue + offset); - } - offset += (MAX_SCREEN_LINE_WIDTH - 1); - } -#endif -} - -void viewctl_display_page() { - if (viewctl_ehGetData == NULL) { - return; - } - - strcpy(viewctl.title, "?"); - strcpy(viewctl.dataKey, "?"); - strcpy(viewctl.dataValue, "?"); - - // Read key and value strings from json - viewctl_ehGetData( - (char *) viewctl.title, sizeof(viewctl.title), - (char *) viewctl.dataKey, sizeof(viewctl.dataKey), - (char *) viewctl.dataValue, sizeof(viewctl.dataValue), - viewctl.detailsCurrentPage, viewctl.chunksIndex, - &viewctl.detailsPageCount, &viewctl.chunksCount); - - // fix possible utf8 issues - asciify((char *) viewctl.title); - asciify((char *) viewctl.dataKey); - asciify((char *) viewctl.dataValue); - - if (viewctl.chunksCount > 0) { - // If value is very long, we split it into chunks - // and add chunk index/count information at the end of the key - if (viewctl.chunksCount > 1) { - int position = strlen((char *) viewctl.dataKey); - snprintf((char *) viewctl.dataKey + position, - sizeof(viewctl.dataKey) - position, - " %d/%d", - viewctl.chunksIndex + 1, - viewctl.chunksCount); - } - -#if defined(TARGET_NANOX) - viewctl_dataValue_split(); -#elif defined(TARGET_NANOS) - switch (viewctl.scrolling_mode) { - case KEY_SCROLLING_NO_VALUE: { - viewctl_crop_key(); - viewctl.scrolling_mode = VALUE_SCROLLING; - break; - } - case PENDING: { - viewctl.scrolling_mode = VALUE_SCROLLING; - if (strlen((char *) viewctl.dataKey) > MAX_SCREEN_LINE_WIDTH) { - int value_length = strlen((char *) viewctl.dataValue); - if (value_length > MAX_SCREEN_LINE_WIDTH) { - strcpy((char *) viewctl.dataValue, "DBL-CLICK FOR VALUE"); - viewctl.scrolling_mode = KEY_SCROLLING_NO_VALUE; - } else { - viewctl.scrolling_mode = KEY_SCROLLING_SHORT_VALUE; - } - } - } - default: - break; - } -#endif - } - - viewctl_display_ux(); -} diff --git a/src/view_common.h b/src/view_common.h deleted file mode 100644 index 07e40c46..00000000 --- a/src/view_common.h +++ /dev/null @@ -1,118 +0,0 @@ -/******************************************************************************* -* (c) 2016 Ledger -* (c) 2018, 2019 ZondaX GmbH -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -********************************************************************************/ -#pragma once - -#include - -#include "os.h" -#include "cx.h" - -#define MAX_CHARS_TITLE 32 -#define MAX_CHARS_PER_KEY_LINE 64 -#define MAX_CHARS_PER_VALUE_LINE 192 -#define MAX_SCREEN_LINE_WIDTH 19 - -enum UI_DISPLAY_MODE { - VALUE_SCROLLING, - KEY_SCROLLING_NO_VALUE, - KEY_SCROLLING_SHORT_VALUE, - PENDING -}; - -#if defined(TARGET_NANOX) -#define MAX_SCREEN_NUM_LINES 4 -#endif - -typedef struct { - enum UI_DISPLAY_MODE scrolling_mode; - // Index of the currently displayed page - int16_t detailsCurrentPage; - // Total number of displayable pages - int16_t detailsPageCount; - - // When data goes beyond the limit, it will be split in chunks that - // that spread over several pages - // Index of currently displayed value chunk - int16_t chunksIndex; - // Total number of displayable value chunks - int16_t chunksCount; - - // DATA - char title[MAX_SCREEN_LINE_WIDTH]; - char dataKey[MAX_CHARS_PER_KEY_LINE]; - char dataValue[MAX_CHARS_PER_VALUE_LINE]; - -#if defined(TARGET_NANOX) - char dataValueChunk[MAX_SCREEN_NUM_LINES][MAX_SCREEN_LINE_WIDTH+1]; -#endif -} viewctl_s; - -extern viewctl_s viewctl; - -// Delegate to update contents -typedef int16_t (*viewctl_delegate_getData)( - char *title, int16_t max_title_length, - char *key, int16_t max_key_length, - char *value, int16_t max_value_length, - int16_t page_index, - int16_t chunk_index, - int16_t *page_count_out, - int16_t *chunk_count_out); - -// Delegate to handle exit view event -typedef void (*viewctl_delegate_exit)(unsigned int ignored); - -// Delegate to handle exit view event -typedef void (*viewctl_delegate_ready)(unsigned int ignored); - -// Delegate to handle exit view event -typedef void (*viewctl_delegate_display_ux)(); - -// Delegate to handle an accept event -typedef void (*viewctl_delegate_accept)(); - -// Delegate to handle a reject event -typedef void (*viewctl_delegate_reject)(); - -extern viewctl_delegate_getData viewctl_ehGetData; -extern viewctl_delegate_ready viewctl_ehReady; -extern viewctl_delegate_exit viewctl_ehExit; -extern viewctl_delegate_display_ux viewctl_display_ux; - -void viewctl_start(int start_page, - viewctl_delegate_getData ehUpdate, - viewctl_delegate_ready ehReady, - viewctl_delegate_exit ehExit, - viewctl_delegate_display_ux func_display_ux); - -void viewctl_display_page(); - -void submenu_left(); - -void submenu_right(); - -void menu_left(); - -void menu_right(); - -#define print_title(...) snprintf(viewctl.title, sizeof(viewctl.title), __VA_ARGS__) - -#define print_key(...) snprintf(viewctl.dataKey, sizeof(viewctl.dataKey), __VA_ARGS__) - -#define print_value(...) snprintf(viewctl.dataValue, sizeof(viewctl.dataValue), __VA_ARGS__) - -void viewctl_dataValue_split(); diff --git a/src/view_expl.c b/src/view_expl.c deleted file mode 100644 index ed80d6b0..00000000 --- a/src/view_expl.c +++ /dev/null @@ -1,197 +0,0 @@ -/******************************************************************************* -* (c) 2016 Ledger -* (c) 2018, 2019 ZondaX GmbH -* -* 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 "view.h" -#include "view_templates.h" -#include "view_expl.h" - -#include "glyphs.h" -#include "bagl.h" -#include "zxmacros.h" - -#include -#include - -#if defined(TARGET_NANOX) -static const bagl_element_t viewexpl_bagl[] = { - UI_BACKGROUND_LEFT_RIGHT_ICONS, - UI_LabelLine(UIID_LABEL+0, 0, 9 + UI_11PX * 0, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.title), - UI_LabelLine(UIID_LABEL+1, 0, 9 + UI_11PX * 1, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataKey), - UI_LabelLine(UIID_LABEL+2, 0, 9 + UI_11PX * 2, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[0]), - UI_LabelLine(UIID_LABEL+3, 0, 9 + UI_11PX * 3, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[1]), - UI_LabelLine(UIID_LABEL+4, 0, 9 + UI_11PX * 4, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[2]), - UI_LabelLine(UIID_LABEL+5, 0, 9 + UI_11PX * 5, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[3]), -}; - -static unsigned int viewexpl_bagl_button( - unsigned int button_mask, - unsigned int button_mask_counter) { - switch (button_mask) { - // Press both left and right to switch to value scrolling - case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: { - if (viewctl.scrolling_mode == KEY_SCROLLING_NO_VALUE) { - viewctl_display_page(); - } else { - viewctl_ehExit(0); - } - break; - } - - // Press left to progress to the previous element - case BUTTON_EVT_RELEASED | BUTTON_LEFT: { - if (viewctl.chunksIndex > 0) { - submenu_left(); - } else { - menu_left(); - } - break; - } - - // Hold left to progress to the previous element quickly - // It also steps out from the value chunk page view mode - case BUTTON_EVT_FAST | BUTTON_LEFT: { - menu_left(); - break; - } - - // Press right to progress to the next element - case BUTTON_EVT_RELEASED | BUTTON_RIGHT: { - if (viewctl.chunksIndex < viewctl.chunksCount - 1) { - submenu_right(); - } else { - menu_right(); - } - break; - } - - // Hold right to progress to the next element quickly - // It also steps out from the value chunk page view mode - case BUTTON_EVT_FAST | BUTTON_RIGHT: { - menu_right(); - break; - } - } - return 0; -} - -#elif defined(TARGET_NANOS) -static const bagl_element_t viewexpl_bagl_valuescrolling[] = { - UI_BACKGROUND_LEFT_RIGHT_ICONS, - UI_LabelLine(UIID_LABEL + 0, 0, 8, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.title), - UI_LabelLine(UIID_LABEL + 1, 0, 19, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataKey), - UI_LabelLineScrolling(UIID_LABELSCROLL, 16, 30, 96, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValue), -}; - -static const bagl_element_t viewexpl_bagl_keyscrolling[] = { - UI_BACKGROUND_LEFT_RIGHT_ICONS, - UI_LabelLine(UIID_LABEL + 0, 0, 8, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.title), - UI_LabelLine(UIID_LABEL + 1, 0, 30, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValue), - UI_LabelLineScrolling(UIID_LABELSCROLL, 16, 19, 96, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataKey), -}; - -static unsigned int viewexpl_bagl_keyscrolling_button( - unsigned int button_mask, - unsigned int button_mask_counter) { - switch (button_mask) { - // Press both left and right to switch to value scrolling - case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: { - if (viewctl.scrolling_mode == KEY_SCROLLING_NO_VALUE) { - viewctl_display_page(); - } else { - viewctl_ehExit(0); - } - break; - } - - // Press left to progress to the previous element - case BUTTON_EVT_RELEASED | BUTTON_LEFT: { - if (viewctl.chunksIndex > 0) { - submenu_left(); - } else { - menu_left(); - } - break; - } - - // Hold left to progress to the previous element quickly - // It also steps out from the value chunk page view mode - case BUTTON_EVT_FAST | BUTTON_LEFT: { - menu_left(); - break; - } - - // Press right to progress to the next element - case BUTTON_EVT_RELEASED | BUTTON_RIGHT: { - if (viewctl.chunksIndex < viewctl.chunksCount - 1) { - submenu_right(); - } else { - menu_right(); - } - break; - } - - // Hold right to progress to the next element quickly - // It also steps out from the value chunk page view mode - case BUTTON_EVT_FAST | BUTTON_RIGHT: { - menu_right(); - break; - } - } - return 0; -} - -static unsigned int viewexpl_bagl_valuescrolling_button( - unsigned int button_mask, - unsigned int button_mask_counter) { - return viewexpl_bagl_keyscrolling_button(button_mask, button_mask_counter); -} - -#endif - -const bagl_element_t *viewexpl_bagl_prepro(const bagl_element_t *element) { - switch (element->component.userid) { - case UIID_ICONLEFT: - UX_CALLBACK_SET_INTERVAL(2000); - break; - case UIID_ICONRIGHT: - UX_CALLBACK_SET_INTERVAL(2000); - break; - case UIID_LABELSCROLL: - UX_CALLBACK_SET_INTERVAL(MAX(3000, 1000 + bagl_label_roundtrip_duration_ms(element, 7))); - break; - } - return element; -} - -void viewexpl_display_ux() { -#if defined(TARGET_NANOX) - UX_DISPLAY(viewexpl_bagl, viewexpl_bagl_prepro); -#else - if (viewctl.scrolling_mode == VALUE_SCROLLING) { - UX_DISPLAY(viewexpl_bagl_valuescrolling, viewexpl_bagl_prepro); - } else { - UX_DISPLAY(viewexpl_bagl_keyscrolling, viewexpl_bagl_prepro); - } -#endif -} - -void viewexpl_start(int start_page, - viewctl_delegate_getData ehUpdate, - viewctl_delegate_ready ehReady, - viewctl_delegate_exit ehExit) { - viewctl_start(start_page, ehUpdate, ehReady, ehExit, viewexpl_display_ux); -} From 4d76445e130330190c06f5c3e909cf7022002aaa Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 3 Sep 2019 19:10:22 +0200 Subject: [PATCH 12/78] Fixing paging --- src/lib/parser.c | 17 ++++++++++++----- src/lib/tx_display.c | 14 +++++++------- src/lib/tx_display.h | 6 +++--- src/lib/tx_parser.c | 13 +++---------- src/tx.c | 17 +++++++++-------- 5 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/lib/parser.c b/src/lib/parser.c index 7e7e45fb..479bec49 100644 --- a/src/lib/parser.c +++ b/src/lib/parser.c @@ -15,7 +15,7 @@ ********************************************************************************/ #include -#include +#include "zxmacros.h" #include "parser.h" #include "cosmos.h" @@ -89,7 +89,7 @@ parser_error_t parser_validate() { } uint8_t parser_getNumItems(parser_context_t *ctx) { - return tx_display_num_pages(); + return tx_display_numItems(); } parser_error_t parser_getItem(parser_context_t *ctx, @@ -103,9 +103,16 @@ parser_error_t parser_getItem(parser_context_t *ctx, parser_error_t err = parser_ok; - INIT_QUERY(outKey, outKeyLen, outValue, outValueLen, displayIdx) - * pageCount = tx_display_get_item(pageIdx); - tx_display_make_friendly(); + INIT_QUERY(outKey, outKeyLen, outValue, outValueLen, pageIdx) + + int16_t ret = tx_display_get_item(displayIdx); + + if (ret < 0 || ret > 255) { + return parser_unexpected_buffer_end; + } + + *pageCount = ret; +// tx_display_make_friendly(); return err; } diff --git a/src/lib/tx_display.c b/src/lib/tx_display.c index b40dd0c1..75fe60db 100644 --- a/src/lib/tx_display.c +++ b/src/lib/tx_display.c @@ -148,7 +148,7 @@ void tx_display_index_root() { } // Clear values - display_cache.num_pages = 0; + display_cache.numItems = 0; memset(display_cache.num_subpages, 0, NUM_REQUIRED_ROOT_PAGES); memset(display_cache.subroot_start_token, TX_TOKEN_NOT_FOUND, NUM_REQUIRED_ROOT_PAGES); @@ -183,7 +183,7 @@ void tx_display_index_root() { tx_ctx.query.item_index++; } }; - display_cache.num_pages += display_cache.num_subpages[idx]; + display_cache.numItems += display_cache.num_subpages[idx]; if (display_cache.num_subpages[idx] == 0) { break; @@ -193,13 +193,13 @@ void tx_display_index_root() { parsing_context.cache_valid = 1; } -int16_t tx_display_num_pages() { +int16_t tx_display_numItems() { tx_display_index_root(); - return display_cache.num_pages; + return display_cache.numItems; } // This function assumes that the tx_ctx has been set properly -int16_t tx_display_get_item(uint16_t page_index) { +int16_t tx_display_get_item(uint16_t itemIndex) { if (!parsing_context.cache_valid) { return ERR_MUST_INDEX_FIRST; } @@ -207,13 +207,13 @@ int16_t tx_display_get_item(uint16_t page_index) { // TODO: Verify it has been properly set? tx_ctx.query.out_key[0] = 0; tx_ctx.query.out_val[0] = 0; - if (page_index < 0 || page_index >= display_cache.num_pages) { + if (itemIndex < 0 || itemIndex >= display_cache.numItems) { return -1; } tx_ctx.query.item_index = 0; uint16_t root_index = 0; - for (uint16_t i = 0; i < page_index; i++) { + for (uint16_t i = 0; i < itemIndex; i++) { tx_ctx.query.item_index++; if (tx_ctx.query.item_index >= display_cache.num_subpages[root_index]) { tx_ctx.query.item_index = 0; diff --git a/src/lib/tx_display.h b/src/lib/tx_display.h index 6beb587c..412d1841 100644 --- a/src/lib/tx_display.h +++ b/src/lib/tx_display.h @@ -36,7 +36,7 @@ typedef struct { } key_subst_t; typedef struct { - int16_t num_pages; + int16_t numItems; int16_t subroot_start_token[NUM_REQUIRED_ROOT_PAGES]; uint8_t num_subpages[NUM_REQUIRED_ROOT_PAGES]; } display_cache_t; @@ -50,11 +50,11 @@ void tx_display_index_root(); /// This is the main function called from ledger that updates key and value strings /// that are going to be displayed in the UI. /// This function assumes that the tx_ctx has been properly set -int16_t tx_display_get_item(uint16_t page_index); +int16_t tx_display_get_item(uint16_t itemIndex); /// Return number of UI pages that we'll have for the current json transaction (only if the tx is valid) /// \return number of pages (msg pages + 5 required) -int16_t tx_display_num_pages(); +int16_t tx_display_numItems(); /// Apply postprocessing rules to key and values void tx_display_make_friendly(); diff --git a/src/lib/tx_parser.c b/src/lib/tx_parser.c index cffd81d5..093c0621 100644 --- a/src/lib/tx_parser.c +++ b/src/lib/tx_parser.c @@ -18,14 +18,7 @@ #include #include "tx_parser.h" #include "json_parser.h" - -#if defined(TARGET_NANOX) || defined(TARGET_NANOS) -#include "os.h" -#define COPYFUNC os_memmove -#else -#define COPYFUNC memcpy -#define __always_inline -#endif +#include "zxmacros.h" // Global context to save memory / stack space in recursive calls parsing_context_t parsing_context; @@ -52,7 +45,7 @@ __always_inline void strcat_chunk_s(char *dst, uint16_t dst_max, const char *src if (src_chunk_size > 0) { // Check bounds - COPYFUNC(dst + prev_size, src_chunk, src_chunk_size); + MEMCPY(dst + prev_size, src_chunk, src_chunk_size); // terminate *(dst + prev_size + src_chunk_size) = 0; } @@ -83,7 +76,7 @@ __always_inline int16_t tx_get_value(const int16_t token_index) { if (chunk_len > tx_ctx.query.out_val_len - 1) { chunk_len = tx_ctx.query.out_val_len - 1; } - COPYFUNC(tx_ctx.query.out_val, parsing_context.tx + chunk_start, chunk_len); + MEMCPY(tx_ctx.query.out_val, parsing_context.tx + chunk_start, chunk_len); tx_ctx.query.out_val[chunk_len] = 0; return num_chunks; diff --git a/src/tx.c b/src/tx.c index 28cb4653..bfb29a09 100644 --- a/src/tx.c +++ b/src/tx.c @@ -106,14 +106,6 @@ tx_error_t tx_getItem(int8_t displayIdx, outValue, outValueLen, pageIdx, pageCount); - if (*pageCount > 1) { - uint8_t keyLen = strlen(outKey); - if (keyLen < outKeyLen) { - // FIXME: Improve Key Trimming - snprintf(outKey + keyLen, outKeyLen - keyLen, " [%d/%d]", pageIdx + 1, *pageCount); - } - } - // Convert error codes if (err == parser_no_data) return tx_no_data; @@ -121,5 +113,14 @@ tx_error_t tx_getItem(int8_t displayIdx, if (err == parser_ok) return tx_no_error; + if (*pageCount > 1) { + uint8_t keyLen = strlen(outKey); + if (keyLen < outKeyLen) { + // FIXME: Improve Key Trimming + snprintf(outKey + keyLen, outKeyLen - keyLen, + " [%d/%d]", pageIdx + 1, *pageCount); + } + } + return err; } From a746dad61d906c6fcdbc8dc7018e8337713f122b Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 3 Sep 2019 19:43:35 +0200 Subject: [PATCH 13/78] fixes --- src/app_main.c | 2 +- src/lib/parser.c | 26 +++++++++++++++----------- src/lib/parser.h | 2 +- src/lib/parser_impl.h | 5 +++++ src/tx.c | 4 +++- 5 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/app_main.c b/src/app_main.c index 8b57711e..1e908446 100644 --- a/src/app_main.c +++ b/src/app_main.c @@ -200,7 +200,7 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { int error_msg_length = strlen(error_msg); os_memmove(G_io_apdu_buffer, error_msg, error_msg_length); *tx += (error_msg_length); - THROW(APDU_CODE_DATA_INVALID); + THROW(APDU_CODE_BAD_KEY_HANDLE); } view_sign_show(); diff --git a/src/lib/parser.c b/src/lib/parser.c index 479bec49..d3e5e087 100644 --- a/src/lib/parser.c +++ b/src/lib/parser.c @@ -15,12 +15,8 @@ ********************************************************************************/ #include -#include "zxmacros.h" -#include "parser.h" -#include "cosmos.h" #include "lib/json_parser.h" -#include "lib/tx_validate.h" #include "lib/tx_parser.h" #include "lib/tx_display.h" @@ -34,11 +30,10 @@ parser_error_t parser_parse(parser_context_t *ctx, uint8_t *data, uint16_t dataLen) { - lastErrorMessage = json_parse_s(&parsed_transaction, (const char *) data, dataLen); - if (lastErrorMessage != NULL) { - return parser_extended_error; - } - lastErrorMessage = json_validate(&parsed_transaction, (const char *) data); + ctx->data = (const char *) data; + ctx->dataLen = dataLen; + + lastErrorMessage = json_parse_s(&parsed_transaction, ctx->data, ctx->dataLen); if (lastErrorMessage != NULL) { return parser_extended_error; } @@ -84,7 +79,13 @@ const char *parser_getErrorDescription(parser_error_t err) { } } -parser_error_t parser_validate() { +parser_error_t parser_validate(parser_context_t *ctx) { + lastErrorMessage = json_validate(&parsed_transaction, ctx->data); + + if (lastErrorMessage != NULL) { + return parser_extended_error; + } + return parser_ok; } @@ -112,7 +113,10 @@ parser_error_t parser_getItem(parser_context_t *ctx, } *pageCount = ret; -// tx_display_make_friendly(); + + if (ctx->flags.make_friendly) { + tx_display_make_friendly(); + } return err; } diff --git a/src/lib/parser.h b/src/lib/parser.h index 2d88a7bb..5449c18c 100644 --- a/src/lib/parser.h +++ b/src/lib/parser.h @@ -29,7 +29,7 @@ parser_error_t parser_parse(parser_context_t *ctx, uint8_t *data, uint16_t dataLen); //// verifies tx fields -parser_error_t parser_validate(); +parser_error_t parser_validate(parser_context_t *ctx); //// returns the number of items in the current parsing context uint8_t parser_getNumItems(parser_context_t *ctx); diff --git a/src/lib/parser_impl.h b/src/lib/parser_impl.h index 083544a1..d558d47b 100644 --- a/src/lib/parser_impl.h +++ b/src/lib/parser_impl.h @@ -37,6 +37,11 @@ typedef enum { } parser_error_t; typedef struct { + const char *data; + uint16_t dataLen; + struct { + unsigned make_friendly :1; + } flags; } parser_context_t; #ifdef __cplusplus diff --git a/src/tx.c b/src/tx.c index bfb29a09..5d2e2acb 100644 --- a/src/tx.c +++ b/src/tx.c @@ -73,6 +73,8 @@ uint8_t *tx_get_buffer() { } const char *tx_parse() { + ctx_parsed_tx.flags.make_friendly = 1; + uint8_t err = parser_parse( &ctx_parsed_tx, tx_get_buffer(), @@ -82,7 +84,7 @@ const char *tx_parse() { return parser_getErrorDescription(err); } - err = parser_validate(); + err = parser_validate(&ctx_parsed_tx); if (err != parser_ok) { return parser_getErrorDescription(err); } From 79f50d60b61d502f62415f73d2de393fb2bab4a7 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 1 Oct 2019 16:14:56 +0200 Subject: [PATCH 14/78] refactoring parsing. step 1 --- src/lib/{ => json}/json_parser.c | 34 ++++++------ src/lib/{ => json}/json_parser.h | 16 ++---- src/lib/{ => json}/tx_display.c | 57 ++++++++++---------- src/lib/{ => json}/tx_display.h | 2 +- src/lib/{ => json}/tx_parser.c | 84 ++++++++++++++--------------- src/lib/{ => json}/tx_parser.h | 45 ++++------------ src/lib/{ => json}/tx_validate.c | 52 +++++++++--------- src/lib/{ => json}/tx_validate.h | 2 +- src/lib/parser.c | 80 ++++++--------------------- src/lib/parser.h | 3 +- src/lib/parser_common.h | 49 +++++++++++++++++ src/lib/parser_impl.c | 93 ++++++++++++++++++++++++++++++++ src/lib/parser_impl.h | 31 ++++------- src/lib/parser_txdef.h | 55 +++++++++++++++++++ src/tx.c | 6 +-- 15 files changed, 353 insertions(+), 256 deletions(-) rename src/lib/{ => json}/json_parser.c (84%) rename src/lib/{ => json}/json_parser.h (91%) rename src/lib/{ => json}/tx_display.c (80%) rename src/lib/{ => json}/tx_display.h (98%) rename src/lib/{ => json}/tx_parser.c (61%) rename src/lib/{ => json}/tx_parser.h (59%) rename src/lib/{ => json}/tx_validate.c (71%) rename src/lib/{ => json}/tx_validate.h (91%) create mode 100644 src/lib/parser_common.h create mode 100644 src/lib/parser_impl.c create mode 100644 src/lib/parser_txdef.h diff --git a/src/lib/json_parser.c b/src/lib/json/json_parser.c similarity index 84% rename from src/lib/json_parser.c rename to src/lib/json/json_parser.c index 5672390b..c384f993 100644 --- a/src/lib/json_parser.c +++ b/src/lib/json/json_parser.c @@ -61,7 +61,7 @@ const char *json_parse_s(parsed_json_t *parsed_json, // Parsing error if (num_tokens <= 0) { - return "Unknown parser error"; + return "No tokens. Unknown parser error"; } // We cannot support if number of tokens exceeds the limit @@ -75,21 +75,21 @@ const char *json_parse_s(parsed_json_t *parsed_json, } uint16_t array_get_element_count(uint16_t array_token_index, - const parsed_json_t *parsed_transaction) { - if (array_token_index < 0 || array_token_index > parsed_transaction->NumberOfTokens) { + const parsed_json_t *json) { + if (array_token_index < 0 || array_token_index > json->NumberOfTokens) { return 0; } - jsmntok_t array_token = parsed_transaction->Tokens[array_token_index]; + jsmntok_t array_token = json->Tokens[array_token_index]; uint16_t token_index = array_token_index; uint16_t element_count = 0; uint16_t prev_element_end = array_token.start; while (true) { token_index++; - if (token_index >= parsed_transaction->NumberOfTokens) { + if (token_index >= json->NumberOfTokens) { break; } - jsmntok_t current_token = parsed_transaction->Tokens[token_index]; + jsmntok_t current_token = json->Tokens[token_index]; if (current_token.start > array_token.end) { break; } @@ -105,21 +105,21 @@ uint16_t array_get_element_count(uint16_t array_token_index, int16_t array_get_nth_element(uint16_t array_token_index, uint16_t element_index, - const parsed_json_t *parsed_transaction) { - if (array_token_index < 0 || array_token_index > parsed_transaction->NumberOfTokens) { + const parsed_json_t *json) { + if (array_token_index < 0 || array_token_index > json->NumberOfTokens) { return -1; } - jsmntok_t array_token = parsed_transaction->Tokens[array_token_index]; + jsmntok_t array_token = json->Tokens[array_token_index]; uint16_t token_index = array_token_index; uint16_t element_count = 0; uint16_t prev_element_end = array_token.start; while (true) { token_index++; - if (token_index >= parsed_transaction->NumberOfTokens) { + if (token_index >= json->NumberOfTokens) { break; } - jsmntok_t current_token = parsed_transaction->Tokens[token_index]; + jsmntok_t current_token = json->Tokens[token_index]; if (current_token.start > array_token.end) { break; } @@ -137,22 +137,22 @@ int16_t array_get_nth_element(uint16_t array_token_index, } uint16_t object_get_element_count(uint16_t object_token_index, - const parsed_json_t *parsed_transaction) { - if (object_token_index < 0 || object_token_index > parsed_transaction->NumberOfTokens) { + const parsed_json_t *json) { + if (object_token_index < 0 || object_token_index > json->NumberOfTokens) { return 0; } - jsmntok_t object_token = parsed_transaction->Tokens[object_token_index]; + jsmntok_t object_token = json->Tokens[object_token_index]; uint16_t token_index = object_token_index; uint16_t element_count = 0; uint16_t prev_element_end = object_token.start; token_index++; while (true) { - if (token_index >= parsed_transaction->NumberOfTokens) { + if (token_index >= json->NumberOfTokens) { break; } - jsmntok_t key_token = parsed_transaction->Tokens[token_index++]; - jsmntok_t value_token = parsed_transaction->Tokens[token_index]; + jsmntok_t key_token = json->Tokens[token_index++]; + jsmntok_t value_token = json->Tokens[token_index]; if (key_token.start > object_token.end) { break; } diff --git a/src/lib/json_parser.h b/src/lib/json/json_parser.h similarity index 91% rename from src/lib/json_parser.h rename to src/lib/json/json_parser.h index d050dd98..55b9d0ac 100644 --- a/src/lib/json_parser.h +++ b/src/lib/json/json_parser.h @@ -55,14 +55,6 @@ typedef struct { /// \param void reset_parsed_json(parsed_json_t *); -typedef struct { - const parsed_json_t *parsed_tx; - uint16_t max_chars_per_key_line; - uint16_t max_chars_per_value_line; - const char *tx; - uint8_t cache_valid; -} parsing_context_t; - //--------------------------------------------- // NEW JSON PARSER CODE @@ -84,19 +76,19 @@ const char *json_parse(parsed_json_t *parsed_json, /// Get the number of elements in the array /// \param array_token_index -/// \param parsed_transaction +/// \param json /// \return number of elements uint16_t array_get_element_count(uint16_t array_token_index, - const parsed_json_t *parsed_transaction); + const parsed_json_t *json); /// Get the token index of the nth array's element /// \param array_token_index /// \param element_index -/// \param parsed_transaction +/// \param json /// \return returns the token index or -1 if not found int16_t array_get_nth_element(uint16_t array_token_index, uint16_t element_index, - const parsed_json_t *parsed_transaction); + const parsed_json_t *json); /// Get the number of dictionary elements (key/value pairs) under given object /// \param object_token_index: token index of the parent object diff --git a/src/lib/tx_display.c b/src/lib/json/tx_display.c similarity index 80% rename from src/lib/tx_display.c rename to src/lib/json/tx_display.c index 75fe60db..49c3b3d0 100644 --- a/src/lib/tx_display.c +++ b/src/lib/json/tx_display.c @@ -18,7 +18,8 @@ #include #include "tx_parser.h" #include "tx_display.h" -#include "json_parser.h" +#include "json/json_parser.h" +#include "lib/parser_impl.h" #if defined(TARGET_NANOS) || defined(TARGET_NANOX) #include "os.h" @@ -143,7 +144,7 @@ display_cache_t *tx_display_cache() { } void tx_display_index_root() { - if (parsing_context.cache_valid) { + if (parser_tx_obj.cache_valid) { return; } @@ -157,8 +158,8 @@ void tx_display_index_root() { for (int8_t idx = 0; idx < NUM_REQUIRED_ROOT_PAGES; idx++) { const int16_t subroot_token_idx = object_get_value(ROOT_TOKEN_INDEX, get_required_root_item(idx), - parsing_context.parsed_tx, - parsing_context.tx); + &parser_tx_obj.json, + parser_tx_obj.tx); if (subroot_token_idx < 0) { break; } @@ -169,18 +170,18 @@ void tx_display_index_root() { char tmp_key[2]; char tmp_val[2]; INIT_QUERY_CONTEXT(tmp_key, sizeof(tmp_key), tmp_val, sizeof(tmp_val), 0, root_max_level[idx]) - STRNCPY_S(tx_ctx.query.out_key, get_required_root_item(idx), tx_ctx.query.out_key_len); - tx_ctx.max_depth = MAX_RECURSION_DEPTH; - tx_ctx.query.item_index = 0; + STRNCPY_S(parser_tx_obj.tx_ctx.query.out_key, get_required_root_item(idx), parser_tx_obj.tx_ctx.query.out_key_len); + parser_tx_obj.tx_ctx.max_depth = MAX_RECURSION_DEPTH; + parser_tx_obj.tx_ctx.query.item_index = 0; found = 0; while (found >= 0) { - tx_ctx.item_index_current = 0; + parser_tx_obj.tx_ctx.item_index_current = 0; found = tx_traverse(subroot_token_idx); if (found >= 0) { display_cache.num_subpages[idx]++; - tx_ctx.query.item_index++; + parser_tx_obj.tx_ctx.query.item_index++; } }; display_cache.numItems += display_cache.num_subpages[idx]; @@ -190,7 +191,7 @@ void tx_display_index_root() { } } - parsing_context.cache_valid = 1; + parser_tx_obj.cache_valid = 1; } int16_t tx_display_numItems() { @@ -200,32 +201,34 @@ int16_t tx_display_numItems() { // This function assumes that the tx_ctx has been set properly int16_t tx_display_get_item(uint16_t itemIndex) { - if (!parsing_context.cache_valid) { + if (!parser_tx_obj.cache_valid) { return ERR_MUST_INDEX_FIRST; } // TODO: Verify it has been properly set? - tx_ctx.query.out_key[0] = 0; - tx_ctx.query.out_val[0] = 0; + parser_tx_obj.tx_ctx.query.out_key[0] = 0; + parser_tx_obj.tx_ctx.query.out_val[0] = 0; if (itemIndex < 0 || itemIndex >= display_cache.numItems) { return -1; } - tx_ctx.query.item_index = 0; + parser_tx_obj.tx_ctx.query.item_index = 0; uint16_t root_index = 0; for (uint16_t i = 0; i < itemIndex; i++) { - tx_ctx.query.item_index++; - if (tx_ctx.query.item_index >= display_cache.num_subpages[root_index]) { - tx_ctx.query.item_index = 0; + parser_tx_obj.tx_ctx.query.item_index++; + if (parser_tx_obj.tx_ctx.query.item_index >= display_cache.num_subpages[root_index]) { + parser_tx_obj.tx_ctx.query.item_index = 0; root_index++; } } - tx_ctx.item_index_current = 0; - tx_ctx.max_level = root_max_level[root_index]; - tx_ctx.max_depth = MAX_RECURSION_DEPTH; + parser_tx_obj.tx_ctx.item_index_current = 0; + parser_tx_obj.tx_ctx.max_level = root_max_level[root_index]; + parser_tx_obj.tx_ctx.max_depth = MAX_RECURSION_DEPTH; - STRNCPY_S(tx_ctx.query.out_key, get_required_root_item(root_index), tx_ctx.query.out_key_len); + STRNCPY_S(parser_tx_obj.tx_ctx.query.out_key, + get_required_root_item(root_index), + parser_tx_obj.tx_ctx.query.out_key_len); int16_t ret = tx_traverse(display_cache.subroot_start_token[root_index]); @@ -235,19 +238,19 @@ int16_t tx_display_get_item(uint16_t itemIndex) { void tx_display_make_friendly() { // post process keys for (int8_t i = 0; i < NUM_KEY_SUBSTITUTIONS; i++) { - if (!strcmp(tx_ctx.query.out_key, key_substitutions[i].str1)) { - STRNCPY_S(tx_ctx.query.out_key, + if (!strcmp(parser_tx_obj.tx_ctx.query.out_key, key_substitutions[i].str1)) { + STRNCPY_S(parser_tx_obj.tx_ctx.query.out_key, key_substitutions[i].str2, - tx_ctx.query.out_key_len); + parser_tx_obj.tx_ctx.query.out_key_len); break; } } for (int8_t i = 0; i < NUM_VALUE_SUBSTITUTIONS; i++) { - if (!strcmp(tx_ctx.query.out_val, value_substitutions[i].str1)) { - STRNCPY_S(tx_ctx.query.out_val, + if (!strcmp(parser_tx_obj.tx_ctx.query.out_val, value_substitutions[i].str1)) { + STRNCPY_S(parser_tx_obj.tx_ctx.query.out_val, value_substitutions[i].str2, - tx_ctx.query.out_val_len); + parser_tx_obj.tx_ctx.query.out_val_len); break; } } diff --git a/src/lib/tx_display.h b/src/lib/json/tx_display.h similarity index 98% rename from src/lib/tx_display.h rename to src/lib/json/tx_display.h index 412d1841..ff5e8cdb 100644 --- a/src/lib/tx_display.h +++ b/src/lib/json/tx_display.h @@ -17,7 +17,7 @@ #pragma once #include -#include "json_parser.h" +#include "json/json_parser.h" #include "tx_parser.h" #ifdef __cplusplus diff --git a/src/lib/tx_parser.c b/src/lib/json/tx_parser.c similarity index 61% rename from src/lib/tx_parser.c rename to src/lib/json/tx_parser.c index 093c0621..c225bec4 100644 --- a/src/lib/tx_parser.c +++ b/src/lib/json/tx_parser.c @@ -17,18 +17,9 @@ #include #include #include "tx_parser.h" -#include "json_parser.h" +#include "json/json_parser.h" #include "zxmacros.h" - -// Global context to save memory / stack space in recursive calls -parsing_context_t parsing_context; -tx_context_t tx_ctx; - -void set_parsing_context(parsing_context_t context) { - parsing_context = context; - // reset cached values - parsing_context.cache_valid = false; -} +#include "parser_impl.h" // strcat but source does not need to be terminated (a chunk from a bigger string is concatenated) // dst_max is measured in bytes including the space for NULL termination @@ -53,103 +44,106 @@ __always_inline void strcat_chunk_s(char *dst, uint16_t dst_max, const char *src __always_inline int16_t tx_get_value(const int16_t token_index) { - const int16_t token_start = parsing_context.parsed_tx->Tokens[token_index].start; - const int16_t token_end = parsing_context.parsed_tx->Tokens[token_index].end; + const int16_t token_start = parser_tx_obj.json.Tokens[token_index].start; + const int16_t token_end = parser_tx_obj.json.Tokens[token_index].end; const int16_t token_len = token_end - token_start; - int16_t num_chunks = (token_len / (tx_ctx.query.out_val_len - 1)) + 1; - if (token_len > 0 && (token_len % (tx_ctx.query.out_val_len - 1) == 0)) + int16_t num_chunks = (token_len / (parser_tx_obj.tx_ctx.query.out_val_len - 1)) + 1; + if (token_len > 0 && (token_len % (parser_tx_obj.tx_ctx.query.out_val_len - 1) == 0)) num_chunks--; - tx_ctx.query.out_val[0] = '\0'; // flush - if (tx_ctx.query.chunk_index >= num_chunks) { + parser_tx_obj.tx_ctx.query.out_val[0] = '\0'; // flush + if (parser_tx_obj.tx_ctx.query.chunk_index >= num_chunks) { return TX_TOKEN_NOT_FOUND; } - const int16_t chunk_start = token_start + tx_ctx.query.chunk_index * (tx_ctx.query.out_val_len - 1); + const int16_t chunk_start = + token_start + parser_tx_obj.tx_ctx.query.chunk_index * (parser_tx_obj.tx_ctx.query.out_val_len - 1); int16_t chunk_len = token_end - chunk_start; if (chunk_len < 0) { return TX_TOKEN_NOT_FOUND; } - if (chunk_len > tx_ctx.query.out_val_len - 1) { - chunk_len = tx_ctx.query.out_val_len - 1; + if (chunk_len > parser_tx_obj.tx_ctx.query.out_val_len - 1) { + chunk_len = parser_tx_obj.tx_ctx.query.out_val_len - 1; } - MEMCPY(tx_ctx.query.out_val, parsing_context.tx + chunk_start, chunk_len); - tx_ctx.query.out_val[chunk_len] = 0; + MEMCPY(parser_tx_obj.tx_ctx.query.out_val, parser_tx_obj.tx + chunk_start, chunk_len); + parser_tx_obj.tx_ctx.query.out_val[chunk_len] = 0; return num_chunks; } ///// Update key characters from json transaction read from the token_index element. __always_inline void append_key_item(int16_t token_index) { - if (*tx_ctx.query.out_key > 0) { + if (*parser_tx_obj.tx_ctx.query.out_key > 0) { // There is already something there, add separator - strcat_chunk_s(tx_ctx.query.out_key, tx_ctx.query.out_key_len, "/", 1); + strcat_chunk_s(parser_tx_obj.tx_ctx.query.out_key, + parser_tx_obj.tx_ctx.query.out_key_len, "/", 1); } - const int16_t token_start = parsing_context.parsed_tx->Tokens[token_index].start; - const int16_t token_end = parsing_context.parsed_tx->Tokens[token_index].end; - const char *address_ptr = parsing_context.tx + token_start; + const int16_t token_start = parser_tx_obj.json.Tokens[token_index].start; + const int16_t token_end = parser_tx_obj.json.Tokens[token_index].end; + const char *address_ptr = parser_tx_obj.tx + token_start; const int16_t new_item_size = token_end - token_start; - strcat_chunk_s(tx_ctx.query.out_key, tx_ctx.query.out_key_len, address_ptr, new_item_size); + strcat_chunk_s(parser_tx_obj.tx_ctx.query.out_key, + parser_tx_obj.tx_ctx.query.out_key_len, address_ptr, new_item_size); } int16_t tx_traverse(int16_t root_token_index) { - const jsmntype_t token_type = parsing_context.parsed_tx->Tokens[root_token_index].type; + const jsmntype_t token_type = parser_tx_obj.json.Tokens[root_token_index].type; - if (tx_ctx.max_level <= 0 || tx_ctx.max_depth <= 0 || + if (parser_tx_obj.tx_ctx.max_level <= 0 || parser_tx_obj.tx_ctx.max_depth <= 0 || token_type == JSMN_STRING || token_type == JSMN_PRIMITIVE) { // Early bail out - if (tx_ctx.item_index_current == tx_ctx.query.item_index) { + if (parser_tx_obj.tx_ctx.item_index_current == parser_tx_obj.tx_ctx.query.item_index) { return tx_get_value(root_token_index); } - tx_ctx.item_index_current++; + parser_tx_obj.tx_ctx.item_index_current++; return TX_TOKEN_NOT_FOUND; } - const int16_t el_count = object_get_element_count(root_token_index, parsing_context.parsed_tx); + const int16_t el_count = object_get_element_count(root_token_index, &parser_tx_obj.json); int16_t num_chunks = TX_TOKEN_NOT_FOUND; switch (token_type) { case JSMN_OBJECT: { - const int16_t key_len = strlen(tx_ctx.query.out_key); + const int16_t key_len = strlen(parser_tx_obj.tx_ctx.query.out_key); for (int16_t i = 0; i < el_count; ++i) { - const int16_t key_index = object_get_nth_key(root_token_index, i, parsing_context.parsed_tx); - const int16_t value_index = object_get_nth_value(root_token_index, i, parsing_context.parsed_tx); + const int16_t key_index = object_get_nth_key(root_token_index, i, &parser_tx_obj.json); + const int16_t value_index = object_get_nth_value(root_token_index, i, &parser_tx_obj.json); // Skip writing keys if we are actually exploring to count - if (tx_ctx.query.item_index != TX_TOKEN_NOT_FOUND) { + if (parser_tx_obj.tx_ctx.query.item_index != TX_TOKEN_NOT_FOUND) { append_key_item(key_index); } // When traversing objects both level and depth should be considered - tx_ctx.max_level--; - tx_ctx.max_depth--; + parser_tx_obj.tx_ctx.max_level--; + parser_tx_obj.tx_ctx.max_depth--; num_chunks = tx_traverse(value_index); // Traverse the value, extracting subkeys - tx_ctx.max_level++; - tx_ctx.max_depth++; + parser_tx_obj.tx_ctx.max_level++; + parser_tx_obj.tx_ctx.max_depth++; if (num_chunks != TX_TOKEN_NOT_FOUND) { break; } - *(tx_ctx.query.out_key + key_len) = 0; + *(parser_tx_obj.tx_ctx.query.out_key + key_len) = 0; } break; } case JSMN_ARRAY: { for (int16_t i = 0; i < el_count; ++i) { - const int16_t element_index = array_get_nth_element(root_token_index, i, parsing_context.parsed_tx); + const int16_t element_index = array_get_nth_element(root_token_index, i, &parser_tx_obj.json); // When iterating along an array, the level does not change but we need to count the recursion - tx_ctx.max_depth--; + parser_tx_obj.tx_ctx.max_depth--; num_chunks = tx_traverse(element_index); - tx_ctx.max_depth++; + parser_tx_obj.tx_ctx.max_depth++; if (num_chunks != TX_TOKEN_NOT_FOUND) { break; diff --git a/src/lib/tx_parser.h b/src/lib/json/tx_parser.h similarity index 59% rename from src/lib/tx_parser.h rename to src/lib/json/tx_parser.h index 3bf3cead..0df3f36a 100644 --- a/src/lib/tx_parser.h +++ b/src/lib/json/tx_parser.h @@ -16,7 +16,7 @@ #pragma once -#include "json_parser.h" +#include "json/json_parser.h" #include #ifdef __cplusplus @@ -27,48 +27,27 @@ extern "C" { #define MAX_RECURSION_DEPTH 6 -typedef struct { - int16_t item_index; // ?? - int16_t chunk_index; // ?? - - char *out_key; // ?? - int16_t out_key_len; // ?? - char *out_val; // ?? - int16_t out_val_len; // ?? -} tx_query_t; - -// used to reduce stack size usage in recursive calls -typedef struct { - tx_query_t query; // ?? - int16_t item_index_current; // ?? - uint8_t max_level; - uint8_t max_depth; -} tx_context_t; - -extern parsing_context_t parsing_context; -extern tx_context_t tx_ctx; - #define INIT_QUERY_CONTEXT(_KEY, _KEY_LEN, _VAL, _VAL_LEN, _CHUNK_IDX, _MAX_LEVEL) \ INIT_QUERY(_KEY, _KEY_LEN, _VAL, _VAL_LEN, _CHUNK_IDX) \ - tx_ctx.item_index_current = 0; \ - tx_ctx.max_depth = MAX_RECURSION_DEPTH; \ - tx_ctx.max_level = _MAX_LEVEL; + parser_tx_obj.tx_ctx.item_index_current = 0; \ + parser_tx_obj.tx_ctx.max_depth = MAX_RECURSION_DEPTH; \ + parser_tx_obj.tx_ctx.max_level = _MAX_LEVEL; #define INIT_QUERY(_KEY, _KEY_LEN, _VAL, _VAL_LEN, _CHUNK_IDX) \ _KEY[0] = 0; \ _VAL[0] = 0; \ - tx_ctx.query.out_key=_KEY; \ - tx_ctx.query.out_val=_VAL; \ - tx_ctx.query.out_key_len = _KEY_LEN; \ - tx_ctx.query.out_val_len = _VAL_LEN; \ - tx_ctx.query.item_index= 0; \ - tx_ctx.query.chunk_index = _CHUNK_IDX; + parser_tx_obj.tx_ctx.query.out_key=_KEY; \ + parser_tx_obj.tx_ctx.query.out_val=_VAL; \ + parser_tx_obj.tx_ctx.query.out_key_len = _KEY_LEN; \ + parser_tx_obj.tx_ctx.query.out_val_len = _VAL_LEN; \ + parser_tx_obj.tx_ctx.query.item_index= 0; \ + parser_tx_obj.tx_ctx.query.chunk_index = _CHUNK_IDX; /// Validate json transaction /// \param parsed_transacton /// \param transaction /// \return -const char *json_validate(parsed_json_t *parsed_transaction, const char *transaction); +const char *tx_validate(parsed_json_t *json, const char *transaction); // Traverses transaction data and fills tx_context // \return -1 if the item was not found or the number of available chunks for this item @@ -77,8 +56,6 @@ int16_t tx_traverse(int16_t root_token_index); // Retrieves the value for the corresponding token index. If the value goes beyond val_len, the chunk_idx will be used int16_t tx_get_value(int16_t token_index); -void set_parsing_context(parsing_context_t context); - //--------------------------------------------- #ifdef __cplusplus diff --git a/src/lib/tx_validate.c b/src/lib/json/tx_validate.c similarity index 71% rename from src/lib/tx_validate.c rename to src/lib/json/tx_validate.c index 8cabe23c..f62a3c6f 100644 --- a/src/lib/tx_validate.c +++ b/src/lib/json/tx_validate.c @@ -17,7 +17,7 @@ #include #include #include "tx_parser.h" -#include "json_parser.h" +#include "json/json_parser.h" const char whitespaces[] = { 0x20,// space ' ' @@ -37,20 +37,20 @@ int8_t is_space(char c) { return 0; } -int8_t contains_whitespace(parsed_json_t *parsed_transaction, const char *transaction) { +int8_t contains_whitespace(parsed_json_t *json, const char *transaction) { int start = 0; - const int last_element_index = parsed_transaction->Tokens[0].end; + const int last_element_index = json->Tokens[0].end; // Starting at token 1 because token 0 contains full tx - for (int i = 1; i < parsed_transaction->NumberOfTokens; i++) { - if (parsed_transaction->Tokens[i].type != JSMN_UNDEFINED) { - const int end = parsed_transaction->Tokens[i].start; + for (int i = 1; i < json->NumberOfTokens; i++) { + if (json->Tokens[i].type != JSMN_UNDEFINED) { + const int end = json->Tokens[i].start; for (int j = start; j < end; j++) { if (is_space(transaction[j]) == 1) { return 1; } } - start = parsed_transaction->Tokens[i].end + 1; + start = json->Tokens[i].end + 1; } else { return 0; } @@ -65,7 +65,7 @@ int8_t contains_whitespace(parsed_json_t *parsed_transaction, const char *transa } int8_t is_sorted(int16_t first_index, int16_t second_index, - parsed_json_t *parsed_transaction, + parsed_json_t *json, const char *transaction) { #if DEBUG_SORTING char first[256]; @@ -79,24 +79,24 @@ int8_t is_sorted(int16_t first_index, int16_t second_index, second[size] = '\0'; #endif - if (strcmp(transaction + parsed_transaction->Tokens[first_index].start, - transaction + parsed_transaction->Tokens[second_index].start) <= 0) { + if (strcmp(transaction + json->Tokens[first_index].start, + transaction + json->Tokens[second_index].start) <= 0) { return 1; } return 0; } -int8_t dictionaries_sorted(parsed_json_t *parsed_transaction, +int8_t dictionaries_sorted(parsed_json_t *json, const char *transaction) { - for (int i = 0; i < parsed_transaction->NumberOfTokens; i++) { - if (parsed_transaction->Tokens[i].type == JSMN_OBJECT) { + for (int i = 0; i < json->NumberOfTokens; i++) { + if (json->Tokens[i].type == JSMN_OBJECT) { - const int count = object_get_element_count(i, parsed_transaction); + const int count = object_get_element_count(i, json); if (count > 1) { - int prev_token_index = object_get_nth_key(i, 0, parsed_transaction); + int prev_token_index = object_get_nth_key(i, 0, json); for (int j = 1; j < count; j++) { - int next_token_index = object_get_nth_key(i, j, parsed_transaction); - if (!is_sorted(prev_token_index, next_token_index, parsed_transaction, transaction)) { + int next_token_index = object_get_nth_key(i, j, json); + if (!is_sorted(prev_token_index, next_token_index, json, transaction)) { return 0; } prev_token_index = next_token_index; @@ -107,53 +107,53 @@ int8_t dictionaries_sorted(parsed_json_t *parsed_transaction, return 1; } -const char *json_validate(parsed_json_t *parsed_transaction, const char *transaction) { - if (contains_whitespace(parsed_transaction, transaction) == 1) { +const char *tx_validate(parsed_json_t *json, const char *transaction) { + if (contains_whitespace(json, transaction) == 1) { return "JSON Contains whitespace in the corpus"; } - if (dictionaries_sorted(parsed_transaction, transaction) != 1) { + if (dictionaries_sorted(json, transaction) != 1) { return "JSON Dictionaries are not sorted"; } if (object_get_value(0, "chain_id", - parsed_transaction, + json, transaction) == -1) { return "JSON Missing chain_id"; } if (object_get_value(0, "sequence", - parsed_transaction, + json, transaction) == -1) { return "JSON Missing sequence"; } if (object_get_value(0, "fee", - parsed_transaction, + json, transaction) == -1) { return "JSON Missing fee"; } if (object_get_value(0, "msgs", - parsed_transaction, + json, transaction) == -1) { return "JSON Missing msgs"; } if (object_get_value(0, "account_number", - parsed_transaction, + json, transaction) == -1) { return "JSON Missing account_number"; } if (object_get_value(0, "memo", - parsed_transaction, + json, transaction) == -1) { return "JSON Missing memo"; } diff --git a/src/lib/tx_validate.h b/src/lib/json/tx_validate.h similarity index 91% rename from src/lib/tx_validate.h rename to src/lib/json/tx_validate.h index 73e598f4..de52f324 100644 --- a/src/lib/tx_validate.h +++ b/src/lib/json/tx_validate.h @@ -27,7 +27,7 @@ extern "C" { /// \param parsed_transacton /// \param transaction /// \return -const char *json_validate(parsed_json_t *parsed_transaction, const char *transaction); +const char *tx_validate(parsed_json_t *json, const char *transaction); #ifdef __cplusplus } diff --git a/src/lib/parser.c b/src/lib/parser.c index d3e5e087..ce8d46c4 100644 --- a/src/lib/parser.c +++ b/src/lib/parser.c @@ -15,77 +15,23 @@ ********************************************************************************/ #include - -#include "lib/json_parser.h" -#include "lib/tx_parser.h" -#include "lib/tx_display.h" - +#include "json/tx_parser.h" +#include "json/tx_display.h" #include "lib/parser_impl.h" #include "view_internal.h" -parsed_json_t parsed_transaction; -const char *lastErrorMessage = NULL; - parser_error_t parser_parse(parser_context_t *ctx, - uint8_t *data, + const uint8_t *data, uint16_t dataLen) { - - ctx->data = (const char *) data; - ctx->dataLen = dataLen; - - lastErrorMessage = json_parse_s(&parsed_transaction, ctx->data, ctx->dataLen); - if (lastErrorMessage != NULL) { - return parser_extended_error; - } - - parsing_context_t context; - context.tx = (const char *) data; - context.max_chars_per_key_line = MAX_CHARS_PER_KEY_LINE; - context.max_chars_per_value_line = MAX_CHARS_PER_VALUE_LINE; - context.parsed_tx = &parsed_transaction; - - set_parsing_context(context); - tx_display_index_root(); - - return parser_ok; -} - -const char *parser_getErrorDescription(parser_error_t err) { - switch (err) { - case parser_ok: - return "No error"; - case parser_no_data: - return "No more data"; - case parser_extended_error: - if (lastErrorMessage != NULL) - return lastErrorMessage; - return "Unknown message"; - case parser_unexpected_buffer_end: - return "Unexpected buffer end"; - case parser_unexpected_wire_type: - return "Unexpected wire type"; - case parser_unexpected_version: - return "Unexpected version"; - case parser_unexpected_characters: - return "Unexpected characters"; - case parser_unexpected_field: - return "Unexpected field"; - case parser_duplicated_field: - return "Unexpected duplicated field"; - case parser_unexpected_chain: - return "Unexpected chain"; - default: - return "Unrecognized error code"; - } + parser_init(ctx, data, dataLen); + return _readTx(ctx, &parser_tx_obj); } parser_error_t parser_validate(parser_context_t *ctx) { - lastErrorMessage = json_validate(&parsed_transaction, ctx->data); - + lastErrorMessage = tx_validate(&parser_tx_obj.json, (const char *) ctx->buffer); if (lastErrorMessage != NULL) { return parser_extended_error; } - return parser_ok; } @@ -104,19 +50,23 @@ parser_error_t parser_getItem(parser_context_t *ctx, parser_error_t err = parser_ok; - INIT_QUERY(outKey, outKeyLen, outValue, outValueLen, pageIdx) + if (displayIdx < 0) { + return parser_no_data; + } + INIT_QUERY(outKey, outKeyLen, outValue, outValueLen, pageIdx) int16_t ret = tx_display_get_item(displayIdx); + if (ret == TX_TOKEN_NOT_FOUND) { + return parser_no_data; + } + if (ret < 0 || ret > 255) { return parser_unexpected_buffer_end; } *pageCount = ret; - - if (ctx->flags.make_friendly) { - tx_display_make_friendly(); - } + tx_display_make_friendly(); return err; } diff --git a/src/lib/parser.h b/src/lib/parser.h index 5449c18c..33418362 100644 --- a/src/lib/parser.h +++ b/src/lib/parser.h @@ -26,7 +26,8 @@ const char *parser_getErrorDescription(parser_error_t err); //// parses a tx buffer parser_error_t parser_parse(parser_context_t *ctx, - uint8_t *data, uint16_t dataLen); + const uint8_t *data, + uint16_t dataLen); //// verifies tx fields parser_error_t parser_validate(parser_context_t *ctx); diff --git a/src/lib/parser_common.h b/src/lib/parser_common.h new file mode 100644 index 00000000..7b20650e --- /dev/null +++ b/src/lib/parser_common.h @@ -0,0 +1,49 @@ +/******************************************************************************* +* (c) 2019 ZondaX GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef enum { + parser_ok = 0, + parser_no_data = 1, + parser_extended_error = 2, + parser_unexpected_buffer_end = 3, + parser_unexpected_wire_type = 4, + parser_unexpected_version = 5, + parser_unexpected_characters = 6, + parser_unexpected_field = 7, + parser_duplicated_field = 8, + parser_value_out_of_range = 9, + parser_unexpected_chain = 10, +} parser_error_t; + +extern const char *lastErrorMessage; + +typedef struct { + const uint8_t *buffer; + uint16_t bufferSize; + uint16_t offset; +} parser_context_t; + +#ifdef __cplusplus +} +#endif diff --git a/src/lib/parser_impl.c b/src/lib/parser_impl.c new file mode 100644 index 00000000..b72edc6a --- /dev/null +++ b/src/lib/parser_impl.c @@ -0,0 +1,93 @@ +/******************************************************************************* +* (c) 2019 ZondaX GmbH +* +* 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 "parser_impl.h" +#include "json/tx_display.h" + +parser_tx_t parser_tx_obj; +const char *lastErrorMessage = NULL; + +parser_error_t parser_init_context(parser_context_t *ctx, + const uint8_t *buffer, + uint16_t bufferSize) { + ctx->offset = 0; + + if (bufferSize == 0 || buffer == NULL) { + // Not available, use defaults + ctx->buffer = NULL; + ctx->bufferSize = 0; + return parser_no_data; + } + + ctx->buffer = buffer; + ctx->bufferSize = bufferSize; + + return parser_ok; +} + +parser_error_t parser_init(parser_context_t *ctx, const uint8_t *buffer, uint16_t bufferSize) { + parser_error_t err = parser_init_context(ctx, buffer, bufferSize); + if (err != parser_ok) + return err; + return err; +} + +const char *parser_getErrorDescription(parser_error_t err) { + switch (err) { + case parser_ok: + return "No error"; + case parser_no_data: + return "No more data"; + case parser_extended_error: + if (lastErrorMessage != NULL) + return lastErrorMessage; + return "Unknown message"; + case parser_unexpected_buffer_end: + return "Unexpected buffer end"; + case parser_unexpected_wire_type: + return "Unexpected wire type"; + case parser_unexpected_version: + return "Unexpected version"; + case parser_unexpected_characters: + return "Unexpected characters"; + case parser_unexpected_field: + return "Unexpected field"; + case parser_duplicated_field: + return "Unexpected duplicated field"; + case parser_unexpected_chain: + return "Unexpected chain"; + default: + return "Unrecognized error code"; + } +} + +parser_error_t _readTx(parser_context_t *c, parser_tx_t *v) { + lastErrorMessage = json_parse_s(&parser_tx_obj.json, (const char *) c->buffer, c->bufferSize); + if (lastErrorMessage != NULL) { + return parser_extended_error; + } + + parser_tx_obj.tx = (const char *) c->buffer; +// context.max_chars_per_key_line = MAX_CHARS_PER_KEY_LINE; +// context.max_chars_per_value_line = MAX_CHARS_PER_VALUE_LINE; + parser_tx_obj.max_chars_per_key_line = 39; + parser_tx_obj.max_chars_per_value_line = 39; + parser_tx_obj.cache_valid = 0; + + tx_display_index_root(); + + return parser_ok; +} diff --git a/src/lib/parser_impl.h b/src/lib/parser_impl.h index d558d47b..29cee8e5 100644 --- a/src/lib/parser_impl.h +++ b/src/lib/parser_impl.h @@ -15,34 +15,21 @@ ********************************************************************************/ #pragma once +#include "parser_common.h" +#include "json/json_parser.h" +#include "parser_txdef.h" + #ifdef __cplusplus extern "C" { #endif -#include -#include +extern parser_tx_t parser_tx_obj; -typedef enum { - parser_ok = 0, - parser_no_data = 1, - parser_extended_error = 2, - parser_unexpected_buffer_end = 3, - parser_unexpected_wire_type = 4, - parser_unexpected_version = 5, - parser_unexpected_characters = 6, - parser_unexpected_field = 7, - parser_duplicated_field = 8, - parser_value_out_of_range = 9, - parser_unexpected_chain = 10, -} parser_error_t; +parser_error_t parser_init(parser_context_t *ctx, + const uint8_t *buffer, + uint16_t bufferSize); -typedef struct { - const char *data; - uint16_t dataLen; - struct { - unsigned make_friendly :1; - } flags; -} parser_context_t; +parser_error_t _readTx(parser_context_t *c, parser_tx_t *v); #ifdef __cplusplus } diff --git a/src/lib/parser_txdef.h b/src/lib/parser_txdef.h new file mode 100644 index 00000000..11ddd66a --- /dev/null +++ b/src/lib/parser_txdef.h @@ -0,0 +1,55 @@ +/******************************************************************************* +* (c) 2019 ZondaX GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef struct { + int16_t item_index; // ?? + int16_t chunk_index; // ?? + + char *out_key; // ?? + int16_t out_key_len; // ?? + char *out_val; // ?? + int16_t out_val_len; // ?? +} tx_query_t; + +typedef struct { + tx_query_t query; // ?? + int16_t item_index_current; // ?? + uint8_t max_level; + uint8_t max_depth; +} tx_context_t; + +typedef struct { + parsed_json_t json; + + uint16_t max_chars_per_key_line; + uint16_t max_chars_per_value_line; + const char *tx; + uint8_t cache_valid; + + tx_context_t tx_ctx; +} parser_tx_t; + +#ifdef __cplusplus +} +#endif diff --git a/src/tx.c b/src/tx.c index 5d2e2acb..a359c398 100644 --- a/src/tx.c +++ b/src/tx.c @@ -73,8 +73,6 @@ uint8_t *tx_get_buffer() { } const char *tx_parse() { - ctx_parsed_tx.flags.make_friendly = 1; - uint8_t err = parser_parse( &ctx_parsed_tx, tx_get_buffer(), @@ -118,9 +116,7 @@ tx_error_t tx_getItem(int8_t displayIdx, if (*pageCount > 1) { uint8_t keyLen = strlen(outKey); if (keyLen < outKeyLen) { - // FIXME: Improve Key Trimming - snprintf(outKey + keyLen, outKeyLen - keyLen, - " [%d/%d]", pageIdx + 1, *pageCount); + snprintf(outKey + keyLen, outKeyLen - keyLen, " [%d/%d]", pageIdx + 1, *pageCount); } } From f17472182cc2fe7549c9b34c0250558a4b277cd7 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 1 Oct 2019 18:27:14 +0200 Subject: [PATCH 15/78] unifying json parsing --- src/lib/json/json_parser.c | 82 +++++++++++----------- src/lib/json/json_parser.h | 14 ++-- src/lib/json/tx_display.c | 136 ++++++++++++++++++------------------- src/lib/json/tx_parser.c | 10 +-- src/lib/json/tx_validate.c | 68 +++++++++---------- 5 files changed, 154 insertions(+), 156 deletions(-) diff --git a/src/lib/json/json_parser.c b/src/lib/json/json_parser.c index c384f993..27f07fb4 100644 --- a/src/lib/json/json_parser.c +++ b/src/lib/json/json_parser.c @@ -18,10 +18,10 @@ #include "json_parser.h" #if defined(TARGET_NANOS) || defined(TARGET_NANOX) - #include "os.h" - #define EQUALS(_P, _Q, _LEN) (os_memcmp( PIC(_P), PIC(_Q), (_LEN))==0) +#include "os.h" +#define EQUALS(_P, _Q, _LEN) (os_memcmp( PIC(_P), PIC(_Q), (_LEN))==0) #else - #define EQUALS(_P, _Q, _LEN) (memcmp( (_P), (_Q), (_LEN))==0) +#define EQUALS(_P, _Q, _LEN) (memcmp( (_P), (_Q), (_LEN))==0) #endif void reset_parsed_json(parsed_json_t *parser_data) { @@ -41,11 +41,11 @@ const char *json_parse_s(parsed_json_t *parsed_json, reset_parsed_json(parsed_json); int num_tokens = jsmn_parse( - &parser, - transaction, - transaction_length, - parsed_json->Tokens, - MAX_NUMBER_OF_TOKENS); + &parser, + transaction, + transaction_length, + parsed_json->tokens, + MAX_NUMBER_OF_TOKENS); switch (num_tokens) { case JSMN_ERROR_NOMEM: @@ -56,8 +56,8 @@ const char *json_parse_s(parsed_json_t *parsed_json, return "JSON string is not complete"; } - parsed_json->NumberOfTokens = 0; - parsed_json->IsValid = 0; + parsed_json->numberOfTokens = 0; + parsed_json->isValid = 0; // Parsing error if (num_tokens <= 0) { @@ -69,27 +69,27 @@ const char *json_parse_s(parsed_json_t *parsed_json, return "TOK: JSON string contains too many tokens"; } - parsed_json->NumberOfTokens = num_tokens; - parsed_json->IsValid = true; + parsed_json->numberOfTokens = num_tokens; + parsed_json->isValid = true; return NULL; } uint16_t array_get_element_count(uint16_t array_token_index, const parsed_json_t *json) { - if (array_token_index < 0 || array_token_index > json->NumberOfTokens) { + if (array_token_index < 0 || array_token_index > json->numberOfTokens) { return 0; } - jsmntok_t array_token = json->Tokens[array_token_index]; + jsmntok_t array_token = json->tokens[array_token_index]; uint16_t token_index = array_token_index; uint16_t element_count = 0; uint16_t prev_element_end = array_token.start; while (true) { token_index++; - if (token_index >= json->NumberOfTokens) { + if (token_index >= json->numberOfTokens) { break; } - jsmntok_t current_token = json->Tokens[token_index]; + jsmntok_t current_token = json->tokens[token_index]; if (current_token.start > array_token.end) { break; } @@ -106,20 +106,20 @@ uint16_t array_get_element_count(uint16_t array_token_index, int16_t array_get_nth_element(uint16_t array_token_index, uint16_t element_index, const parsed_json_t *json) { - if (array_token_index < 0 || array_token_index > json->NumberOfTokens) { + if (array_token_index < 0 || array_token_index > json->numberOfTokens) { return -1; } - jsmntok_t array_token = json->Tokens[array_token_index]; + jsmntok_t array_token = json->tokens[array_token_index]; uint16_t token_index = array_token_index; uint16_t element_count = 0; uint16_t prev_element_end = array_token.start; while (true) { token_index++; - if (token_index >= json->NumberOfTokens) { + if (token_index >= json->numberOfTokens) { break; } - jsmntok_t current_token = json->Tokens[token_index]; + jsmntok_t current_token = json->tokens[token_index]; if (current_token.start > array_token.end) { break; } @@ -138,21 +138,21 @@ int16_t array_get_nth_element(uint16_t array_token_index, uint16_t object_get_element_count(uint16_t object_token_index, const parsed_json_t *json) { - if (object_token_index < 0 || object_token_index > json->NumberOfTokens) { + if (object_token_index < 0 || object_token_index > json->numberOfTokens) { return 0; } - jsmntok_t object_token = json->Tokens[object_token_index]; + jsmntok_t object_token = json->tokens[object_token_index]; uint16_t token_index = object_token_index; uint16_t element_count = 0; uint16_t prev_element_end = object_token.start; token_index++; while (true) { - if (token_index >= json->NumberOfTokens) { + if (token_index >= json->numberOfTokens) { break; } - jsmntok_t key_token = json->Tokens[token_index++]; - jsmntok_t value_token = json->Tokens[token_index]; + jsmntok_t key_token = json->tokens[token_index++]; + jsmntok_t value_token = json->tokens[token_index]; if (key_token.start > object_token.end) { break; } @@ -169,21 +169,21 @@ uint16_t object_get_element_count(uint16_t object_token_index, int16_t object_get_nth_key(uint16_t object_token_index, uint16_t object_element_index, const parsed_json_t *parsed_transaction) { - if (object_token_index < 0 || object_token_index > parsed_transaction->NumberOfTokens) { + if (object_token_index < 0 || object_token_index > parsed_transaction->numberOfTokens) { return -1; } - jsmntok_t object_token = parsed_transaction->Tokens[object_token_index]; + jsmntok_t object_token = parsed_transaction->tokens[object_token_index]; uint16_t token_index = object_token_index; uint16_t element_count = 0; uint16_t prev_element_end = object_token.start; token_index++; while (true) { - if (token_index >= parsed_transaction->NumberOfTokens) { + if (token_index >= parsed_transaction->numberOfTokens) { break; } - jsmntok_t key_token = parsed_transaction->Tokens[token_index++]; - jsmntok_t value_token = parsed_transaction->Tokens[token_index]; + jsmntok_t key_token = parsed_transaction->tokens[token_index++]; + jsmntok_t value_token = parsed_transaction->tokens[token_index]; if (key_token.start > object_token.end) { break; } @@ -203,7 +203,7 @@ int16_t object_get_nth_key(uint16_t object_token_index, int16_t object_get_nth_value(uint16_t object_token_index, uint16_t object_element_index, const parsed_json_t *parsed_transaction) { - if (object_token_index < 0 || object_token_index > parsed_transaction->NumberOfTokens) { + if (object_token_index < 0 || object_token_index > parsed_transaction->numberOfTokens) { return -1; } @@ -214,24 +214,24 @@ int16_t object_get_nth_value(uint16_t object_token_index, return -1; } -int16_t object_get_value(uint16_t object_token_index, - const char *key_name, - const parsed_json_t *parsed_transaction, - const char *transaction) { - if (object_token_index < 0 || object_token_index > parsed_transaction->NumberOfTokens) { +int16_t object_get_value(const parsed_json_t *parsed_transaction, + const char *transaction, + uint16_t object_token_index, + const char *key_name) { + if (object_token_index < 0 || object_token_index > parsed_transaction->numberOfTokens) { return -1; } - const jsmntok_t object_token = parsed_transaction->Tokens[object_token_index]; + const jsmntok_t object_token = parsed_transaction->tokens[object_token_index]; int token_index = object_token_index; int prev_element_end = object_token.start; token_index++; - while (token_index < parsed_transaction->NumberOfTokens) { - const jsmntok_t key_token = parsed_transaction->Tokens[token_index]; + while (token_index < parsed_transaction->numberOfTokens) { + const jsmntok_t key_token = parsed_transaction->tokens[token_index]; token_index++; - const jsmntok_t value_token = parsed_transaction->Tokens[token_index]; + const jsmntok_t value_token = parsed_transaction->tokens[token_index]; if (key_token.start > object_token.end) { break; @@ -241,7 +241,7 @@ int16_t object_get_value(uint16_t object_token_index, } prev_element_end = value_token.end; - if ( ((uint16_t) strlen(key_name)) == (key_token.end - key_token.start)) { + if (((uint16_t) strlen(key_name)) == (key_token.end - key_token.start)) { if (EQUALS(key_name, transaction + key_token.start, key_token.end - key_token.start)) { return token_index; } diff --git a/src/lib/json/json_parser.h b/src/lib/json/json_parser.h index 55b9d0ac..04af54e6 100644 --- a/src/lib/json/json_parser.h +++ b/src/lib/json/json_parser.h @@ -46,9 +46,11 @@ extern "C" { // - parsed json tokens // - re-created SendMsg struct with indices pointing to tokens in parsed json typedef struct { - int8_t IsValid; - uint16_t NumberOfTokens; - jsmntok_t Tokens[MAX_NUMBER_OF_TOKENS]; + int8_t isValid; + uint16_t numberOfTokens; + jsmntok_t tokens[MAX_NUMBER_OF_TOKENS]; + uint8_t buffer; + uint16_t bufferLen; } parsed_json_t; /// Resets parsed_json data structure @@ -121,10 +123,8 @@ int16_t object_get_nth_value(uint16_t object_token_index, /// \param parsed_transaction /// \param transaction /// \return returns token index or -1 if not found -int16_t object_get_value(uint16_t object_token_index, - const char *key_name, - const parsed_json_t *parsed_transaction, - const char *transaction); +int16_t object_get_value(const parsed_json_t *parsed_transaction, const char *transaction, uint16_t object_token_index, + const char *key_name); #ifdef __cplusplus } diff --git a/src/lib/json/tx_display.c b/src/lib/json/tx_display.c index 49c3b3d0..4d9cf639 100644 --- a/src/lib/json/tx_display.c +++ b/src/lib/json/tx_display.c @@ -28,7 +28,6 @@ #endif // Required pages -// FIXME: the required root items have been moved to a function due to PIC issues. Refactor and fix const char *get_required_root_item(uint8_t i) { switch (i) { case 0: @@ -58,79 +57,79 @@ const char *get_required_root_item(uint8_t i) { //}; static const uint16_t root_max_level[NUM_REQUIRED_ROOT_PAGES] = { - 2, // "chain_id", - 2, // "account_number", - 2, // "sequence", - 1, // "fee", - 2, // "memo" - 2, // "msgs" + 2, // "chain_id", + 2, // "account_number", + 2, // "sequence", + 1, // "fee", + 2, // "memo" + 2, // "msgs" }; static const key_subst_t key_substitutions[NUM_KEY_SUBSTITUTIONS] = { - {"chain_id", "Chain ID"}, - {"account_number", "Account"}, - {"sequence", "Sequence"}, - {"memo", "Memo"}, - {"fee/amount", "Fee"}, - {"fee/gas", "Gas"}, - {"msgs/type", "Type"}, - - // FIXME: Are these obsolete?? multisend? - {"msgs/inputs/address", "Source Address"}, - {"msgs/inputs/coins", "Source Coins"}, - {"msgs/outputs/address", "Dest Address"}, - {"msgs/outputs/coins", "Dest Coins"}, - - // MsgSend - {"msgs/value/from_address", "From"}, - {"msgs/value/to_address", "To"}, - {"msgs/value/amount", "Amount"}, - - // MsgDelegate - {"msgs/value/delegator_address", "Delegator"}, - {"msgs/value/validator_address", "Validator"}, - - // MsgUndelegate + {"chain_id", "Chain ID"}, + {"account_number", "Account"}, + {"sequence", "Sequence"}, + {"memo", "Memo"}, + {"fee/amount", "Fee"}, + {"fee/gas", "Gas"}, + {"msgs/type", "Type"}, + + // FIXME: Are these obsolete?? multisend? + {"msgs/inputs/address", "Source Address"}, + {"msgs/inputs/coins", "Source Coins"}, + {"msgs/outputs/address", "Dest Address"}, + {"msgs/outputs/coins", "Dest Coins"}, + + // MsgSend + {"msgs/value/from_address", "From"}, + {"msgs/value/to_address", "To"}, + {"msgs/value/amount", "Amount"}, + + // MsgDelegate + {"msgs/value/delegator_address", "Delegator"}, + {"msgs/value/validator_address", "Validator"}, + + // MsgUndelegate // {"msgs/value/delegator_address", "Delegator"}, // {"msgs/value/validator_address", "Validator"}, - // MsgBeginRedelegate + // MsgBeginRedelegate // {"msgs/value/delegator_address", "Delegator"}, - {"msgs/value/validator_src_address", "Validator Source"}, - {"msgs/value/validator_dst_address", "Validator Dest"}, - - // MsgSubmitProposal - {"msgs/value/description", "Description"}, - {"msgs/value/initial_deposit/amount", "Deposit Amount"}, - {"msgs/value/initial_deposit/denom", "Deposit Denom"}, - {"msgs/value/proposal_type", "Proposal"}, - {"msgs/value/proposer", "Proposer"}, - {"msgs/value/title", "Title"}, - - // MsgDeposit - {"msgs/value/depositer", "Sender"}, - {"msgs/value/proposal_id", "Proposal ID"}, - {"msgs/value/amount", "Amount"}, - - // MsgVote - {"msgs/value/voter", "Description"}, + {"msgs/value/validator_src_address", "Validator Source"}, + {"msgs/value/validator_dst_address", "Validator Dest"}, + + // MsgSubmitProposal + {"msgs/value/description", "Description"}, + {"msgs/value/initial_deposit/amount", "Deposit Amount"}, + {"msgs/value/initial_deposit/denom", "Deposit Denom"}, + {"msgs/value/proposal_type", "Proposal"}, + {"msgs/value/proposer", "Proposer"}, + {"msgs/value/title", "Title"}, + + // MsgDeposit + {"msgs/value/depositer", "Sender"}, + {"msgs/value/proposal_id", "Proposal ID"}, + {"msgs/value/amount", "Amount"}, + + // MsgVote + {"msgs/value/voter", "Description"}, // {"msgs/value/proposal_id", "Proposal ID"}, - {"msgs/value/option", "Option"}, + {"msgs/value/option", "Option"}, - // MsgWithdrawDelegationReward + // MsgWithdrawDelegationReward // {"msgs/value/delegator_address", "Delegator"}, // duplicated // {"msgs/value/validator_address", "Validator"}, // duplicated }; static const key_subst_t value_substitutions[NUM_VALUE_SUBSTITUTIONS] = { - {"cosmos-sdk/MsgSend", "Send"}, - {"cosmos-sdk/MsgDelegate", "Delegate"}, - {"cosmos-sdk/MsgUndelegate", "Undelegate"}, - {"cosmos-sdk/MsgBeginRedelegate", "Redelegate"}, - {"cosmos-sdk/MsgSubmitProposal", "Propose"}, - {"cosmos-sdk/MsgDeposit", "Deposit"}, - {"cosmos-sdk/MsgVote", "Vote"}, - {"cosmos-sdk/MsgWithdrawDelegationReward", "Withdraw Reward"}, + {"cosmos-sdk/MsgSend", "Send"}, + {"cosmos-sdk/MsgDelegate", "Delegate"}, + {"cosmos-sdk/MsgUndelegate", "Undelegate"}, + {"cosmos-sdk/MsgBeginRedelegate", "Redelegate"}, + {"cosmos-sdk/MsgSubmitProposal", "Propose"}, + {"cosmos-sdk/MsgDeposit", "Deposit"}, + {"cosmos-sdk/MsgVote", "Vote"}, + {"cosmos-sdk/MsgWithdrawDelegationReward", "Withdraw Reward"}, }; #define STRNCPY_S(DST, SRC, DST_SIZE) \ @@ -151,15 +150,15 @@ void tx_display_index_root() { // Clear values display_cache.numItems = 0; memset(display_cache.num_subpages, 0, NUM_REQUIRED_ROOT_PAGES); - memset(display_cache.subroot_start_token, TX_TOKEN_NOT_FOUND, NUM_REQUIRED_ROOT_PAGES); + memset(display_cache.subroot_start_token, TX_TOKEN_NOT_FOUND, NUM_REQUIRED_ROOT_PAGES); // FIXME: This is not clearing everything // Calculate pages int8_t found = 0; for (int8_t idx = 0; idx < NUM_REQUIRED_ROOT_PAGES; idx++) { - const int16_t subroot_token_idx = object_get_value(ROOT_TOKEN_INDEX, - get_required_root_item(idx), - &parser_tx_obj.json, - parser_tx_obj.tx); + const int16_t subroot_token_idx = object_get_value( + &parser_tx_obj.json, + parser_tx_obj.tx, ROOT_TOKEN_INDEX, + get_required_root_item(idx)); if (subroot_token_idx < 0) { break; } @@ -170,7 +169,8 @@ void tx_display_index_root() { char tmp_key[2]; char tmp_val[2]; INIT_QUERY_CONTEXT(tmp_key, sizeof(tmp_key), tmp_val, sizeof(tmp_val), 0, root_max_level[idx]) - STRNCPY_S(parser_tx_obj.tx_ctx.query.out_key, get_required_root_item(idx), parser_tx_obj.tx_ctx.query.out_key_len); + STRNCPY_S(parser_tx_obj.tx_ctx.query.out_key, get_required_root_item(idx), + parser_tx_obj.tx_ctx.query.out_key_len); parser_tx_obj.tx_ctx.max_depth = MAX_RECURSION_DEPTH; parser_tx_obj.tx_ctx.query.item_index = 0; @@ -227,8 +227,8 @@ int16_t tx_display_get_item(uint16_t itemIndex) { parser_tx_obj.tx_ctx.max_depth = MAX_RECURSION_DEPTH; STRNCPY_S(parser_tx_obj.tx_ctx.query.out_key, - get_required_root_item(root_index), - parser_tx_obj.tx_ctx.query.out_key_len); + get_required_root_item(root_index), + parser_tx_obj.tx_ctx.query.out_key_len); int16_t ret = tx_traverse(display_cache.subroot_start_token[root_index]); diff --git a/src/lib/json/tx_parser.c b/src/lib/json/tx_parser.c index c225bec4..5e094e41 100644 --- a/src/lib/json/tx_parser.c +++ b/src/lib/json/tx_parser.c @@ -44,8 +44,8 @@ __always_inline void strcat_chunk_s(char *dst, uint16_t dst_max, const char *src __always_inline int16_t tx_get_value(const int16_t token_index) { - const int16_t token_start = parser_tx_obj.json.Tokens[token_index].start; - const int16_t token_end = parser_tx_obj.json.Tokens[token_index].end; + const int16_t token_start = parser_tx_obj.json.tokens[token_index].start; + const int16_t token_end = parser_tx_obj.json.tokens[token_index].end; const int16_t token_len = token_end - token_start; int16_t num_chunks = (token_len / (parser_tx_obj.tx_ctx.query.out_val_len - 1)) + 1; @@ -82,8 +82,8 @@ __always_inline void append_key_item(int16_t token_index) { parser_tx_obj.tx_ctx.query.out_key_len, "/", 1); } - const int16_t token_start = parser_tx_obj.json.Tokens[token_index].start; - const int16_t token_end = parser_tx_obj.json.Tokens[token_index].end; + const int16_t token_start = parser_tx_obj.json.tokens[token_index].start; + const int16_t token_end = parser_tx_obj.json.tokens[token_index].end; const char *address_ptr = parser_tx_obj.tx + token_start; const int16_t new_item_size = token_end - token_start; @@ -92,7 +92,7 @@ __always_inline void append_key_item(int16_t token_index) { } int16_t tx_traverse(int16_t root_token_index) { - const jsmntype_t token_type = parser_tx_obj.json.Tokens[root_token_index].type; + const jsmntype_t token_type = parser_tx_obj.json.tokens[root_token_index].type; if (parser_tx_obj.tx_ctx.max_level <= 0 || parser_tx_obj.tx_ctx.max_depth <= 0 || token_type == JSMN_STRING || diff --git a/src/lib/json/tx_validate.c b/src/lib/json/tx_validate.c index f62a3c6f..baf94062 100644 --- a/src/lib/json/tx_validate.c +++ b/src/lib/json/tx_validate.c @@ -15,8 +15,6 @@ ********************************************************************************/ #include -#include -#include "tx_parser.h" #include "json/json_parser.h" const char whitespaces[] = { @@ -39,18 +37,18 @@ int8_t is_space(char c) { int8_t contains_whitespace(parsed_json_t *json, const char *transaction) { int start = 0; - const int last_element_index = json->Tokens[0].end; + const int last_element_index = json->tokens[0].end; // Starting at token 1 because token 0 contains full tx - for (int i = 1; i < json->NumberOfTokens; i++) { - if (json->Tokens[i].type != JSMN_UNDEFINED) { - const int end = json->Tokens[i].start; + for (int i = 1; i < json->numberOfTokens; i++) { + if (json->tokens[i].type != JSMN_UNDEFINED) { + const int end = json->tokens[i].start; for (int j = start; j < end; j++) { if (is_space(transaction[j]) == 1) { return 1; } } - start = json->Tokens[i].end + 1; + start = json->tokens[i].end + 1; } else { return 0; } @@ -79,8 +77,8 @@ int8_t is_sorted(int16_t first_index, int16_t second_index, second[size] = '\0'; #endif - if (strcmp(transaction + json->Tokens[first_index].start, - transaction + json->Tokens[second_index].start) <= 0) { + if (strcmp(transaction + json->tokens[first_index].start, + transaction + json->tokens[second_index].start) <= 0) { return 1; } return 0; @@ -88,8 +86,8 @@ int8_t is_sorted(int16_t first_index, int16_t second_index, int8_t dictionaries_sorted(parsed_json_t *json, const char *transaction) { - for (int i = 0; i < json->NumberOfTokens; i++) { - if (json->Tokens[i].type == JSMN_OBJECT) { + for (int i = 0; i < json->numberOfTokens; i++) { + if (json->tokens[i].type == JSMN_OBJECT) { const int count = object_get_element_count(i, json); if (count > 1) { @@ -116,45 +114,45 @@ const char *tx_validate(parsed_json_t *json, const char *transaction) { return "JSON Dictionaries are not sorted"; } - if (object_get_value(0, - "chain_id", - json, - transaction) == -1) { + if (object_get_value( + json, + transaction, 0, + "chain_id") == -1) { return "JSON Missing chain_id"; } - if (object_get_value(0, - "sequence", - json, - transaction) == -1) { + if (object_get_value( + json, + transaction, 0, + "sequence") == -1) { return "JSON Missing sequence"; } - if (object_get_value(0, - "fee", - json, - transaction) == -1) { + if (object_get_value( + json, + transaction, 0, + "fee") == -1) { return "JSON Missing fee"; } - if (object_get_value(0, - "msgs", - json, - transaction) == -1) { + if (object_get_value( + json, + transaction, 0, + "msgs") == -1) { return "JSON Missing msgs"; } - if (object_get_value(0, - "account_number", - json, - transaction) == -1) { + if (object_get_value( + json, + transaction, 0, + "account_number") == -1) { return "JSON Missing account_number"; } - if (object_get_value(0, - "memo", - json, - transaction) == -1) { + if (object_get_value( + json, + transaction, 0, + "memo") == -1) { return "JSON Missing memo"; } From 06a7f0f7f02bac40d055b3a09fb145d425c2a2a2 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 1 Oct 2019 19:23:25 +0200 Subject: [PATCH 16/78] reduce parameter passing --- src/lib/json/json_parser.c | 25 +++++++++++------------ src/lib/json/json_parser.h | 5 +++-- src/lib/json/tx_display.c | 2 +- src/lib/json/tx_parser.h | 6 ------ src/lib/json/tx_validate.c | 41 +++++++++++++++++++------------------- src/lib/json/tx_validate.h | 2 +- 6 files changed, 37 insertions(+), 44 deletions(-) diff --git a/src/lib/json/json_parser.c b/src/lib/json/json_parser.c index 27f07fb4..a872ddbc 100644 --- a/src/lib/json/json_parser.c +++ b/src/lib/json/json_parser.c @@ -15,6 +15,7 @@ ********************************************************************************/ #include +#include #include "json_parser.h" #if defined(TARGET_NANOS) || defined(TARGET_NANOX) @@ -24,26 +25,23 @@ #define EQUALS(_P, _Q, _LEN) (memcmp( (_P), (_Q), (_LEN))==0) #endif -void reset_parsed_json(parsed_json_t *parser_data) { - memset(parser_data, 0, sizeof(parsed_json_t)); -} - -const char *json_parse(parsed_json_t *parsed_json, const char *transaction) { - return json_parse_s(parsed_json, transaction, strlen(transaction)); +const char *json_parse(parsed_json_t *parsed_json, const char *buffer) { + return json_parse_s(parsed_json, buffer, strlen(buffer)); } const char *json_parse_s(parsed_json_t *parsed_json, - const char *transaction, - uint16_t transaction_length) { + const char *buffer, uint16_t bufferLen) { jsmn_parser parser; jsmn_init(&parser); - reset_parsed_json(parsed_json); + MEMSET(parsed_json, 0, sizeof(parsed_json_t)); + parsed_json->buffer = buffer; + parsed_json->bufferLen = bufferLen; int num_tokens = jsmn_parse( &parser, - transaction, - transaction_length, + parsed_json->buffer, + parsed_json->bufferLen, parsed_json->tokens, MAX_NUMBER_OF_TOKENS); @@ -215,7 +213,6 @@ int16_t object_get_nth_value(uint16_t object_token_index, } int16_t object_get_value(const parsed_json_t *parsed_transaction, - const char *transaction, uint16_t object_token_index, const char *key_name) { if (object_token_index < 0 || object_token_index > parsed_transaction->numberOfTokens) { @@ -242,7 +239,9 @@ int16_t object_get_value(const parsed_json_t *parsed_transaction, prev_element_end = value_token.end; if (((uint16_t) strlen(key_name)) == (key_token.end - key_token.start)) { - if (EQUALS(key_name, transaction + key_token.start, key_token.end - key_token.start)) { + if (EQUALS(key_name, + parsed_transaction->buffer + key_token.start, + key_token.end - key_token.start)) { return token_index; } } diff --git a/src/lib/json/json_parser.h b/src/lib/json/json_parser.h index 04af54e6..ac6e8a04 100644 --- a/src/lib/json/json_parser.h +++ b/src/lib/json/json_parser.h @@ -49,7 +49,7 @@ typedef struct { int8_t isValid; uint16_t numberOfTokens; jsmntok_t tokens[MAX_NUMBER_OF_TOKENS]; - uint8_t buffer; + const char *buffer; uint16_t bufferLen; } parsed_json_t; @@ -123,7 +123,8 @@ int16_t object_get_nth_value(uint16_t object_token_index, /// \param parsed_transaction /// \param transaction /// \return returns token index or -1 if not found -int16_t object_get_value(const parsed_json_t *parsed_transaction, const char *transaction, uint16_t object_token_index, +int16_t object_get_value(const parsed_json_t *parsed_transaction, + uint16_t object_token_index, const char *key_name); #ifdef __cplusplus diff --git a/src/lib/json/tx_display.c b/src/lib/json/tx_display.c index 4d9cf639..8b38240a 100644 --- a/src/lib/json/tx_display.c +++ b/src/lib/json/tx_display.c @@ -157,7 +157,7 @@ void tx_display_index_root() { for (int8_t idx = 0; idx < NUM_REQUIRED_ROOT_PAGES; idx++) { const int16_t subroot_token_idx = object_get_value( &parser_tx_obj.json, - parser_tx_obj.tx, ROOT_TOKEN_INDEX, + ROOT_TOKEN_INDEX, get_required_root_item(idx)); if (subroot_token_idx < 0) { break; diff --git a/src/lib/json/tx_parser.h b/src/lib/json/tx_parser.h index 0df3f36a..5167412d 100644 --- a/src/lib/json/tx_parser.h +++ b/src/lib/json/tx_parser.h @@ -43,12 +43,6 @@ extern "C" { parser_tx_obj.tx_ctx.query.item_index= 0; \ parser_tx_obj.tx_ctx.query.chunk_index = _CHUNK_IDX; -/// Validate json transaction -/// \param parsed_transacton -/// \param transaction -/// \return -const char *tx_validate(parsed_json_t *json, const char *transaction); - // Traverses transaction data and fills tx_context // \return -1 if the item was not found or the number of available chunks for this item int16_t tx_traverse(int16_t root_token_index); diff --git a/src/lib/json/tx_validate.c b/src/lib/json/tx_validate.c index baf94062..84bfd1f7 100644 --- a/src/lib/json/tx_validate.c +++ b/src/lib/json/tx_validate.c @@ -35,7 +35,7 @@ int8_t is_space(char c) { return 0; } -int8_t contains_whitespace(parsed_json_t *json, const char *transaction) { +int8_t contains_whitespace(parsed_json_t *json) { int start = 0; const int last_element_index = json->tokens[0].end; @@ -44,7 +44,7 @@ int8_t contains_whitespace(parsed_json_t *json, const char *transaction) { if (json->tokens[i].type != JSMN_UNDEFINED) { const int end = json->tokens[i].start; for (int j = start; j < end; j++) { - if (is_space(transaction[j]) == 1) { + if (is_space(json->buffer[j]) == 1) { return 1; } } @@ -53,8 +53,8 @@ int8_t contains_whitespace(parsed_json_t *json, const char *transaction) { return 0; } } - while (start <= last_element_index && transaction[start] != '\0') { - if (is_space(transaction[start])) { + while (start <= last_element_index && json->buffer[start] != '\0') { + if (is_space(json->buffer[start])) { return 1; } start++; @@ -62,9 +62,9 @@ int8_t contains_whitespace(parsed_json_t *json, const char *transaction) { return 0; } -int8_t is_sorted(int16_t first_index, int16_t second_index, - parsed_json_t *json, - const char *transaction) { +int8_t is_sorted(int16_t first_index, + int16_t second_index, + parsed_json_t *json) { #if DEBUG_SORTING char first[256]; char second[256]; @@ -77,15 +77,14 @@ int8_t is_sorted(int16_t first_index, int16_t second_index, second[size] = '\0'; #endif - if (strcmp(transaction + json->tokens[first_index].start, - transaction + json->tokens[second_index].start) <= 0) { + if (strcmp((const char *) (json->buffer+json->tokens[first_index].start), + (const char *) (json->buffer+json->tokens[second_index].start)) <= 0) { return 1; } return 0; } -int8_t dictionaries_sorted(parsed_json_t *json, - const char *transaction) { +int8_t dictionaries_sorted(parsed_json_t *json) { for (int i = 0; i < json->numberOfTokens; i++) { if (json->tokens[i].type == JSMN_OBJECT) { @@ -94,7 +93,7 @@ int8_t dictionaries_sorted(parsed_json_t *json, int prev_token_index = object_get_nth_key(i, 0, json); for (int j = 1; j < count; j++) { int next_token_index = object_get_nth_key(i, j, json); - if (!is_sorted(prev_token_index, next_token_index, json, transaction)) { + if (!is_sorted(prev_token_index, next_token_index, json)) { return 0; } prev_token_index = next_token_index; @@ -105,53 +104,53 @@ int8_t dictionaries_sorted(parsed_json_t *json, return 1; } -const char *tx_validate(parsed_json_t *json, const char *transaction) { - if (contains_whitespace(json, transaction) == 1) { +const char *tx_validate(parsed_json_t *json) { + if (contains_whitespace(json) == 1) { return "JSON Contains whitespace in the corpus"; } - if (dictionaries_sorted(json, transaction) != 1) { + if (dictionaries_sorted(json) != 1) { return "JSON Dictionaries are not sorted"; } if (object_get_value( json, - transaction, 0, + 0, "chain_id") == -1) { return "JSON Missing chain_id"; } if (object_get_value( json, - transaction, 0, + 0, "sequence") == -1) { return "JSON Missing sequence"; } if (object_get_value( json, - transaction, 0, + 0, "fee") == -1) { return "JSON Missing fee"; } if (object_get_value( json, - transaction, 0, + 0, "msgs") == -1) { return "JSON Missing msgs"; } if (object_get_value( json, - transaction, 0, + 0, "account_number") == -1) { return "JSON Missing account_number"; } if (object_get_value( json, - transaction, 0, + 0, "memo") == -1) { return "JSON Missing memo"; } diff --git a/src/lib/json/tx_validate.h b/src/lib/json/tx_validate.h index de52f324..068e083a 100644 --- a/src/lib/json/tx_validate.h +++ b/src/lib/json/tx_validate.h @@ -27,7 +27,7 @@ extern "C" { /// \param parsed_transacton /// \param transaction /// \return -const char *tx_validate(parsed_json_t *json, const char *transaction); +const char *tx_validate(parsed_json_t *json); #ifdef __cplusplus } From 16d5b28c943cf925c2c50fcd60312217effd35ea Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 1 Oct 2019 19:42:34 +0200 Subject: [PATCH 17/78] encapsulating display functionality --- src/lib/json/json_parser.h | 4 ---- src/lib/json/tx_display.c | 34 +++++++++++++++++++++------------- src/lib/json/tx_display.h | 19 ------------------- src/lib/parser.c | 5 ++++- src/lib/parser_impl.c | 6 ------ 5 files changed, 25 insertions(+), 43 deletions(-) diff --git a/src/lib/json/json_parser.h b/src/lib/json/json_parser.h index ac6e8a04..782584f7 100644 --- a/src/lib/json/json_parser.h +++ b/src/lib/json/json_parser.h @@ -53,10 +53,6 @@ typedef struct { uint16_t bufferLen; } parsed_json_t; -/// Resets parsed_json data structure -/// \param -void reset_parsed_json(parsed_json_t *); - //--------------------------------------------- // NEW JSON PARSER CODE diff --git a/src/lib/json/tx_display.c b/src/lib/json/tx_display.c index 8b38240a..87a036ec 100644 --- a/src/lib/json/tx_display.c +++ b/src/lib/json/tx_display.c @@ -65,6 +65,11 @@ static const uint16_t root_max_level[NUM_REQUIRED_ROOT_PAGES] = { 2, // "msgs" }; +typedef struct { + char str1[50]; + char str2[50]; +} key_subst_t; + static const key_subst_t key_substitutions[NUM_KEY_SUBSTITUTIONS] = { {"chain_id", "Chain ID"}, {"account_number", "Account"}, @@ -136,21 +141,21 @@ static const key_subst_t value_substitutions[NUM_VALUE_SUBSTITUTIONS] = { strncpy(DST, SRC, DST_SIZE - 1); \ DST[DST_SIZE - 1] = 0; -display_cache_t display_cache; +typedef struct { + int16_t numItems; + int16_t subroot_start_token[NUM_REQUIRED_ROOT_PAGES]; + uint8_t num_subpages[NUM_REQUIRED_ROOT_PAGES]; +} display_cache_t; -display_cache_t *tx_display_cache() { - return &display_cache; -} +display_cache_t display_cache; void tx_display_index_root() { if (parser_tx_obj.cache_valid) { return; } - // Clear values - display_cache.numItems = 0; - memset(display_cache.num_subpages, 0, NUM_REQUIRED_ROOT_PAGES); - memset(display_cache.subroot_start_token, TX_TOKEN_NOT_FOUND, NUM_REQUIRED_ROOT_PAGES); // FIXME: This is not clearing everything + // Clear cache + memset(&display_cache, 0, sizeof(display_cache_t)); // Calculate pages int8_t found = 0; @@ -168,9 +173,13 @@ void tx_display_index_root() { char tmp_key[2]; char tmp_val[2]; - INIT_QUERY_CONTEXT(tmp_key, sizeof(tmp_key), tmp_val, sizeof(tmp_val), 0, root_max_level[idx]) + INIT_QUERY_CONTEXT(tmp_key, sizeof(tmp_key), + tmp_val, sizeof(tmp_val), + 0, root_max_level[idx]) + STRNCPY_S(parser_tx_obj.tx_ctx.query.out_key, get_required_root_item(idx), parser_tx_obj.tx_ctx.query.out_key_len); + parser_tx_obj.tx_ctx.max_depth = MAX_RECURSION_DEPTH; parser_tx_obj.tx_ctx.query.item_index = 0; @@ -201,9 +210,7 @@ int16_t tx_display_numItems() { // This function assumes that the tx_ctx has been set properly int16_t tx_display_get_item(uint16_t itemIndex) { - if (!parser_tx_obj.cache_valid) { - return ERR_MUST_INDEX_FIRST; - } + tx_display_index_root(); // TODO: Verify it has been properly set? parser_tx_obj.tx_ctx.query.out_key[0] = 0; @@ -236,6 +243,8 @@ int16_t tx_display_get_item(uint16_t itemIndex) { } void tx_display_make_friendly() { + tx_display_index_root(); + // post process keys for (int8_t i = 0; i < NUM_KEY_SUBSTITUTIONS; i++) { if (!strcmp(parser_tx_obj.tx_ctx.query.out_key, key_substitutions[i].str1)) { @@ -254,6 +263,5 @@ void tx_display_make_friendly() { break; } } - } diff --git a/src/lib/json/tx_display.h b/src/lib/json/tx_display.h index ff5e8cdb..68fa924f 100644 --- a/src/lib/json/tx_display.h +++ b/src/lib/json/tx_display.h @@ -24,29 +24,10 @@ extern "C" { #endif -#define ERR_MUST_INDEX_FIRST -2 - #define NUM_REQUIRED_ROOT_PAGES 6 #define NUM_KEY_SUBSTITUTIONS 29 #define NUM_VALUE_SUBSTITUTIONS 8 -typedef struct { - char str1[50]; - char str2[50]; -} key_subst_t; - -typedef struct { - int16_t numItems; - int16_t subroot_start_token[NUM_REQUIRED_ROOT_PAGES]; - uint8_t num_subpages[NUM_REQUIRED_ROOT_PAGES]; -} display_cache_t; - -// This is only used for testing purposes -display_cache_t *tx_display_cache(); - -// This must be run before accessing items -void tx_display_index_root(); - /// This is the main function called from ledger that updates key and value strings /// that are going to be displayed in the UI. /// This function assumes that the tx_ctx has been properly set diff --git a/src/lib/parser.c b/src/lib/parser.c index ce8d46c4..b6e4d391 100644 --- a/src/lib/parser.c +++ b/src/lib/parser.c @@ -15,6 +15,7 @@ ********************************************************************************/ #include +#include #include "json/tx_parser.h" #include "json/tx_display.h" #include "lib/parser_impl.h" @@ -28,7 +29,7 @@ parser_error_t parser_parse(parser_context_t *ctx, } parser_error_t parser_validate(parser_context_t *ctx) { - lastErrorMessage = tx_validate(&parser_tx_obj.json, (const char *) ctx->buffer); + lastErrorMessage = tx_validate(&parser_tx_obj.json); if (lastErrorMessage != NULL) { return parser_extended_error; } @@ -47,6 +48,8 @@ parser_error_t parser_getItem(parser_context_t *ctx, snprintf(outKey, outKeyLen, "?"); snprintf(outValue, outValueLen, "?"); + parser_tx_obj.max_chars_per_key_line = outKeyLen; + parser_tx_obj.max_chars_per_value_line = outValueLen; parser_error_t err = parser_ok; diff --git a/src/lib/parser_impl.c b/src/lib/parser_impl.c index b72edc6a..b7f685d6 100644 --- a/src/lib/parser_impl.c +++ b/src/lib/parser_impl.c @@ -81,13 +81,7 @@ parser_error_t _readTx(parser_context_t *c, parser_tx_t *v) { } parser_tx_obj.tx = (const char *) c->buffer; -// context.max_chars_per_key_line = MAX_CHARS_PER_KEY_LINE; -// context.max_chars_per_value_line = MAX_CHARS_PER_VALUE_LINE; - parser_tx_obj.max_chars_per_key_line = 39; - parser_tx_obj.max_chars_per_value_line = 39; parser_tx_obj.cache_valid = 0; - tx_display_index_root(); - return parser_ok; } From 6b94b7c6c6b1638a2fc861d1e183bcdba36b5d94 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 1 Oct 2019 19:51:26 +0200 Subject: [PATCH 18/78] removing unnecessary indirection --- src/lib/crypto.c | 2 +- src/lib/json/json_parser.c | 2 +- src/lib/json/json_parser.h | 2 +- src/lib/json/tx_display.c | 235 +++++++++++++++++++------------------ src/lib/json/tx_display.h | 9 +- src/lib/json/tx_parser.c | 58 ++++----- src/lib/json/tx_parser.h | 20 ++-- src/lib/json/tx_validate.c | 2 +- src/lib/json/tx_validate.h | 2 +- src/lib/parser.c | 2 - src/lib/parser_txdef.h | 15 +-- 11 files changed, 172 insertions(+), 177 deletions(-) diff --git a/src/lib/crypto.c b/src/lib/crypto.c index e19c1ede..ea158c98 100644 --- a/src/lib/crypto.c +++ b/src/lib/crypto.c @@ -1,6 +1,6 @@ /******************************************************************************* +* (c) 2018, 2019 ZondaX GmbH * (c) 2016 Ledger -* (c) 2018 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/lib/json/json_parser.c b/src/lib/json/json_parser.c index a872ddbc..cbe74e23 100644 --- a/src/lib/json/json_parser.c +++ b/src/lib/json/json_parser.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) ZondaX GmbH +* (c) 2018, 2019 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/lib/json/json_parser.h b/src/lib/json/json_parser.h index 782584f7..8bb6ae06 100644 --- a/src/lib/json/json_parser.h +++ b/src/lib/json/json_parser.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) ZondaX GmbH +* (c) 2018, 2019 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/lib/json/tx_display.c b/src/lib/json/tx_display.c index 87a036ec..6178fec6 100644 --- a/src/lib/json/tx_display.c +++ b/src/lib/json/tx_display.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) ZondaX GmbH +* (c) 2018, 2019 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +14,18 @@ * limitations under the License. ********************************************************************************/ -#include -#include -#include "tx_parser.h" #include "tx_display.h" +#include "tx_parser.h" #include "json/json_parser.h" #include "lib/parser_impl.h" +#include #if defined(TARGET_NANOS) || defined(TARGET_NANOX) #include "os.h" -#else -#define PIC(X) X #endif +#define NUM_REQUIRED_ROOT_PAGES 6 + // Required pages const char *get_required_root_item(uint8_t i) { switch (i) { @@ -47,15 +46,6 @@ const char *get_required_root_item(uint8_t i) { } } -//const char *root_required_items[NUM_REQUIRED_ROOT_PAGES] = { -// "chain_id", -// "account_number", -// "sequence", -// "fee", -// "memo", -// "msgs" -//}; - static const uint16_t root_max_level[NUM_REQUIRED_ROOT_PAGES] = { 2, // "chain_id", 2, // "account_number", @@ -65,81 +55,9 @@ static const uint16_t root_max_level[NUM_REQUIRED_ROOT_PAGES] = { 2, // "msgs" }; -typedef struct { - char str1[50]; - char str2[50]; -} key_subst_t; - -static const key_subst_t key_substitutions[NUM_KEY_SUBSTITUTIONS] = { - {"chain_id", "Chain ID"}, - {"account_number", "Account"}, - {"sequence", "Sequence"}, - {"memo", "Memo"}, - {"fee/amount", "Fee"}, - {"fee/gas", "Gas"}, - {"msgs/type", "Type"}, - - // FIXME: Are these obsolete?? multisend? - {"msgs/inputs/address", "Source Address"}, - {"msgs/inputs/coins", "Source Coins"}, - {"msgs/outputs/address", "Dest Address"}, - {"msgs/outputs/coins", "Dest Coins"}, - - // MsgSend - {"msgs/value/from_address", "From"}, - {"msgs/value/to_address", "To"}, - {"msgs/value/amount", "Amount"}, - - // MsgDelegate - {"msgs/value/delegator_address", "Delegator"}, - {"msgs/value/validator_address", "Validator"}, - - // MsgUndelegate -// {"msgs/value/delegator_address", "Delegator"}, -// {"msgs/value/validator_address", "Validator"}, - - // MsgBeginRedelegate -// {"msgs/value/delegator_address", "Delegator"}, - {"msgs/value/validator_src_address", "Validator Source"}, - {"msgs/value/validator_dst_address", "Validator Dest"}, - - // MsgSubmitProposal - {"msgs/value/description", "Description"}, - {"msgs/value/initial_deposit/amount", "Deposit Amount"}, - {"msgs/value/initial_deposit/denom", "Deposit Denom"}, - {"msgs/value/proposal_type", "Proposal"}, - {"msgs/value/proposer", "Proposer"}, - {"msgs/value/title", "Title"}, - - // MsgDeposit - {"msgs/value/depositer", "Sender"}, - {"msgs/value/proposal_id", "Proposal ID"}, - {"msgs/value/amount", "Amount"}, - - // MsgVote - {"msgs/value/voter", "Description"}, -// {"msgs/value/proposal_id", "Proposal ID"}, - {"msgs/value/option", "Option"}, - - // MsgWithdrawDelegationReward -// {"msgs/value/delegator_address", "Delegator"}, // duplicated -// {"msgs/value/validator_address", "Validator"}, // duplicated -}; - -static const key_subst_t value_substitutions[NUM_VALUE_SUBSTITUTIONS] = { - {"cosmos-sdk/MsgSend", "Send"}, - {"cosmos-sdk/MsgDelegate", "Delegate"}, - {"cosmos-sdk/MsgUndelegate", "Undelegate"}, - {"cosmos-sdk/MsgBeginRedelegate", "Redelegate"}, - {"cosmos-sdk/MsgSubmitProposal", "Propose"}, - {"cosmos-sdk/MsgDeposit", "Deposit"}, - {"cosmos-sdk/MsgVote", "Vote"}, - {"cosmos-sdk/MsgWithdrawDelegationReward", "Withdraw Reward"}, -}; - #define STRNCPY_S(DST, SRC, DST_SIZE) \ - strncpy(DST, SRC, DST_SIZE - 1); \ - DST[DST_SIZE - 1] = 0; + MEMSET(DST, 0, DST_SIZE); \ + strncpy(DST, SRC, DST_SIZE - 1); typedef struct { int16_t numItems; @@ -177,22 +95,24 @@ void tx_display_index_root() { tmp_val, sizeof(tmp_val), 0, root_max_level[idx]) - STRNCPY_S(parser_tx_obj.tx_ctx.query.out_key, get_required_root_item(idx), - parser_tx_obj.tx_ctx.query.out_key_len); + STRNCPY_S(parser_tx_obj.query.out_key, + get_required_root_item(idx), + parser_tx_obj.query.out_key_len) - parser_tx_obj.tx_ctx.max_depth = MAX_RECURSION_DEPTH; - parser_tx_obj.tx_ctx.query.item_index = 0; + parser_tx_obj.max_depth = MAX_RECURSION_DEPTH; + parser_tx_obj.query.item_index = 0; found = 0; while (found >= 0) { - parser_tx_obj.tx_ctx.item_index_current = 0; + parser_tx_obj.item_index_current = 0; found = tx_traverse(subroot_token_idx); if (found >= 0) { display_cache.num_subpages[idx]++; - parser_tx_obj.tx_ctx.query.item_index++; + parser_tx_obj.query.item_index++; } - }; + } + display_cache.numItems += display_cache.num_subpages[idx]; if (display_cache.num_subpages[idx] == 0) { @@ -213,53 +133,142 @@ int16_t tx_display_get_item(uint16_t itemIndex) { tx_display_index_root(); // TODO: Verify it has been properly set? - parser_tx_obj.tx_ctx.query.out_key[0] = 0; - parser_tx_obj.tx_ctx.query.out_val[0] = 0; + parser_tx_obj.query.out_key[0] = 0; + parser_tx_obj.query.out_val[0] = 0; if (itemIndex < 0 || itemIndex >= display_cache.numItems) { return -1; } - parser_tx_obj.tx_ctx.query.item_index = 0; + parser_tx_obj.query.item_index = 0; uint16_t root_index = 0; for (uint16_t i = 0; i < itemIndex; i++) { - parser_tx_obj.tx_ctx.query.item_index++; - if (parser_tx_obj.tx_ctx.query.item_index >= display_cache.num_subpages[root_index]) { - parser_tx_obj.tx_ctx.query.item_index = 0; + parser_tx_obj.query.item_index++; + if (parser_tx_obj.query.item_index >= display_cache.num_subpages[root_index]) { + parser_tx_obj.query.item_index = 0; root_index++; } } - parser_tx_obj.tx_ctx.item_index_current = 0; - parser_tx_obj.tx_ctx.max_level = root_max_level[root_index]; - parser_tx_obj.tx_ctx.max_depth = MAX_RECURSION_DEPTH; + parser_tx_obj.item_index_current = 0; + parser_tx_obj.max_level = root_max_level[root_index]; + parser_tx_obj.max_depth = MAX_RECURSION_DEPTH; - STRNCPY_S(parser_tx_obj.tx_ctx.query.out_key, + STRNCPY_S(parser_tx_obj.query.out_key, get_required_root_item(root_index), - parser_tx_obj.tx_ctx.query.out_key_len); + parser_tx_obj.query.out_key_len) int16_t ret = tx_traverse(display_cache.subroot_start_token[root_index]); return ret; } +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// + +#define NUM_KEY_SUBSTITUTIONS 29 +#define NUM_VALUE_SUBSTITUTIONS 8 + +typedef struct { + char str1[50]; + char str2[50]; +} key_subst_t; + +static const key_subst_t key_substitutions[NUM_KEY_SUBSTITUTIONS] = { + {"chain_id", "Chain ID"}, + {"account_number", "Account"}, + {"sequence", "Sequence"}, + {"memo", "Memo"}, + {"fee/amount", "Fee"}, + {"fee/gas", "Gas"}, + {"msgs/type", "Type"}, + + // FIXME: Are these obsolete?? multisend? + {"msgs/inputs/address", "Source Address"}, + {"msgs/inputs/coins", "Source Coins"}, + {"msgs/outputs/address", "Dest Address"}, + {"msgs/outputs/coins", "Dest Coins"}, + + // MsgSend + {"msgs/value/from_address", "From"}, + {"msgs/value/to_address", "To"}, + {"msgs/value/amount", "Amount"}, + + // MsgDelegate + {"msgs/value/delegator_address", "Delegator"}, + {"msgs/value/validator_address", "Validator"}, + + // MsgUndelegate +// {"msgs/value/delegator_address", "Delegator"}, +// {"msgs/value/validator_address", "Validator"}, + + // MsgBeginRedelegate +// {"msgs/value/delegator_address", "Delegator"}, + {"msgs/value/validator_src_address", "Validator Source"}, + {"msgs/value/validator_dst_address", "Validator Dest"}, + + // MsgSubmitProposal + {"msgs/value/description", "Description"}, + {"msgs/value/initial_deposit/amount", "Deposit Amount"}, + {"msgs/value/initial_deposit/denom", "Deposit Denom"}, + {"msgs/value/proposal_type", "Proposal"}, + {"msgs/value/proposer", "Proposer"}, + {"msgs/value/title", "Title"}, + + // MsgDeposit + {"msgs/value/depositer", "Sender"}, + {"msgs/value/proposal_id", "Proposal ID"}, + {"msgs/value/amount", "Amount"}, + + // MsgVote + {"msgs/value/voter", "Description"}, +// {"msgs/value/proposal_id", "Proposal ID"}, + {"msgs/value/option", "Option"}, + + // MsgWithdrawDelegationReward +// {"msgs/value/delegator_address", "Delegator"}, // duplicated +// {"msgs/value/validator_address", "Validator"}, // duplicated +}; + +static const key_subst_t value_substitutions[NUM_VALUE_SUBSTITUTIONS] = { + {"cosmos-sdk/MsgSend", "Send"}, + {"cosmos-sdk/MsgDelegate", "Delegate"}, + {"cosmos-sdk/MsgUndelegate", "Undelegate"}, + {"cosmos-sdk/MsgBeginRedelegate", "Redelegate"}, + {"cosmos-sdk/MsgSubmitProposal", "Propose"}, + {"cosmos-sdk/MsgDeposit", "Deposit"}, + {"cosmos-sdk/MsgVote", "Vote"}, + {"cosmos-sdk/MsgWithdrawDelegationReward", "Withdraw Reward"}, +}; + void tx_display_make_friendly() { tx_display_index_root(); // post process keys for (int8_t i = 0; i < NUM_KEY_SUBSTITUTIONS; i++) { - if (!strcmp(parser_tx_obj.tx_ctx.query.out_key, key_substitutions[i].str1)) { - STRNCPY_S(parser_tx_obj.tx_ctx.query.out_key, + if (!strcmp(parser_tx_obj.query.out_key, key_substitutions[i].str1)) { + STRNCPY_S(parser_tx_obj.query.out_key, key_substitutions[i].str2, - parser_tx_obj.tx_ctx.query.out_key_len); + parser_tx_obj.query.out_key_len) break; } } for (int8_t i = 0; i < NUM_VALUE_SUBSTITUTIONS; i++) { - if (!strcmp(parser_tx_obj.tx_ctx.query.out_val, value_substitutions[i].str1)) { - STRNCPY_S(parser_tx_obj.tx_ctx.query.out_val, + if (!strcmp(parser_tx_obj.query.out_val, value_substitutions[i].str1)) { + STRNCPY_S(parser_tx_obj.query.out_val, value_substitutions[i].str2, - parser_tx_obj.tx_ctx.query.out_val_len); + parser_tx_obj.query.out_val_len) break; } } diff --git a/src/lib/json/tx_display.h b/src/lib/json/tx_display.h index 68fa924f..20e7a092 100644 --- a/src/lib/json/tx_display.h +++ b/src/lib/json/tx_display.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) ZondaX GmbH +* (c) 2018, 2019 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,17 +17,10 @@ #pragma once #include -#include "json/json_parser.h" -#include "tx_parser.h" - #ifdef __cplusplus extern "C" { #endif -#define NUM_REQUIRED_ROOT_PAGES 6 -#define NUM_KEY_SUBSTITUTIONS 29 -#define NUM_VALUE_SUBSTITUTIONS 8 - /// This is the main function called from ledger that updates key and value strings /// that are going to be displayed in the UI. /// This function assumes that the tx_ctx has been properly set diff --git a/src/lib/json/tx_parser.c b/src/lib/json/tx_parser.c index 5e094e41..ccda7c18 100644 --- a/src/lib/json/tx_parser.c +++ b/src/lib/json/tx_parser.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) ZondaX GmbH +* (c) 2018, 2019 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,38 +48,38 @@ __always_inline int16_t tx_get_value(const int16_t token_index) { const int16_t token_end = parser_tx_obj.json.tokens[token_index].end; const int16_t token_len = token_end - token_start; - int16_t num_chunks = (token_len / (parser_tx_obj.tx_ctx.query.out_val_len - 1)) + 1; - if (token_len > 0 && (token_len % (parser_tx_obj.tx_ctx.query.out_val_len - 1) == 0)) + int16_t num_chunks = (token_len / (parser_tx_obj.query.out_val_len - 1)) + 1; + if (token_len > 0 && (token_len % (parser_tx_obj.query.out_val_len - 1) == 0)) num_chunks--; - parser_tx_obj.tx_ctx.query.out_val[0] = '\0'; // flush - if (parser_tx_obj.tx_ctx.query.chunk_index >= num_chunks) { + parser_tx_obj.query.out_val[0] = '\0'; // flush + if (parser_tx_obj.query.chunk_index >= num_chunks) { return TX_TOKEN_NOT_FOUND; } const int16_t chunk_start = - token_start + parser_tx_obj.tx_ctx.query.chunk_index * (parser_tx_obj.tx_ctx.query.out_val_len - 1); + token_start + parser_tx_obj.query.chunk_index * (parser_tx_obj.query.out_val_len - 1); int16_t chunk_len = token_end - chunk_start; if (chunk_len < 0) { return TX_TOKEN_NOT_FOUND; } - if (chunk_len > parser_tx_obj.tx_ctx.query.out_val_len - 1) { - chunk_len = parser_tx_obj.tx_ctx.query.out_val_len - 1; + if (chunk_len > parser_tx_obj.query.out_val_len - 1) { + chunk_len = parser_tx_obj.query.out_val_len - 1; } - MEMCPY(parser_tx_obj.tx_ctx.query.out_val, parser_tx_obj.tx + chunk_start, chunk_len); - parser_tx_obj.tx_ctx.query.out_val[chunk_len] = 0; + MEMCPY(parser_tx_obj.query.out_val, parser_tx_obj.tx + chunk_start, chunk_len); + parser_tx_obj.query.out_val[chunk_len] = 0; return num_chunks; } ///// Update key characters from json transaction read from the token_index element. __always_inline void append_key_item(int16_t token_index) { - if (*parser_tx_obj.tx_ctx.query.out_key > 0) { + if (*parser_tx_obj.query.out_key > 0) { // There is already something there, add separator - strcat_chunk_s(parser_tx_obj.tx_ctx.query.out_key, - parser_tx_obj.tx_ctx.query.out_key_len, "/", 1); + strcat_chunk_s(parser_tx_obj.query.out_key, + parser_tx_obj.query.out_key_len, "/", 1); } const int16_t token_start = parser_tx_obj.json.tokens[token_index].start; @@ -87,22 +87,22 @@ __always_inline void append_key_item(int16_t token_index) { const char *address_ptr = parser_tx_obj.tx + token_start; const int16_t new_item_size = token_end - token_start; - strcat_chunk_s(parser_tx_obj.tx_ctx.query.out_key, - parser_tx_obj.tx_ctx.query.out_key_len, address_ptr, new_item_size); + strcat_chunk_s(parser_tx_obj.query.out_key, + parser_tx_obj.query.out_key_len, address_ptr, new_item_size); } int16_t tx_traverse(int16_t root_token_index) { const jsmntype_t token_type = parser_tx_obj.json.tokens[root_token_index].type; - if (parser_tx_obj.tx_ctx.max_level <= 0 || parser_tx_obj.tx_ctx.max_depth <= 0 || + if (parser_tx_obj.max_level <= 0 || parser_tx_obj.max_depth <= 0 || token_type == JSMN_STRING || token_type == JSMN_PRIMITIVE) { // Early bail out - if (parser_tx_obj.tx_ctx.item_index_current == parser_tx_obj.tx_ctx.query.item_index) { + if (parser_tx_obj.item_index_current == parser_tx_obj.query.item_index) { return tx_get_value(root_token_index); } - parser_tx_obj.tx_ctx.item_index_current++; + parser_tx_obj.item_index_current++; return TX_TOKEN_NOT_FOUND; } @@ -111,39 +111,41 @@ int16_t tx_traverse(int16_t root_token_index) { switch (token_type) { case JSMN_OBJECT: { - const int16_t key_len = strlen(parser_tx_obj.tx_ctx.query.out_key); + const int16_t key_len = strlen(parser_tx_obj.query.out_key); for (int16_t i = 0; i < el_count; ++i) { const int16_t key_index = object_get_nth_key(root_token_index, i, &parser_tx_obj.json); const int16_t value_index = object_get_nth_value(root_token_index, i, &parser_tx_obj.json); // Skip writing keys if we are actually exploring to count - if (parser_tx_obj.tx_ctx.query.item_index != TX_TOKEN_NOT_FOUND) { + if (parser_tx_obj.query.item_index != TX_TOKEN_NOT_FOUND) { append_key_item(key_index); } // When traversing objects both level and depth should be considered - parser_tx_obj.tx_ctx.max_level--; - parser_tx_obj.tx_ctx.max_depth--; + parser_tx_obj.max_level--; + parser_tx_obj.max_depth--; num_chunks = tx_traverse(value_index); // Traverse the value, extracting subkeys - parser_tx_obj.tx_ctx.max_level++; - parser_tx_obj.tx_ctx.max_depth++; + parser_tx_obj.max_level++; + parser_tx_obj.max_depth++; if (num_chunks != TX_TOKEN_NOT_FOUND) { break; } - *(parser_tx_obj.tx_ctx.query.out_key + key_len) = 0; + *(parser_tx_obj.query.out_key + key_len) = 0; } break; } case JSMN_ARRAY: { for (int16_t i = 0; i < el_count; ++i) { - const int16_t element_index = array_get_nth_element(root_token_index, i, &parser_tx_obj.json); + const int16_t element_index = array_get_nth_element( + root_token_index, i, + &parser_tx_obj.json); // When iterating along an array, the level does not change but we need to count the recursion - parser_tx_obj.tx_ctx.max_depth--; + parser_tx_obj.max_depth--; num_chunks = tx_traverse(element_index); - parser_tx_obj.tx_ctx.max_depth++; + parser_tx_obj.max_depth++; if (num_chunks != TX_TOKEN_NOT_FOUND) { break; diff --git a/src/lib/json/tx_parser.h b/src/lib/json/tx_parser.h index 5167412d..db380400 100644 --- a/src/lib/json/tx_parser.h +++ b/src/lib/json/tx_parser.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) ZondaX GmbH +* (c) 2018, 2019 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,19 +29,19 @@ extern "C" { #define INIT_QUERY_CONTEXT(_KEY, _KEY_LEN, _VAL, _VAL_LEN, _CHUNK_IDX, _MAX_LEVEL) \ INIT_QUERY(_KEY, _KEY_LEN, _VAL, _VAL_LEN, _CHUNK_IDX) \ - parser_tx_obj.tx_ctx.item_index_current = 0; \ - parser_tx_obj.tx_ctx.max_depth = MAX_RECURSION_DEPTH; \ - parser_tx_obj.tx_ctx.max_level = _MAX_LEVEL; + parser_tx_obj.item_index_current = 0; \ + parser_tx_obj.max_depth = MAX_RECURSION_DEPTH; \ + parser_tx_obj.max_level = _MAX_LEVEL; #define INIT_QUERY(_KEY, _KEY_LEN, _VAL, _VAL_LEN, _CHUNK_IDX) \ _KEY[0] = 0; \ _VAL[0] = 0; \ - parser_tx_obj.tx_ctx.query.out_key=_KEY; \ - parser_tx_obj.tx_ctx.query.out_val=_VAL; \ - parser_tx_obj.tx_ctx.query.out_key_len = _KEY_LEN; \ - parser_tx_obj.tx_ctx.query.out_val_len = _VAL_LEN; \ - parser_tx_obj.tx_ctx.query.item_index= 0; \ - parser_tx_obj.tx_ctx.query.chunk_index = _CHUNK_IDX; + parser_tx_obj.query.out_key=_KEY; \ + parser_tx_obj.query.out_val=_VAL; \ + parser_tx_obj.query.out_key_len = _KEY_LEN; \ + parser_tx_obj.query.out_val_len = _VAL_LEN; \ + parser_tx_obj.query.item_index= 0; \ + parser_tx_obj.query.chunk_index = _CHUNK_IDX; // Traverses transaction data and fills tx_context // \return -1 if the item was not found or the number of available chunks for this item diff --git a/src/lib/json/tx_validate.c b/src/lib/json/tx_validate.c index 84bfd1f7..e5cac05c 100644 --- a/src/lib/json/tx_validate.c +++ b/src/lib/json/tx_validate.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) ZondaX GmbH +* (c) 2018, 2019 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/lib/json/tx_validate.h b/src/lib/json/tx_validate.h index 068e083a..407b6f28 100644 --- a/src/lib/json/tx_validate.h +++ b/src/lib/json/tx_validate.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) ZondaX GmbH +* (c) 2018, 2019 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/lib/parser.c b/src/lib/parser.c index b6e4d391..436d5455 100644 --- a/src/lib/parser.c +++ b/src/lib/parser.c @@ -48,8 +48,6 @@ parser_error_t parser_getItem(parser_context_t *ctx, snprintf(outKey, outKeyLen, "?"); snprintf(outValue, outValueLen, "?"); - parser_tx_obj.max_chars_per_key_line = outKeyLen; - parser_tx_obj.max_chars_per_value_line = outValueLen; parser_error_t err = parser_ok; diff --git a/src/lib/parser_txdef.h b/src/lib/parser_txdef.h index 11ddd66a..2c3daf1b 100644 --- a/src/lib/parser_txdef.h +++ b/src/lib/parser_txdef.h @@ -32,22 +32,15 @@ typedef struct { int16_t out_val_len; // ?? } tx_query_t; -typedef struct { - tx_query_t query; // ?? - int16_t item_index_current; // ?? - uint8_t max_level; - uint8_t max_depth; -} tx_context_t; - typedef struct { parsed_json_t json; - - uint16_t max_chars_per_key_line; - uint16_t max_chars_per_value_line; const char *tx; uint8_t cache_valid; - tx_context_t tx_ctx; + int16_t item_index_current; // ?? + uint8_t max_level; + uint8_t max_depth; + tx_query_t query; // ?? } parser_tx_t; #ifdef __cplusplus From 1f6354dd22621ca909af9d819b4b9457e777a948 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Wed, 2 Oct 2019 10:12:21 +0200 Subject: [PATCH 19/78] abstract away functions --- src/lib/json/tx_display.c | 3 +++ src/lib/json/tx_display.h | 3 --- src/lib/parser.c | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/json/tx_display.c b/src/lib/json/tx_display.c index 6178fec6..0a80d30d 100644 --- a/src/lib/json/tx_display.c +++ b/src/lib/json/tx_display.c @@ -123,6 +123,8 @@ void tx_display_index_root() { parser_tx_obj.cache_valid = 1; } +void tx_display_make_friendly(); + int16_t tx_display_numItems() { tx_display_index_root(); return display_cache.numItems; @@ -159,6 +161,7 @@ int16_t tx_display_get_item(uint16_t itemIndex) { int16_t ret = tx_traverse(display_cache.subroot_start_token[root_index]); + tx_display_make_friendly(); return ret; } diff --git a/src/lib/json/tx_display.h b/src/lib/json/tx_display.h index 20e7a092..6b6c4b20 100644 --- a/src/lib/json/tx_display.h +++ b/src/lib/json/tx_display.h @@ -30,9 +30,6 @@ int16_t tx_display_get_item(uint16_t itemIndex); /// \return number of pages (msg pages + 5 required) int16_t tx_display_numItems(); -/// Apply postprocessing rules to key and values -void tx_display_make_friendly(); - //--------------------------------------------- #ifdef __cplusplus diff --git a/src/lib/parser.c b/src/lib/parser.c index 436d5455..9e544564 100644 --- a/src/lib/parser.c +++ b/src/lib/parser.c @@ -67,7 +67,6 @@ parser_error_t parser_getItem(parser_context_t *ctx, } *pageCount = ret; - tx_display_make_friendly(); return err; } From 64adf18668e98236666293d58aa1275377de1348 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Thu, 3 Oct 2019 11:09:44 +0200 Subject: [PATCH 20/78] improve initialization --- src/lib/json/tx_display.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/json/tx_display.c b/src/lib/json/tx_display.c index 0a80d30d..6a53c044 100644 --- a/src/lib/json/tx_display.c +++ b/src/lib/json/tx_display.c @@ -67,7 +67,7 @@ typedef struct { display_cache_t display_cache; -void tx_display_index_root() { +void _indexRootFields() { if (parser_tx_obj.cache_valid) { return; } @@ -126,17 +126,17 @@ void tx_display_index_root() { void tx_display_make_friendly(); int16_t tx_display_numItems() { - tx_display_index_root(); + _indexRootFields(); return display_cache.numItems; } // This function assumes that the tx_ctx has been set properly int16_t tx_display_get_item(uint16_t itemIndex) { - tx_display_index_root(); + _indexRootFields(); + + MEMSET(parser_tx_obj.query.out_key, 0, parser_tx_obj.query.out_key_len); + MEMSET(parser_tx_obj.query.out_val, 0, parser_tx_obj.query.out_val_len); - // TODO: Verify it has been properly set? - parser_tx_obj.query.out_key[0] = 0; - parser_tx_obj.query.out_val[0] = 0; if (itemIndex < 0 || itemIndex >= display_cache.numItems) { return -1; } From 0f2a63f217b3a64ad9e7f92d940535f803083df0 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Thu, 3 Oct 2019 13:16:03 +0200 Subject: [PATCH 21/78] refactoring error handling (wip) --- src/lib/json/json_parser.c | 18 +++++++------- src/lib/json/json_parser.h | 11 +++++---- src/lib/json/tx_display.c | 18 +++++++------- src/lib/json/tx_display.h | 3 ++- src/lib/json/tx_parser.c | 48 +++++++++++++++++++------------------- src/lib/json/tx_parser.h | 6 ++--- src/lib/json/tx_validate.c | 21 +++++++++-------- src/lib/json/tx_validate.h | 3 ++- src/lib/parser.c | 22 ++--------------- src/lib/parser_common.h | 31 ++++++++++++++---------- src/lib/parser_impl.c | 39 +++++++++++++++++++++++-------- 11 files changed, 117 insertions(+), 103 deletions(-) diff --git a/src/lib/json/json_parser.c b/src/lib/json/json_parser.c index cbe74e23..66ec8cb6 100644 --- a/src/lib/json/json_parser.c +++ b/src/lib/json/json_parser.c @@ -16,6 +16,7 @@ #include #include +#include #include "json_parser.h" #if defined(TARGET_NANOS) || defined(TARGET_NANOX) @@ -25,11 +26,11 @@ #define EQUALS(_P, _Q, _LEN) (memcmp( (_P), (_Q), (_LEN))==0) #endif -const char *json_parse(parsed_json_t *parsed_json, const char *buffer) { +parser_error_t json_parse(parsed_json_t *parsed_json, const char *buffer) { return json_parse_s(parsed_json, buffer, strlen(buffer)); } -const char *json_parse_s(parsed_json_t *parsed_json, +parser_error_t json_parse_s(parsed_json_t *parsed_json, const char *buffer, uint16_t bufferLen) { jsmn_parser parser; jsmn_init(&parser); @@ -47,11 +48,11 @@ const char *json_parse_s(parsed_json_t *parsed_json, switch (num_tokens) { case JSMN_ERROR_NOMEM: - return "NOMEM: JSON string contains too many tokens"; + return parser_too_many_tokens; case JSMN_ERROR_INVAL: - return "Invalid character in JSON string"; + return parser_unexpected_characters; case JSMN_ERROR_PART: - return "JSON string is not complete"; + return parser_incomplete_json; } parsed_json->numberOfTokens = 0; @@ -59,17 +60,18 @@ const char *json_parse_s(parsed_json_t *parsed_json, // Parsing error if (num_tokens <= 0) { - return "No tokens. Unknown parser error"; + return parser_no_data; } // We cannot support if number of tokens exceeds the limit if (num_tokens > MAX_NUMBER_OF_TOKENS) { - return "TOK: JSON string contains too many tokens"; + return parser_too_many_tokens; } parsed_json->numberOfTokens = num_tokens; parsed_json->isValid = true; - return NULL; + + return parser_ok; } uint16_t array_get_element_count(uint16_t array_token_index, diff --git a/src/lib/json/json_parser.h b/src/lib/json/json_parser.h index 8bb6ae06..6994e6b2 100644 --- a/src/lib/json/json_parser.h +++ b/src/lib/json/json_parser.h @@ -20,6 +20,7 @@ #include #include #include +#include "parser_common.h" #ifdef __cplusplus extern "C" { @@ -61,16 +62,16 @@ typedef struct { /// \param transaction /// \param transaction_length /// \return Error message -const char *json_parse_s(parsed_json_t *parsed_json, - const char *transaction, - uint16_t transaction_length); +parser_error_t json_parse_s(parsed_json_t *parsed_json, + const char *transaction, + uint16_t transaction_length); /// Parse json to create a token representation /// \param parsed_json /// \param transaction /// \return Error message -const char *json_parse(parsed_json_t *parsed_json, - const char *transaction); +parser_error_t json_parse(parsed_json_t *parsed_json, + const char *transaction); /// Get the number of elements in the array /// \param array_token_index diff --git a/src/lib/json/tx_display.c b/src/lib/json/tx_display.c index 6a53c044..6007444d 100644 --- a/src/lib/json/tx_display.c +++ b/src/lib/json/tx_display.c @@ -102,12 +102,12 @@ void _indexRootFields() { parser_tx_obj.max_depth = MAX_RECURSION_DEPTH; parser_tx_obj.query.item_index = 0; - found = 0; - while (found >= 0) { + parser_error_t err = parser_ok; + while (err == parser_ok) { parser_tx_obj.item_index_current = 0; - found = tx_traverse(subroot_token_idx); - - if (found >= 0) { + uint8_t dummy; + err = tx_traverse(subroot_token_idx, &dummy); + if (err == parser_ok) { display_cache.num_subpages[idx]++; parser_tx_obj.query.item_index++; } @@ -131,14 +131,14 @@ int16_t tx_display_numItems() { } // This function assumes that the tx_ctx has been set properly -int16_t tx_display_get_item(uint16_t itemIndex) { +parser_error_t tx_display_get_item(uint16_t itemIndex, uint8_t *numChunks) { _indexRootFields(); MEMSET(parser_tx_obj.query.out_key, 0, parser_tx_obj.query.out_key_len); MEMSET(parser_tx_obj.query.out_val, 0, parser_tx_obj.query.out_val_len); if (itemIndex < 0 || itemIndex >= display_cache.numItems) { - return -1; + return parser_no_data; } parser_tx_obj.query.item_index = 0; @@ -159,7 +159,7 @@ int16_t tx_display_get_item(uint16_t itemIndex) { get_required_root_item(root_index), parser_tx_obj.query.out_key_len) - int16_t ret = tx_traverse(display_cache.subroot_start_token[root_index]); + int16_t ret = tx_traverse(display_cache.subroot_start_token[root_index], numChunks); tx_display_make_friendly(); return ret; @@ -255,7 +255,7 @@ static const key_subst_t value_substitutions[NUM_VALUE_SUBSTITUTIONS] = { }; void tx_display_make_friendly() { - tx_display_index_root(); + _indexRootFields(); // post process keys for (int8_t i = 0; i < NUM_KEY_SUBSTITUTIONS; i++) { diff --git a/src/lib/json/tx_display.h b/src/lib/json/tx_display.h index 6b6c4b20..bccd2a57 100644 --- a/src/lib/json/tx_display.h +++ b/src/lib/json/tx_display.h @@ -16,6 +16,7 @@ #pragma once #include +#include #ifdef __cplusplus extern "C" { @@ -24,7 +25,7 @@ extern "C" { /// This is the main function called from ledger that updates key and value strings /// that are going to be displayed in the UI. /// This function assumes that the tx_ctx has been properly set -int16_t tx_display_get_item(uint16_t itemIndex); +parser_error_t tx_display_get_item(uint16_t itemIndex, uint8_t *numPages); /// Return number of UI pages that we'll have for the current json transaction (only if the tx is valid) /// \return number of pages (msg pages + 5 required) diff --git a/src/lib/json/tx_parser.c b/src/lib/json/tx_parser.c index ccda7c18..89951627 100644 --- a/src/lib/json/tx_parser.c +++ b/src/lib/json/tx_parser.c @@ -42,27 +42,25 @@ __always_inline void strcat_chunk_s(char *dst, uint16_t dst_max, const char *src } } -__always_inline int16_t tx_get_value(const int16_t token_index) { - +__always_inline parser_error_t tx_get_value(int16_t token_index, uint8_t *numChunks) { const int16_t token_start = parser_tx_obj.json.tokens[token_index].start; const int16_t token_end = parser_tx_obj.json.tokens[token_index].end; const int16_t token_len = token_end - token_start; - int16_t num_chunks = (token_len / (parser_tx_obj.query.out_val_len - 1)) + 1; + *numChunks = (token_len / (parser_tx_obj.query.out_val_len - 1)) + 1; if (token_len > 0 && (token_len % (parser_tx_obj.query.out_val_len - 1) == 0)) - num_chunks--; + numChunks--; parser_tx_obj.query.out_val[0] = '\0'; // flush - if (parser_tx_obj.query.chunk_index >= num_chunks) { - return TX_TOKEN_NOT_FOUND; + if (parser_tx_obj.query.chunk_index >= *numChunks) { + return parser_no_data; } - const int16_t chunk_start = - token_start + parser_tx_obj.query.chunk_index * (parser_tx_obj.query.out_val_len - 1); + const int16_t chunk_start = token_start + parser_tx_obj.query.chunk_index * (parser_tx_obj.query.out_val_len - 1); int16_t chunk_len = token_end - chunk_start; if (chunk_len < 0) { - return TX_TOKEN_NOT_FOUND; + return parser_no_data; } if (chunk_len > parser_tx_obj.query.out_val_len - 1) { @@ -71,7 +69,7 @@ __always_inline int16_t tx_get_value(const int16_t token_index) { MEMCPY(parser_tx_obj.query.out_val, parser_tx_obj.tx + chunk_start, chunk_len); parser_tx_obj.query.out_val[chunk_len] = 0; - return num_chunks; + return parser_ok; } ///// Update key characters from json transaction read from the token_index element. @@ -91,7 +89,9 @@ __always_inline void append_key_item(int16_t token_index) { parser_tx_obj.query.out_key_len, address_ptr, new_item_size); } -int16_t tx_traverse(int16_t root_token_index) { +parser_error_t tx_traverse(int16_t root_token_index, uint8_t *numChunks) { +//int16_t tx_traverse(int16_t root_token_index) { + parser_error_t err; const jsmntype_t token_type = parser_tx_obj.json.tokens[root_token_index].type; if (parser_tx_obj.max_level <= 0 || parser_tx_obj.max_depth <= 0 || @@ -100,14 +100,14 @@ int16_t tx_traverse(int16_t root_token_index) { // Early bail out if (parser_tx_obj.item_index_current == parser_tx_obj.query.item_index) { - return tx_get_value(root_token_index); + return tx_get_value(root_token_index, numChunks); } parser_tx_obj.item_index_current++; - return TX_TOKEN_NOT_FOUND; + return parser_no_data; } + *numChunks = 0; const int16_t el_count = object_get_element_count(root_token_index, &parser_tx_obj.json); - int16_t num_chunks = TX_TOKEN_NOT_FOUND; switch (token_type) { case JSMN_OBJECT: { @@ -124,13 +124,13 @@ int16_t tx_traverse(int16_t root_token_index) { // When traversing objects both level and depth should be considered parser_tx_obj.max_level--; parser_tx_obj.max_depth--; - num_chunks = tx_traverse(value_index); // Traverse the value, extracting subkeys + // Traverse the value, extracting subkeys + err = tx_traverse(value_index, numChunks); parser_tx_obj.max_level++; parser_tx_obj.max_depth++; - if (num_chunks != TX_TOKEN_NOT_FOUND) { - break; - } + if (err == parser_ok) + return err; *(parser_tx_obj.query.out_key + key_len) = 0; } @@ -142,14 +142,14 @@ int16_t tx_traverse(int16_t root_token_index) { root_token_index, i, &parser_tx_obj.json); - // When iterating along an array, the level does not change but we need to count the recursion + // When iterating along an array, + // the level does not change but we need to count the recursion parser_tx_obj.max_depth--; - num_chunks = tx_traverse(element_index); + err = tx_traverse(element_index, numChunks); parser_tx_obj.max_depth++; - if (num_chunks != TX_TOKEN_NOT_FOUND) { - break; - } + if (err == parser_ok) + return err; } break; } @@ -157,5 +157,5 @@ int16_t tx_traverse(int16_t root_token_index) { break; } - return num_chunks; + return parser_no_data; } diff --git a/src/lib/json/tx_parser.h b/src/lib/json/tx_parser.h index db380400..05af2821 100644 --- a/src/lib/json/tx_parser.h +++ b/src/lib/json/tx_parser.h @@ -18,6 +18,7 @@ #include "json/json_parser.h" #include +#include #ifdef __cplusplus extern "C" { @@ -44,11 +45,10 @@ extern "C" { parser_tx_obj.query.chunk_index = _CHUNK_IDX; // Traverses transaction data and fills tx_context -// \return -1 if the item was not found or the number of available chunks for this item -int16_t tx_traverse(int16_t root_token_index); +parser_error_t tx_traverse(int16_t root_token_index, uint8_t *numChunks); // Retrieves the value for the corresponding token index. If the value goes beyond val_len, the chunk_idx will be used -int16_t tx_get_value(int16_t token_index); +parser_error_t tx_get_value(int16_t token_index, uint8_t *numChunks); //--------------------------------------------- diff --git a/src/lib/json/tx_validate.c b/src/lib/json/tx_validate.c index e5cac05c..86c4e520 100644 --- a/src/lib/json/tx_validate.c +++ b/src/lib/json/tx_validate.c @@ -15,6 +15,7 @@ ********************************************************************************/ #include +#include #include "json/json_parser.h" const char whitespaces[] = { @@ -104,56 +105,56 @@ int8_t dictionaries_sorted(parsed_json_t *json) { return 1; } -const char *tx_validate(parsed_json_t *json) { +parser_error_t tx_validate(parsed_json_t *json) { if (contains_whitespace(json) == 1) { - return "JSON Contains whitespace in the corpus"; + return parser_json_contains_whitespace; } if (dictionaries_sorted(json) != 1) { - return "JSON Dictionaries are not sorted"; + return parser_json_is_not_sorted; } if (object_get_value( json, 0, "chain_id") == -1) { - return "JSON Missing chain_id"; + return parser_json_missing_chain_id; } if (object_get_value( json, 0, "sequence") == -1) { - return "JSON Missing sequence"; + return parser_json_missing_sequence; } if (object_get_value( json, 0, "fee") == -1) { - return "JSON Missing fee"; + return parser_json_missing_fee; } if (object_get_value( json, 0, "msgs") == -1) { - return "JSON Missing msgs"; + return parser_json_missing_msgs; } if (object_get_value( json, 0, "account_number") == -1) { - return "JSON Missing account_number"; + return parser_json_missing_account_number; } if (object_get_value( json, 0, "memo") == -1) { - return "JSON Missing memo"; + return parser_json_missing_memo; } - return NULL; + return parser_ok; } diff --git a/src/lib/json/tx_validate.h b/src/lib/json/tx_validate.h index 407b6f28..92d8b246 100644 --- a/src/lib/json/tx_validate.h +++ b/src/lib/json/tx_validate.h @@ -18,6 +18,7 @@ #include "json_parser.h" #include +#include #ifdef __cplusplus extern "C" { @@ -27,7 +28,7 @@ extern "C" { /// \param parsed_transacton /// \param transaction /// \return -const char *tx_validate(parsed_json_t *json); +parser_error_t tx_validate(parsed_json_t *json); #ifdef __cplusplus } diff --git a/src/lib/parser.c b/src/lib/parser.c index 9e544564..3127ed9a 100644 --- a/src/lib/parser.c +++ b/src/lib/parser.c @@ -29,11 +29,7 @@ parser_error_t parser_parse(parser_context_t *ctx, } parser_error_t parser_validate(parser_context_t *ctx) { - lastErrorMessage = tx_validate(&parser_tx_obj.json); - if (lastErrorMessage != NULL) { - return parser_extended_error; - } - return parser_ok; + return tx_validate(&parser_tx_obj.json); } uint8_t parser_getNumItems(parser_context_t *ctx) { @@ -49,24 +45,10 @@ parser_error_t parser_getItem(parser_context_t *ctx, snprintf(outKey, outKeyLen, "?"); snprintf(outValue, outValueLen, "?"); - parser_error_t err = parser_ok; - if (displayIdx < 0) { return parser_no_data; } INIT_QUERY(outKey, outKeyLen, outValue, outValueLen, pageIdx) - int16_t ret = tx_display_get_item(displayIdx); - - if (ret == TX_TOKEN_NOT_FOUND) { - return parser_no_data; - } - - if (ret < 0 || ret > 255) { - return parser_unexpected_buffer_end; - } - - *pageCount = ret; - - return err; + return tx_display_get_item(displayIdx, pageCount); } diff --git a/src/lib/parser_common.h b/src/lib/parser_common.h index 7b20650e..7bc4fe66 100644 --- a/src/lib/parser_common.h +++ b/src/lib/parser_common.h @@ -24,20 +24,27 @@ extern "C" { typedef enum { parser_ok = 0, - parser_no_data = 1, - parser_extended_error = 2, - parser_unexpected_buffer_end = 3, - parser_unexpected_wire_type = 4, - parser_unexpected_version = 5, - parser_unexpected_characters = 6, - parser_unexpected_field = 7, - parser_duplicated_field = 8, - parser_value_out_of_range = 9, - parser_unexpected_chain = 10, + parser_no_data, + parser_unexpected_buffer_end, + parser_unexpected_version, + parser_unexpected_characters, + parser_unexpected_field, + parser_duplicated_field, + parser_value_out_of_range, + parser_unexpected_chain, + parser_too_many_tokens, // "NOMEM: JSON string contains too many tokens" + parser_incomplete_json, // "JSON string is not complete"; + //// + parser_json_contains_whitespace, + parser_json_is_not_sorted, + parser_json_missing_chain_id, + parser_json_missing_sequence, + parser_json_missing_fee, + parser_json_missing_msgs, + parser_json_missing_account_number, + parser_json_missing_memo, } parser_error_t; -extern const char *lastErrorMessage; - typedef struct { const uint8_t *buffer; uint16_t bufferSize; diff --git a/src/lib/parser_impl.c b/src/lib/parser_impl.c index b7f685d6..9f24aedf 100644 --- a/src/lib/parser_impl.c +++ b/src/lib/parser_impl.c @@ -18,7 +18,6 @@ #include "json/tx_display.h" parser_tx_t parser_tx_obj; -const char *lastErrorMessage = NULL; parser_error_t parser_init_context(parser_context_t *ctx, const uint8_t *buffer, @@ -51,14 +50,8 @@ const char *parser_getErrorDescription(parser_error_t err) { return "No error"; case parser_no_data: return "No more data"; - case parser_extended_error: - if (lastErrorMessage != NULL) - return lastErrorMessage; - return "Unknown message"; case parser_unexpected_buffer_end: return "Unexpected buffer end"; - case parser_unexpected_wire_type: - return "Unexpected wire type"; case parser_unexpected_version: return "Unexpected version"; case parser_unexpected_characters: @@ -67,17 +60,43 @@ const char *parser_getErrorDescription(parser_error_t err) { return "Unexpected field"; case parser_duplicated_field: return "Unexpected duplicated field"; + case parser_value_out_of_range: + return "Value out of range"; case parser_unexpected_chain: return "Unexpected chain"; + case parser_too_many_tokens: + return "NOMEM: JSON string contains too many tokens"; + case parser_incomplete_json: + return "JSON string is not complete"; + +////// + case parser_json_contains_whitespace: + return "JSON Contains whitespace in the corpus"; + case parser_json_is_not_sorted: + return "JSON Dictionaries are not sorted"; + case parser_json_missing_chain_id: + return "JSON Missing chain_id"; + case parser_json_missing_sequence: + return "JSON Missing sequence"; + case parser_json_missing_fee: + return "JSON Missing fee"; + case parser_json_missing_msgs: + return "JSON Missing msgs"; + case parser_json_missing_account_number: + return "JSON Missing account number"; + case parser_json_missing_memo: + return "JSON Missing memo"; + default: return "Unrecognized error code"; } } parser_error_t _readTx(parser_context_t *c, parser_tx_t *v) { - lastErrorMessage = json_parse_s(&parser_tx_obj.json, (const char *) c->buffer, c->bufferSize); - if (lastErrorMessage != NULL) { - return parser_extended_error; + parser_error_t err = json_parse_s(&parser_tx_obj.json, + (const char *) c->buffer, c->bufferSize); + if (err != parser_ok) { + return err; } parser_tx_obj.tx = (const char *) c->buffer; From a37e53e1a6d333caa5f70588a0d1ca81c9a3d747 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Thu, 3 Oct 2019 16:12:14 +0200 Subject: [PATCH 22/78] wip --- src/lib/json/tx_display.c | 2 +- src/lib/json/tx_parser.c | 27 +++++++++++++++++++++++---- src/lib/json/tx_parser.h | 2 -- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/lib/json/tx_display.c b/src/lib/json/tx_display.c index 6007444d..965ada80 100644 --- a/src/lib/json/tx_display.c +++ b/src/lib/json/tx_display.c @@ -76,7 +76,6 @@ void _indexRootFields() { memset(&display_cache, 0, sizeof(display_cache_t)); // Calculate pages - int8_t found = 0; for (int8_t idx = 0; idx < NUM_REQUIRED_ROOT_PAGES; idx++) { const int16_t subroot_token_idx = object_get_value( &parser_tx_obj.json, @@ -143,6 +142,7 @@ parser_error_t tx_display_get_item(uint16_t itemIndex, uint8_t *numChunks) { parser_tx_obj.query.item_index = 0; uint16_t root_index = 0; + for (uint16_t i = 0; i < itemIndex; i++) { parser_tx_obj.query.item_index++; if (parser_tx_obj.query.item_index >= display_cache.num_subpages[root_index]) { diff --git a/src/lib/json/tx_parser.c b/src/lib/json/tx_parser.c index 89951627..e667fec6 100644 --- a/src/lib/json/tx_parser.c +++ b/src/lib/json/tx_parser.c @@ -42,7 +42,20 @@ __always_inline void strcat_chunk_s(char *dst, uint16_t dst_max, const char *src } } +/////////////////////////// +/////////////////////////// +/////////////////////////// +/////////////////////////// +/////////////////////////// +/////////////////////////// +/////////////////////////// __always_inline parser_error_t tx_get_value(int16_t token_index, uint8_t *numChunks) { + // json.tokens + // query.out_val_len + // query.out_val + // chunk_index + // tx + const int16_t token_start = parser_tx_obj.json.tokens[token_index].start; const int16_t token_end = parser_tx_obj.json.tokens[token_index].end; const int16_t token_len = token_end - token_start; @@ -72,7 +85,6 @@ __always_inline parser_error_t tx_get_value(int16_t token_index, uint8_t *numChu return parser_ok; } -///// Update key characters from json transaction read from the token_index element. __always_inline void append_key_item(int16_t token_index) { if (*parser_tx_obj.query.out_key > 0) { // There is already something there, add separator @@ -88,6 +100,15 @@ __always_inline void append_key_item(int16_t token_index) { strcat_chunk_s(parser_tx_obj.query.out_key, parser_tx_obj.query.out_key_len, address_ptr, new_item_size); } +/////////////////////////// +/////////////////////////// +/////////////////////////// +/////////////////////////// +/////////////////////////// +/////////////////////////// + +parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *value_token_index) { +// item_index_current == query.item_index << when there is a match, extract the token start/end parser_error_t tx_traverse(int16_t root_token_index, uint8_t *numChunks) { //int16_t tx_traverse(int16_t root_token_index) { @@ -117,9 +138,7 @@ parser_error_t tx_traverse(int16_t root_token_index, uint8_t *numChunks) { const int16_t value_index = object_get_nth_value(root_token_index, i, &parser_tx_obj.json); // Skip writing keys if we are actually exploring to count - if (parser_tx_obj.query.item_index != TX_TOKEN_NOT_FOUND) { - append_key_item(key_index); - } + append_key_item(key_index); // When traversing objects both level and depth should be considered parser_tx_obj.max_level--; diff --git a/src/lib/json/tx_parser.h b/src/lib/json/tx_parser.h index 05af2821..c914580e 100644 --- a/src/lib/json/tx_parser.h +++ b/src/lib/json/tx_parser.h @@ -24,8 +24,6 @@ extern "C" { #endif -#define TX_TOKEN_NOT_FOUND (-1) - #define MAX_RECURSION_DEPTH 6 #define INIT_QUERY_CONTEXT(_KEY, _KEY_LEN, _VAL, _VAL_LEN, _CHUNK_IDX, _MAX_LEVEL) \ From f0a50965aa00713d9f6e2d3c451248be7140b6b9 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Thu, 3 Oct 2019 19:06:46 +0200 Subject: [PATCH 23/78] further improvements --- src/lib/json/tx_display.c | 25 ++++++------------------- src/lib/json/tx_display.h | 10 +++++++++- src/lib/json/tx_parser.c | 29 ++++++++++++----------------- src/lib/json/tx_parser.h | 7 +++++-- src/lib/parser.c | 35 +++++++++++++++++++++++++++++++---- src/lib/parser_txdef.h | 9 ++++++--- 6 files changed, 69 insertions(+), 46 deletions(-) diff --git a/src/lib/json/tx_display.c b/src/lib/json/tx_display.c index 965ada80..c6391651 100644 --- a/src/lib/json/tx_display.c +++ b/src/lib/json/tx_display.c @@ -55,10 +55,6 @@ static const uint16_t root_max_level[NUM_REQUIRED_ROOT_PAGES] = { 2, // "msgs" }; -#define STRNCPY_S(DST, SRC, DST_SIZE) \ - MEMSET(DST, 0, DST_SIZE); \ - strncpy(DST, SRC, DST_SIZE - 1); - typedef struct { int16_t numItems; int16_t subroot_start_token[NUM_REQUIRED_ROOT_PAGES]; @@ -104,8 +100,8 @@ void _indexRootFields() { parser_error_t err = parser_ok; while (err == parser_ok) { parser_tx_obj.item_index_current = 0; - uint8_t dummy; - err = tx_traverse(subroot_token_idx, &dummy); + uint16_t dummy; + err = tx_traverse_find(subroot_token_idx, &dummy); if (err == parser_ok) { display_cache.num_subpages[idx]++; parser_tx_obj.query.item_index++; @@ -122,20 +118,15 @@ void _indexRootFields() { parser_tx_obj.cache_valid = 1; } -void tx_display_make_friendly(); - int16_t tx_display_numItems() { _indexRootFields(); return display_cache.numItems; } // This function assumes that the tx_ctx has been set properly -parser_error_t tx_display_get_item(uint16_t itemIndex, uint8_t *numChunks) { +parser_error_t tx_display_set_query(uint16_t itemIndex, uint16_t *outStartToken) { _indexRootFields(); - MEMSET(parser_tx_obj.query.out_key, 0, parser_tx_obj.query.out_key_len); - MEMSET(parser_tx_obj.query.out_val, 0, parser_tx_obj.query.out_val_len); - if (itemIndex < 0 || itemIndex >= display_cache.numItems) { return parser_no_data; } @@ -151,18 +142,14 @@ parser_error_t tx_display_get_item(uint16_t itemIndex, uint8_t *numChunks) { } } + parser_tx_obj.item_index_root = root_index; parser_tx_obj.item_index_current = 0; parser_tx_obj.max_level = root_max_level[root_index]; parser_tx_obj.max_depth = MAX_RECURSION_DEPTH; - STRNCPY_S(parser_tx_obj.query.out_key, - get_required_root_item(root_index), - parser_tx_obj.query.out_key_len) - - int16_t ret = tx_traverse(display_cache.subroot_start_token[root_index], numChunks); + *outStartToken = display_cache.subroot_start_token[parser_tx_obj.item_index_root]; - tx_display_make_friendly(); - return ret; + return parser_ok; } ///////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/lib/json/tx_display.h b/src/lib/json/tx_display.h index bccd2a57..cd8741fe 100644 --- a/src/lib/json/tx_display.h +++ b/src/lib/json/tx_display.h @@ -22,15 +22,23 @@ extern "C" { #endif +#define STRNCPY_S(DST, SRC, DST_SIZE) \ + MEMSET(DST, 0, DST_SIZE); \ + strncpy(DST, SRC, DST_SIZE - 1); + /// This is the main function called from ledger that updates key and value strings /// that are going to be displayed in the UI. /// This function assumes that the tx_ctx has been properly set -parser_error_t tx_display_get_item(uint16_t itemIndex, uint8_t *numPages); +parser_error_t tx_display_set_query(uint16_t itemIndex, uint16_t *outStartToken); /// Return number of UI pages that we'll have for the current json transaction (only if the tx is valid) /// \return number of pages (msg pages + 5 required) int16_t tx_display_numItems(); +const char *get_required_root_item(uint8_t i); + +void tx_display_make_friendly(); + //--------------------------------------------- #ifdef __cplusplus diff --git a/src/lib/json/tx_parser.c b/src/lib/json/tx_parser.c index e667fec6..1df02731 100644 --- a/src/lib/json/tx_parser.c +++ b/src/lib/json/tx_parser.c @@ -49,12 +49,12 @@ __always_inline void strcat_chunk_s(char *dst, uint16_t dst_max, const char *src /////////////////////////// /////////////////////////// /////////////////////////// -__always_inline parser_error_t tx_get_value(int16_t token_index, uint8_t *numChunks) { - // json.tokens - // query.out_val_len - // query.out_val + +__always_inline parser_error_t tx_getToken(uint16_t token_index, + char *out_val, uint16_t out_val_len, + uint16_t chunk_index, uint8_t *numChunks) { // chunk_index - // tx + // query.out_val_len, query.out_val const int16_t token_start = parser_tx_obj.json.tokens[token_index].start; const int16_t token_end = parser_tx_obj.json.tokens[token_index].end; @@ -107,11 +107,7 @@ __always_inline void append_key_item(int16_t token_index) { /////////////////////////// /////////////////////////// -parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *value_token_index) { -// item_index_current == query.item_index << when there is a match, extract the token start/end - -parser_error_t tx_traverse(int16_t root_token_index, uint8_t *numChunks) { -//int16_t tx_traverse(int16_t root_token_index) { +parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_token_index) { parser_error_t err; const jsmntype_t token_type = parser_tx_obj.json.tokens[root_token_index].type; @@ -121,13 +117,13 @@ parser_error_t tx_traverse(int16_t root_token_index, uint8_t *numChunks) { // Early bail out if (parser_tx_obj.item_index_current == parser_tx_obj.query.item_index) { - return tx_get_value(root_token_index, numChunks); + *ret_value_token_index = root_token_index; + return parser_ok; } parser_tx_obj.item_index_current++; return parser_no_data; } - *numChunks = 0; const int16_t el_count = object_get_element_count(root_token_index, &parser_tx_obj.json); switch (token_type) { @@ -144,7 +140,7 @@ parser_error_t tx_traverse(int16_t root_token_index, uint8_t *numChunks) { parser_tx_obj.max_level--; parser_tx_obj.max_depth--; // Traverse the value, extracting subkeys - err = tx_traverse(value_index, numChunks); + err = tx_traverse_find(value_index, ret_value_token_index); parser_tx_obj.max_level++; parser_tx_obj.max_depth++; @@ -157,14 +153,13 @@ parser_error_t tx_traverse(int16_t root_token_index, uint8_t *numChunks) { } case JSMN_ARRAY: { for (int16_t i = 0; i < el_count; ++i) { - const int16_t element_index = array_get_nth_element( - root_token_index, i, - &parser_tx_obj.json); + const int16_t element_index = array_get_nth_element(root_token_index, i, + &parser_tx_obj.json); // When iterating along an array, // the level does not change but we need to count the recursion parser_tx_obj.max_depth--; - err = tx_traverse(element_index, numChunks); + err = tx_traverse_find(element_index, ret_value_token_index); parser_tx_obj.max_depth++; if (err == parser_ok) diff --git a/src/lib/json/tx_parser.h b/src/lib/json/tx_parser.h index c914580e..512afe98 100644 --- a/src/lib/json/tx_parser.h +++ b/src/lib/json/tx_parser.h @@ -42,12 +42,15 @@ extern "C" { parser_tx_obj.query.item_index= 0; \ parser_tx_obj.query.chunk_index = _CHUNK_IDX; +parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_token_index); + // Traverses transaction data and fills tx_context parser_error_t tx_traverse(int16_t root_token_index, uint8_t *numChunks); // Retrieves the value for the corresponding token index. If the value goes beyond val_len, the chunk_idx will be used -parser_error_t tx_get_value(int16_t token_index, uint8_t *numChunks); - +parser_error_t tx_getToken(uint16_t token_index, + char *out_val, uint16_t out_val_len, + uint16_t chunk_index, uint8_t *numChunks); //--------------------------------------------- #ifdef __cplusplus diff --git a/src/lib/parser.c b/src/lib/parser.c index 3127ed9a..76cfb4c8 100644 --- a/src/lib/parser.c +++ b/src/lib/parser.c @@ -16,6 +16,7 @@ #include #include +#include #include "json/tx_parser.h" #include "json/tx_display.h" #include "lib/parser_impl.h" @@ -39,16 +40,42 @@ uint8_t parser_getNumItems(parser_context_t *ctx) { parser_error_t parser_getItem(parser_context_t *ctx, int8_t displayIdx, char *outKey, uint16_t outKeyLen, - char *outValue, uint16_t outValueLen, + char *outVal, uint16_t outValLen, uint8_t pageIdx, uint8_t *pageCount) { + MEMSET(outKey, 0, outKeyLen); + MEMSET(outVal, 0, outValLen); snprintf(outKey, outKeyLen, "?"); - snprintf(outValue, outValueLen, "?"); + snprintf(outVal, outValLen, "?"); if (displayIdx < 0) { return parser_no_data; } - INIT_QUERY(outKey, outKeyLen, outValue, outValueLen, pageIdx) - return tx_display_get_item(displayIdx, pageCount); + INIT_QUERY(outKey, outKeyLen, outVal, outValLen, pageIdx) + + uint16_t displayStartToken; + parser_error_t err = tx_display_set_query(displayIdx, &displayStartToken); + if (err != parser_ok) + return err; + + STRNCPY_S(parser_tx_obj.query.out_key, + get_required_root_item(parser_tx_obj.item_index_root), + parser_tx_obj.query.out_key_len) + + uint16_t ret_value_token_index; + err = tx_traverse_find(displayStartToken, &ret_value_token_index); + + if (err != parser_ok) + return err; + + err = tx_getToken(ret_value_token_index, + parser_tx_obj.query.out_val, parser_tx_obj.query.out_val_len, + parser_tx_obj.query.chunk_index, pageCount); + if (err != parser_ok) + return err; + + tx_display_make_friendly(); + + return err; } diff --git a/src/lib/parser_txdef.h b/src/lib/parser_txdef.h index 2c3daf1b..fc28ce8b 100644 --- a/src/lib/parser_txdef.h +++ b/src/lib/parser_txdef.h @@ -24,10 +24,11 @@ extern "C" { typedef struct { int16_t item_index; // ?? - int16_t chunk_index; // ?? char *out_key; // ?? int16_t out_key_len; // ?? + + int16_t chunk_index; // ?? char *out_val; // ?? int16_t out_val_len; // ?? } tx_query_t; @@ -37,10 +38,12 @@ typedef struct { const char *tx; uint8_t cache_valid; - int16_t item_index_current; // ?? + tx_query_t query; + + uint16_t item_index_current; + uint16_t item_index_root; uint8_t max_level; uint8_t max_depth; - tx_query_t query; // ?? } parser_tx_t; #ifdef __cplusplus From 2afd7cc8653c190b3e0b4169a9df30aa7c585be4 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Mon, 7 Oct 2019 14:48:57 +0200 Subject: [PATCH 24/78] struct refactoring --- src/lib/json/tx_display.c | 14 +++++++------- src/lib/json/tx_parser.c | 18 +++++++++--------- src/lib/json/tx_parser.h | 6 +++--- src/lib/parser.c | 2 +- src/lib/parser_txdef.h | 22 ++++++++++------------ 5 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/lib/json/tx_display.c b/src/lib/json/tx_display.c index c6391651..284fd2e4 100644 --- a/src/lib/json/tx_display.c +++ b/src/lib/json/tx_display.c @@ -94,12 +94,12 @@ void _indexRootFields() { get_required_root_item(idx), parser_tx_obj.query.out_key_len) - parser_tx_obj.max_depth = MAX_RECURSION_DEPTH; + parser_tx_obj.query.max_depth = MAX_RECURSION_DEPTH; parser_tx_obj.query.item_index = 0; parser_error_t err = parser_ok; while (err == parser_ok) { - parser_tx_obj.item_index_current = 0; + parser_tx_obj.query.item_index_current = 0; uint16_t dummy; err = tx_traverse_find(subroot_token_idx, &dummy); if (err == parser_ok) { @@ -142,12 +142,12 @@ parser_error_t tx_display_set_query(uint16_t itemIndex, uint16_t *outStartToken) } } - parser_tx_obj.item_index_root = root_index; - parser_tx_obj.item_index_current = 0; - parser_tx_obj.max_level = root_max_level[root_index]; - parser_tx_obj.max_depth = MAX_RECURSION_DEPTH; + parser_tx_obj.query.item_index_root = root_index; + parser_tx_obj.query.item_index_current = 0; + parser_tx_obj.query.max_level = root_max_level[root_index]; + parser_tx_obj.query.max_depth = MAX_RECURSION_DEPTH; - *outStartToken = display_cache.subroot_start_token[parser_tx_obj.item_index_root]; + *outStartToken = display_cache.subroot_start_token[parser_tx_obj.query.item_index_root]; return parser_ok; } diff --git a/src/lib/json/tx_parser.c b/src/lib/json/tx_parser.c index 1df02731..5b052ba1 100644 --- a/src/lib/json/tx_parser.c +++ b/src/lib/json/tx_parser.c @@ -111,16 +111,16 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to parser_error_t err; const jsmntype_t token_type = parser_tx_obj.json.tokens[root_token_index].type; - if (parser_tx_obj.max_level <= 0 || parser_tx_obj.max_depth <= 0 || + if (parser_tx_obj.query.max_level <= 0 || parser_tx_obj.query.max_depth <= 0 || token_type == JSMN_STRING || token_type == JSMN_PRIMITIVE) { // Early bail out - if (parser_tx_obj.item_index_current == parser_tx_obj.query.item_index) { + if (parser_tx_obj.query.item_index_current == parser_tx_obj.query.item_index) { *ret_value_token_index = root_token_index; return parser_ok; } - parser_tx_obj.item_index_current++; + parser_tx_obj.query.item_index_current++; return parser_no_data; } @@ -137,12 +137,12 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to append_key_item(key_index); // When traversing objects both level and depth should be considered - parser_tx_obj.max_level--; - parser_tx_obj.max_depth--; + parser_tx_obj.query.max_level--; + parser_tx_obj.query.max_depth--; // Traverse the value, extracting subkeys err = tx_traverse_find(value_index, ret_value_token_index); - parser_tx_obj.max_level++; - parser_tx_obj.max_depth++; + parser_tx_obj.query.max_level++; + parser_tx_obj.query.max_depth++; if (err == parser_ok) return err; @@ -158,9 +158,9 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to // When iterating along an array, // the level does not change but we need to count the recursion - parser_tx_obj.max_depth--; + parser_tx_obj.query.max_depth--; err = tx_traverse_find(element_index, ret_value_token_index); - parser_tx_obj.max_depth++; + parser_tx_obj.query.max_depth++; if (err == parser_ok) return err; diff --git a/src/lib/json/tx_parser.h b/src/lib/json/tx_parser.h index 512afe98..4db50448 100644 --- a/src/lib/json/tx_parser.h +++ b/src/lib/json/tx_parser.h @@ -28,9 +28,9 @@ extern "C" { #define INIT_QUERY_CONTEXT(_KEY, _KEY_LEN, _VAL, _VAL_LEN, _CHUNK_IDX, _MAX_LEVEL) \ INIT_QUERY(_KEY, _KEY_LEN, _VAL, _VAL_LEN, _CHUNK_IDX) \ - parser_tx_obj.item_index_current = 0; \ - parser_tx_obj.max_depth = MAX_RECURSION_DEPTH; \ - parser_tx_obj.max_level = _MAX_LEVEL; + parser_tx_obj.query.item_index_current = 0; \ + parser_tx_obj.query.max_depth = MAX_RECURSION_DEPTH; \ + parser_tx_obj.query.max_level = _MAX_LEVEL; #define INIT_QUERY(_KEY, _KEY_LEN, _VAL, _VAL_LEN, _CHUNK_IDX) \ _KEY[0] = 0; \ diff --git a/src/lib/parser.c b/src/lib/parser.c index 76cfb4c8..459102f0 100644 --- a/src/lib/parser.c +++ b/src/lib/parser.c @@ -60,7 +60,7 @@ parser_error_t parser_getItem(parser_context_t *ctx, return err; STRNCPY_S(parser_tx_obj.query.out_key, - get_required_root_item(parser_tx_obj.item_index_root), + get_required_root_item(parser_tx_obj.query.item_index_root), parser_tx_obj.query.out_key_len) uint16_t ret_value_token_index; diff --git a/src/lib/parser_txdef.h b/src/lib/parser_txdef.h index fc28ce8b..fed623c3 100644 --- a/src/lib/parser_txdef.h +++ b/src/lib/parser_txdef.h @@ -23,27 +23,25 @@ extern "C" { #include typedef struct { - int16_t item_index; // ?? + int16_t item_index; + int16_t chunk_index; + uint16_t item_index_current; + uint16_t item_index_root; + uint8_t max_level; + uint8_t max_depth; - char *out_key; // ?? - int16_t out_key_len; // ?? + char *out_key; + int16_t out_key_len; - int16_t chunk_index; // ?? - char *out_val; // ?? - int16_t out_val_len; // ?? + char *out_val; + int16_t out_val_len; } tx_query_t; typedef struct { parsed_json_t json; const char *tx; uint8_t cache_valid; - tx_query_t query; - - uint16_t item_index_current; - uint16_t item_index_root; - uint8_t max_level; - uint8_t max_depth; } parser_tx_t; #ifdef __cplusplus From fc97e915fbad10727dda5bda69450ddd59a66afc Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Wed, 9 Oct 2019 17:28:00 +0200 Subject: [PATCH 25/78] amount formatter --- deps/ledger-zxlib/include/zxmacros.h | 111 +++++++++++++++++---------- src/lib/json/tx_parser.c | 23 +++--- src/lib/parser.c | 94 +++++++++++++++++++++-- 3 files changed, 169 insertions(+), 59 deletions(-) diff --git a/deps/ledger-zxlib/include/zxmacros.h b/deps/ledger-zxlib/include/zxmacros.h index 9a9170a2..b1ea9fda 100644 --- a/deps/ledger-zxlib/include/zxmacros.h +++ b/deps/ledger-zxlib/include/zxmacros.h @@ -32,6 +32,10 @@ extern "C" { #define NV_VOL #endif +#ifndef PIC +#define PIC(x) (x) +#endif + #define NV_ALIGN __attribute__ ((aligned(64))) #if defined (TARGET_NANOS) || defined(TARGET_NANOX) @@ -75,7 +79,9 @@ void __logstack(); #define LOGSTACK() __logstack() #else + #include + #define MEMMOVE memmove #define MEMSET memset #define MEMCPY memcpy @@ -104,44 +110,31 @@ void __logstack(); #define NtoHL(x) (x) #endif -__Z_INLINE void array_to_hexstr(char *dst, const uint8_t *src, uint8_t count) { - const char hexchars[] = "0123456789ABCDEF"; - for (uint8_t i = 0; i < count; i++, src++) { - *dst++ = hexchars[*src >> 4]; - *dst++ = hexchars[*src & 0x0F]; - } - *dst = 0; // terminate string +#define NUM_TO_STR(TYPE) __Z_INLINE const char * TYPE##_to_str(char *data, int dataLen, TYPE##_t number) { \ + if (dataLen < 2) return "Buffer too small"; \ + MEMSET(data, 0, dataLen); \ + char *p = data; \ + if (number < 0) { *(p++) = '-'; data++; } \ + else if (number == 0) { *(p++) = '0'; } \ + TYPE##_t tmp; \ + while (number != 0) { \ + if (p - data >= (dataLen - 1)) { return "Buffer too small"; } \ + tmp = number % 10; \ + tmp = tmp < 0 ? -tmp : tmp; \ + *(p++) = (char) ('0' + tmp); \ + number /= 10u; \ + } \ + while (p > data) { \ + p--; \ + char z = *data; *data = *p; *p = z; \ + data++; \ + } \ + return NULL; \ } -__Z_INLINE const char *int64_to_str(char *data, int size, int64_t number) { - char temp[] = "-9223372036854775808"; +NUM_TO_STR(int64) - char *ptr = temp; - int64_t num = number; - int sign = 1; - if (number < 0) { - sign = -1; - } - while (num != 0) { - *ptr++ = '0' + (num % 10) * sign; - num /= 10; - } - if (number < 0) { - *ptr++ = '-'; - } else if (number == 0) { - *ptr++ = '0'; - } - int distance = (ptr - temp) + 1; - if (size < distance) { - return "Size too small"; - } - int index = 0; - while (--ptr >= temp) { - data[index++] = *ptr; - } - data[index] = '\0'; - return NULL; -} +NUM_TO_STR(uint64) __Z_INLINE int8_t str_to_int8(const char *start, const char *end, char *error) { @@ -227,21 +220,57 @@ __Z_INLINE void fpuint64_to_str(char *dst, const uint64_t value, uint8_t decimal __Z_INLINE uint64_t uint64_from_BEarray(const uint8_t data[8]) { uint64_t result = 0; - for (int i = 0; i < 8; i++) { - result <<= 8; + for (uint8_t i = 0; i < 8u; i++) { + result <<= 8u; result += data[i]; } return result; } +__Z_INLINE void array_to_hexstr(char *dst, const uint8_t *src, uint8_t count) { + const char hexchars[] = "0123456789ABCDEF"; + for (uint8_t i = 0; i < count; i++, src++) { + *dst++ = hexchars[*src >> 4u]; + *dst++ = hexchars[*src & 0x0Fu]; + } + *dst = 0; // terminate string +} + +__Z_INLINE void pageString(char *outValue, uint16_t outValueLen, + char *inValue, uint8_t pageIdx, uint8_t *pageCount) { + MEMSET(outValue, 0, outValueLen); + outValueLen--; // leave space for NULL termination + uint16_t inValueLen = strlen(inValue); + *pageCount = (inValueLen / outValueLen); + const uint16_t lastChunkLen = (inValueLen % outValueLen); + + if (lastChunkLen > 0) { + (*pageCount)++; + } + + if (pageIdx < *pageCount) { + if (lastChunkLen > 0 && pageIdx == *pageCount - 1) { + MEMCPY(outValue, inValue + (pageIdx * outValueLen), lastChunkLen); + } else { + MEMCPY(outValue, inValue + (pageIdx * outValueLen), outValueLen); + } + } +} + +/////////////////////// +/////////////////////// +/////////////////////// +/////////////////////// + +typedef enum { + FALSE, + TRUE +} bool_t; + size_t asciify(char *utf8_in); size_t asciify_ext(const char *utf8_in, char *ascii_only_out); -#ifndef PIC -#define PIC(x) (x) -#endif - #ifdef __cplusplus } #endif diff --git a/src/lib/json/tx_parser.c b/src/lib/json/tx_parser.c index 5b052ba1..e7ad70b9 100644 --- a/src/lib/json/tx_parser.c +++ b/src/lib/json/tx_parser.c @@ -53,34 +53,31 @@ __always_inline void strcat_chunk_s(char *dst, uint16_t dst_max, const char *src __always_inline parser_error_t tx_getToken(uint16_t token_index, char *out_val, uint16_t out_val_len, uint16_t chunk_index, uint8_t *numChunks) { - // chunk_index - // query.out_val_len, query.out_val - const int16_t token_start = parser_tx_obj.json.tokens[token_index].start; const int16_t token_end = parser_tx_obj.json.tokens[token_index].end; const int16_t token_len = token_end - token_start; - *numChunks = (token_len / (parser_tx_obj.query.out_val_len - 1)) + 1; - if (token_len > 0 && (token_len % (parser_tx_obj.query.out_val_len - 1) == 0)) + *numChunks = (token_len / (out_val_len - 1)) + 1; + if (token_len > 0 && (token_len % (out_val_len - 1) == 0)) numChunks--; - parser_tx_obj.query.out_val[0] = '\0'; // flush - if (parser_tx_obj.query.chunk_index >= *numChunks) { + out_val[0] = '\0'; // flush + if (chunk_index >= *numChunks) { return parser_no_data; } - const int16_t chunk_start = token_start + parser_tx_obj.query.chunk_index * (parser_tx_obj.query.out_val_len - 1); + const int16_t chunk_start = token_start + chunk_index * (out_val_len - 1); int16_t chunk_len = token_end - chunk_start; - if (chunk_len < 0) { return parser_no_data; } - if (chunk_len > parser_tx_obj.query.out_val_len - 1) { - chunk_len = parser_tx_obj.query.out_val_len - 1; + if (chunk_len > out_val_len - 1) { + chunk_len = out_val_len - 1; } - MEMCPY(parser_tx_obj.query.out_val, parser_tx_obj.tx + chunk_start, chunk_len); - parser_tx_obj.query.out_val[chunk_len] = 0; + + MEMCPY(out_val, parser_tx_obj.tx + chunk_start, chunk_len); + out_val[chunk_len] = 0; return parser_ok; } diff --git a/src/lib/parser.c b/src/lib/parser.c index 459102f0..ea84f507 100644 --- a/src/lib/parser.c +++ b/src/lib/parser.c @@ -21,6 +21,7 @@ #include "json/tx_display.h" #include "lib/parser_impl.h" #include "view_internal.h" +#include "jsmn.h" parser_error_t parser_parse(parser_context_t *ctx, const uint8_t *data, @@ -37,6 +38,84 @@ uint8_t parser_getNumItems(parser_context_t *ctx) { return tx_display_numItems(); } +__Z_INLINE bool_t parser_areEqual(uint16_t tokenidx, char *expected) { + if (parser_tx_obj.json.tokens[tokenidx].type != JSMN_STRING) { + return FALSE; + } + + uint16_t len = parser_tx_obj.json.tokens[tokenidx].end - parser_tx_obj.json.tokens[tokenidx].start; + if (strlen(expected) != len) { + return FALSE; + } + + const char *p = parser_tx_obj.tx + parser_tx_obj.json.tokens[tokenidx].start; + for (uint16_t i = 0; i < len; i++) { + if (expected[i] != *(p + i)) { + return FALSE; + } + } + + return TRUE; +} + +__Z_INLINE bool_t parser_isAmount(char *key) { + if (strcmp(parser_tx_obj.query.out_key, "fee/amount") == 0) + return TRUE; + + if (strcmp(parser_tx_obj.query.out_key, "msgs/inputs/coins") == 0) + return TRUE; + + if (strcmp(parser_tx_obj.query.out_key, "msgs/outputs/coins") == 0) + return TRUE; + + if (strcmp(parser_tx_obj.query.out_key, "msgs/value/amount") == 0) + return TRUE; + + return FALSE; +} + +__Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, + char *outVal, uint16_t outValLen, + uint8_t pageIdx, uint8_t *pageCount) { + const uint16_t numElementsList = array_get_element_count(amountToken, &parser_tx_obj.json); + const uint16_t numElementsDict = array_get_element_count(amountToken + 1, &parser_tx_obj.json); + + if (numElementsList != 1 || numElementsDict != 4) + return parser_unexpected_field; + + if (parser_tx_obj.json.tokens[amountToken + 1].type != JSMN_OBJECT) + return parser_unexpected_field; + + if (!parser_areEqual(amountToken + 2, "amount")) + return parser_unexpected_field; + + if (!parser_areEqual(amountToken + 4, "denom")) + return parser_unexpected_field; + + char bufferUI[160]; + MEMSET(outVal, 0, outValLen); + MEMSET(bufferUI, 0, sizeof(bufferUI)); + + const char *amountPtr = parser_tx_obj.tx + parser_tx_obj.json.tokens[amountToken + 3].start; + const uint16_t amountLen = parser_tx_obj.json.tokens[amountToken + 3].end - + parser_tx_obj.json.tokens[amountToken + 3].start; + const char *denomPtr = parser_tx_obj.tx + parser_tx_obj.json.tokens[amountToken + 5].start; + const uint16_t denomLen = parser_tx_obj.json.tokens[amountToken + 5].end - + parser_tx_obj.json.tokens[amountToken + 5].start; + + if (sizeof(bufferUI) < amountLen + denomLen + 2) { + return parser_unexpected_buffer_end; + } + + MEMCPY(bufferUI, amountPtr, amountLen); + bufferUI[amountLen] = ' '; + MEMCPY(bufferUI + 1 + amountLen, denomPtr, denomLen); + + pageString(outVal, outValLen, bufferUI, pageIdx, pageCount); + + return parser_ok; +} + parser_error_t parser_getItem(parser_context_t *ctx, int8_t displayIdx, char *outKey, uint16_t outKeyLen, @@ -69,13 +148,18 @@ parser_error_t parser_getItem(parser_context_t *ctx, if (err != parser_ok) return err; - err = tx_getToken(ret_value_token_index, - parser_tx_obj.query.out_val, parser_tx_obj.query.out_val_len, - parser_tx_obj.query.chunk_index, pageCount); - if (err != parser_ok) - return err; + if (parser_isAmount(parser_tx_obj.query.out_key)) { + err = parser_formatAmount(ret_value_token_index, outVal, outValLen, pageIdx, pageCount); + } else { + err = tx_getToken(ret_value_token_index, + parser_tx_obj.query.out_val, parser_tx_obj.query.out_val_len, + parser_tx_obj.query.chunk_index, pageCount); + } tx_display_make_friendly(); + if (err != parser_ok) + return err; + return err; } From f3a52f616362a6af7d51b344f19108bb16d710ba Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Wed, 9 Oct 2019 18:51:29 +0200 Subject: [PATCH 26/78] improving workflow --- Makefile | 2 +- deps/ledger-zxlib/include/zxmacros.h | 12 ++++++-- src/lib/crypto.c | 7 ++++- src/lib/json/tx_display.c | 6 ++-- src/lib/json/tx_display.h | 2 +- src/lib/json/tx_parser.c | 30 +++++-------------- src/lib/json/tx_parser.h | 3 +- src/lib/parser.c | 43 ++++++++++++++-------------- src/tx.c | 16 +++++------ 9 files changed, 58 insertions(+), 63 deletions(-) diff --git a/Makefile b/Makefile index 3b0c4201..31804ab1 100755 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ include $(BOLOS_SDK)/Makefile.defines APPNAME = "Cosmos" APPVERSION_M=2 APPVERSION_N=0 -APPVERSION_P=0 +APPVERSION_P=1 APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path "44'/118'" diff --git a/deps/ledger-zxlib/include/zxmacros.h b/deps/ledger-zxlib/include/zxmacros.h index b1ea9fda..3c13faf7 100644 --- a/deps/ledger-zxlib/include/zxmacros.h +++ b/deps/ledger-zxlib/include/zxmacros.h @@ -236,11 +236,11 @@ __Z_INLINE void array_to_hexstr(char *dst, const uint8_t *src, uint8_t count) { *dst = 0; // terminate string } -__Z_INLINE void pageString(char *outValue, uint16_t outValueLen, - char *inValue, uint8_t pageIdx, uint8_t *pageCount) { +__Z_INLINE void pageStringExt(char *outValue, uint16_t outValueLen, + const char *inValue, uint16_t inValueLen, + uint8_t pageIdx, uint8_t *pageCount) { MEMSET(outValue, 0, outValueLen); outValueLen--; // leave space for NULL termination - uint16_t inValueLen = strlen(inValue); *pageCount = (inValueLen / outValueLen); const uint16_t lastChunkLen = (inValueLen % outValueLen); @@ -257,6 +257,12 @@ __Z_INLINE void pageString(char *outValue, uint16_t outValueLen, } } +__Z_INLINE void pageString(char *outValue, uint16_t outValueLen, + const char *inValue, + uint8_t pageIdx, uint8_t *pageCount) { + pageStringExt(outValue, outValueLen, inValue, strlen(inValue), pageIdx, pageCount); +} + /////////////////////// /////////////////////// /////////////////////// diff --git a/src/lib/crypto.c b/src/lib/crypto.c index ea158c98..cd757db0 100644 --- a/src/lib/crypto.c +++ b/src/lib/crypto.c @@ -26,7 +26,13 @@ uint32_t bip32Path[BIP32_LEN_DEFAULT]; uint8_t bech32_hrp_len; char bech32_hrp[MAX_BECH32_HRP_LEN + 1]; +#if defined(TARGET_NANOS) #define SAFE_HEARTBEAT(X) io_seproxyhal_io_heartbeat(); X; io_seproxyhal_io_heartbeat(); +#endif + +#if defined(TARGET_NANOX) +#define SAFE_HEARTBEAT(X) X; +#endif #if defined(TARGET_NANOS) || defined(TARGET_NANOX) void crypto_extractPublicKey(uint32_t bip32Path[BIP32_LEN_DEFAULT], uint8_t *pubKey){ @@ -72,7 +78,6 @@ uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t BIP32_LEN_DEFAULT, privateKeyData, NULL)); - io_seproxyhal_io_heartbeat(); SAFE_HEARTBEAT(cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &cx_privateKey)); // Sign diff --git a/src/lib/json/tx_display.c b/src/lib/json/tx_display.c index 284fd2e4..64c6b4e5 100644 --- a/src/lib/json/tx_display.c +++ b/src/lib/json/tx_display.c @@ -124,17 +124,17 @@ int16_t tx_display_numItems() { } // This function assumes that the tx_ctx has been set properly -parser_error_t tx_display_set_query(uint16_t itemIndex, uint16_t *outStartToken) { +parser_error_t tx_display_set_query(uint16_t displayIdx, uint16_t *outStartToken) { _indexRootFields(); - if (itemIndex < 0 || itemIndex >= display_cache.numItems) { + if (displayIdx < 0 || displayIdx >= display_cache.numItems) { return parser_no_data; } parser_tx_obj.query.item_index = 0; uint16_t root_index = 0; - for (uint16_t i = 0; i < itemIndex; i++) { + for (uint16_t i = 0; i < displayIdx; i++) { parser_tx_obj.query.item_index++; if (parser_tx_obj.query.item_index >= display_cache.num_subpages[root_index]) { parser_tx_obj.query.item_index = 0; diff --git a/src/lib/json/tx_display.h b/src/lib/json/tx_display.h index cd8741fe..82fee99c 100644 --- a/src/lib/json/tx_display.h +++ b/src/lib/json/tx_display.h @@ -29,7 +29,7 @@ extern "C" { /// This is the main function called from ledger that updates key and value strings /// that are going to be displayed in the UI. /// This function assumes that the tx_ctx has been properly set -parser_error_t tx_display_set_query(uint16_t itemIndex, uint16_t *outStartToken); +parser_error_t tx_display_set_query(uint16_t displayIdx, uint16_t *outStartToken); /// Return number of UI pages that we'll have for the current json transaction (only if the tx is valid) /// \return number of pages (msg pages + 5 required) diff --git a/src/lib/json/tx_parser.c b/src/lib/json/tx_parser.c index e7ad70b9..6296861b 100644 --- a/src/lib/json/tx_parser.c +++ b/src/lib/json/tx_parser.c @@ -50,34 +50,18 @@ __always_inline void strcat_chunk_s(char *dst, uint16_t dst_max, const char *src /////////////////////////// /////////////////////////// -__always_inline parser_error_t tx_getToken(uint16_t token_index, - char *out_val, uint16_t out_val_len, - uint16_t chunk_index, uint8_t *numChunks) { +parser_error_t tx_getToken(uint16_t token_index, + char *out_val, uint16_t out_val_len, + uint8_t pageIdx, uint8_t *pageCount) { const int16_t token_start = parser_tx_obj.json.tokens[token_index].start; const int16_t token_end = parser_tx_obj.json.tokens[token_index].end; - const int16_t token_len = token_end - token_start; - *numChunks = (token_len / (out_val_len - 1)) + 1; - if (token_len > 0 && (token_len % (out_val_len - 1) == 0)) - numChunks--; + pageStringExt(out_val, out_val_len, + parser_tx_obj.tx + token_start, token_end - token_start, + pageIdx, pageCount); - out_val[0] = '\0'; // flush - if (chunk_index >= *numChunks) { + if (pageIdx >= *pageCount) return parser_no_data; - } - - const int16_t chunk_start = token_start + chunk_index * (out_val_len - 1); - int16_t chunk_len = token_end - chunk_start; - if (chunk_len < 0) { - return parser_no_data; - } - - if (chunk_len > out_val_len - 1) { - chunk_len = out_val_len - 1; - } - - MEMCPY(out_val, parser_tx_obj.tx + chunk_start, chunk_len); - out_val[chunk_len] = 0; return parser_ok; } diff --git a/src/lib/json/tx_parser.h b/src/lib/json/tx_parser.h index 4db50448..da91cd02 100644 --- a/src/lib/json/tx_parser.h +++ b/src/lib/json/tx_parser.h @@ -48,9 +48,10 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to parser_error_t tx_traverse(int16_t root_token_index, uint8_t *numChunks); // Retrieves the value for the corresponding token index. If the value goes beyond val_len, the chunk_idx will be used + parser_error_t tx_getToken(uint16_t token_index, char *out_val, uint16_t out_val_len, - uint16_t chunk_index, uint8_t *numChunks); + uint8_t pageIdx, uint8_t *pageCount); //--------------------------------------------- #ifdef __cplusplus diff --git a/src/lib/parser.c b/src/lib/parser.c index ea84f507..9f665c3f 100644 --- a/src/lib/parser.c +++ b/src/lib/parser.c @@ -77,31 +77,34 @@ __Z_INLINE bool_t parser_isAmount(char *key) { __Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, char *outVal, uint16_t outValLen, uint8_t pageIdx, uint8_t *pageCount) { - const uint16_t numElementsList = array_get_element_count(amountToken, &parser_tx_obj.json); - const uint16_t numElementsDict = array_get_element_count(amountToken + 1, &parser_tx_obj.json); + uint16_t numElements = array_get_element_count(amountToken, &parser_tx_obj.json); + if (parser_tx_obj.json.tokens[amountToken].type == JSMN_ARRAY){ + amountToken++; + } - if (numElementsList != 1 || numElementsDict != 4) + numElements = array_get_element_count(amountToken, &parser_tx_obj.json); + if (numElements != 4) return parser_unexpected_field; - if (parser_tx_obj.json.tokens[amountToken + 1].type != JSMN_OBJECT) + if (parser_tx_obj.json.tokens[amountToken].type != JSMN_OBJECT) return parser_unexpected_field; - if (!parser_areEqual(amountToken + 2, "amount")) + if (!parser_areEqual(amountToken + 1, "amount")) return parser_unexpected_field; - if (!parser_areEqual(amountToken + 4, "denom")) + if (!parser_areEqual(amountToken + 3, "denom")) return parser_unexpected_field; char bufferUI[160]; MEMSET(outVal, 0, outValLen); MEMSET(bufferUI, 0, sizeof(bufferUI)); - const char *amountPtr = parser_tx_obj.tx + parser_tx_obj.json.tokens[amountToken + 3].start; - const uint16_t amountLen = parser_tx_obj.json.tokens[amountToken + 3].end - - parser_tx_obj.json.tokens[amountToken + 3].start; - const char *denomPtr = parser_tx_obj.tx + parser_tx_obj.json.tokens[amountToken + 5].start; - const uint16_t denomLen = parser_tx_obj.json.tokens[amountToken + 5].end - - parser_tx_obj.json.tokens[amountToken + 5].start; + const char *amountPtr = parser_tx_obj.tx + parser_tx_obj.json.tokens[amountToken + 2].start; + const uint16_t amountLen = parser_tx_obj.json.tokens[amountToken + 2].end - + parser_tx_obj.json.tokens[amountToken + 2].start; + const char *denomPtr = parser_tx_obj.tx + parser_tx_obj.json.tokens[amountToken + 4].start; + const uint16_t denomLen = parser_tx_obj.json.tokens[amountToken + 4].end - + parser_tx_obj.json.tokens[amountToken + 4].start; if (sizeof(bufferUI) < amountLen + denomLen + 2) { return parser_unexpected_buffer_end; @@ -124,15 +127,10 @@ parser_error_t parser_getItem(parser_context_t *ctx, MEMSET(outKey, 0, outKeyLen); MEMSET(outVal, 0, outValLen); + INIT_QUERY(outKey, outKeyLen, outVal, outValLen, pageIdx) snprintf(outKey, outKeyLen, "?"); snprintf(outVal, outValLen, "?"); - if (displayIdx < 0) { - return parser_no_data; - } - - INIT_QUERY(outKey, outKeyLen, outVal, outValLen, pageIdx) - uint16_t displayStartToken; parser_error_t err = tx_display_set_query(displayIdx, &displayStartToken); if (err != parser_ok) @@ -144,7 +142,6 @@ parser_error_t parser_getItem(parser_context_t *ctx, uint16_t ret_value_token_index; err = tx_traverse_find(displayStartToken, &ret_value_token_index); - if (err != parser_ok) return err; @@ -158,8 +155,12 @@ parser_error_t parser_getItem(parser_context_t *ctx, tx_display_make_friendly(); - if (err != parser_ok) - return err; + if (*pageCount > 1) { + uint8_t keyLen = strlen(outKey); + if (keyLen < outKeyLen) { + snprintf(outKey + keyLen, outKeyLen - keyLen, " [%d/%d]", pageIdx + 1, *pageCount); + } + } return err; } diff --git a/src/tx.c b/src/tx.c index a359c398..be143129 100644 --- a/src/tx.c +++ b/src/tx.c @@ -19,6 +19,7 @@ #include "buffering.h" #include "lib/parser.h" #include +#include "zxmacros.h" #if defined(TARGET_NANOX) #define RAM_BUFFER_SIZE 8192 @@ -96,14 +97,18 @@ uint8_t tx_getNumItems() { tx_error_t tx_getItem(int8_t displayIdx, char *outKey, uint16_t outKeyLen, - char *outValue, uint16_t outValueLen, + char *outVal, uint16_t outValLen, uint8_t pageIdx, uint8_t *pageCount) { tx_error_t err = tx_no_error; + if (displayIdx < 0 || displayIdx > tx_getNumItems() ) { + return tx_no_data; + } + err = (tx_error_t) parser_getItem(&ctx_parsed_tx, displayIdx, outKey, outKeyLen, - outValue, outValueLen, + outVal, outValLen, pageIdx, pageCount); // Convert error codes @@ -113,12 +118,5 @@ tx_error_t tx_getItem(int8_t displayIdx, if (err == parser_ok) return tx_no_error; - if (*pageCount > 1) { - uint8_t keyLen = strlen(outKey); - if (keyLen < outKeyLen) { - snprintf(outKey + keyLen, outKeyLen - keyLen, " [%d/%d]", pageIdx + 1, *pageCount); - } - } - return err; } From 02dcbf80275990ccd4397be5bb9f33403cee9f8d Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sat, 12 Oct 2019 20:32:52 +0200 Subject: [PATCH 27/78] reducing max tokens in Nano X --- src/lib/json/json_parser.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/json/json_parser.h b/src/lib/json/json_parser.h index 6994e6b2..79d92267 100644 --- a/src/lib/json/json_parser.h +++ b/src/lib/json/json_parser.h @@ -31,7 +31,7 @@ extern "C" { #endif /// Max number of accepted tokens in the JSON input -#define MAX_NUMBER_OF_TOKENS 2048 +#define MAX_NUMBER_OF_TOKENS 1536 // we must limit the number #if defined(TARGET_NANOS) From 7297397ee8c0cce65d2508574e85103d9f16d4b3 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sat, 12 Oct 2019 21:15:57 +0200 Subject: [PATCH 28/78] using explicit_bzero as per guidelines --- deps/ledger-zxlib/include/zxmacros.h | 9 +-- deps/ledger-zxlib/src/zxmacros.c | 1 + src/lib/crypto.c | 90 ++++++++++++++++------------ src/lib/json/json_parser.c | 2 +- src/lib/json/tx_display.c | 2 +- src/lib/json/tx_display.h | 2 +- 6 files changed, 61 insertions(+), 45 deletions(-) diff --git a/deps/ledger-zxlib/include/zxmacros.h b/deps/ledger-zxlib/include/zxmacros.h index 3c13faf7..6522b8a7 100644 --- a/deps/ledger-zxlib/include/zxmacros.h +++ b/deps/ledger-zxlib/include/zxmacros.h @@ -16,6 +16,9 @@ #pragma once +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -80,8 +83,6 @@ void __logstack(); #else -#include - #define MEMMOVE memmove #define MEMSET memset #define MEMCPY memcpy @@ -112,7 +113,7 @@ void __logstack(); #define NUM_TO_STR(TYPE) __Z_INLINE const char * TYPE##_to_str(char *data, int dataLen, TYPE##_t number) { \ if (dataLen < 2) return "Buffer too small"; \ - MEMSET(data, 0, dataLen); \ + explicit_bzero(data, dataLen); \ char *p = data; \ if (number < 0) { *(p++) = '-'; data++; } \ else if (number == 0) { *(p++) = '0'; } \ @@ -239,7 +240,7 @@ __Z_INLINE void array_to_hexstr(char *dst, const uint8_t *src, uint8_t count) { __Z_INLINE void pageStringExt(char *outValue, uint16_t outValueLen, const char *inValue, uint16_t inValueLen, uint8_t pageIdx, uint8_t *pageCount) { - MEMSET(outValue, 0, outValueLen); + explicit_bzero(outValue, outValueLen); outValueLen--; // leave space for NULL termination *pageCount = (inValueLen / outValueLen); const uint16_t lastChunkLen = (inValueLen % outValueLen); diff --git a/deps/ledger-zxlib/src/zxmacros.c b/deps/ledger-zxlib/src/zxmacros.c index 65fb22c4..99187a6a 100644 --- a/deps/ledger-zxlib/src/zxmacros.c +++ b/deps/ledger-zxlib/src/zxmacros.c @@ -15,6 +15,7 @@ ********************************************************************************/ #include "zxmacros.h" #include "utf8.h" +#include #ifdef LEDGER_SPECIFIC #include diff --git a/src/lib/crypto.c b/src/lib/crypto.c index cd757db0..9a1b26cb 100644 --- a/src/lib/crypto.c +++ b/src/lib/crypto.c @@ -36,22 +36,28 @@ char bech32_hrp[MAX_BECH32_HRP_LEN + 1]; #if defined(TARGET_NANOS) || defined(TARGET_NANOX) void crypto_extractPublicKey(uint32_t bip32Path[BIP32_LEN_DEFAULT], uint8_t *pubKey){ - cx_ecfp_public_key_t cx_publicKey; - cx_ecfp_private_key_t cx_privateKey; - uint8_t privateKeyData[32]; - - SAFE_HEARTBEAT(os_perso_derive_node_bip32(CX_CURVE_256K1, - bip32Path, - BIP32_LEN_DEFAULT, - privateKeyData, NULL)); - - ////////////////////// - SAFE_HEARTBEAT(cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &cx_privateKey)); - SAFE_HEARTBEAT(cx_ecfp_init_public_key(CX_CURVE_256K1, NULL, 0, &cx_publicKey)); - SAFE_HEARTBEAT(cx_ecfp_generate_pair(CX_CURVE_256K1, &cx_publicKey, &cx_privateKey, 1)); - - MEMSET(&cx_privateKey, 0, sizeof(cx_privateKey)); - MEMSET(privateKeyData, 0, 32); + cx_ecfp_public_key_t cx_publicKey; + cx_ecfp_private_key_t cx_privateKey; + uint8_t privateKeyData[32]; + BEGIN_TRY + { + TRY { + SAFE_HEARTBEAT(os_perso_derive_node_bip32(CX_CURVE_256K1, + bip32Path, + BIP32_LEN_DEFAULT, + privateKeyData, NULL)); + + ////////////////////// + SAFE_HEARTBEAT(cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &cx_privateKey)); + SAFE_HEARTBEAT(cx_ecfp_init_public_key(CX_CURVE_256K1, NULL, 0, &cx_publicKey)); + SAFE_HEARTBEAT(cx_ecfp_generate_pair(CX_CURVE_256K1, &cx_publicKey, &cx_privateKey, 1)); + } + FINALLY { + explicit_bzero(&cx_privateKey, sizeof(cx_privateKey)); + explicit_bzero(privateKeyData, 32); + }; + } + END_TRY; // Format pubkey for (int i = 0; i < 32; i++) { @@ -70,30 +76,38 @@ uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t uint8_t message_digest[CX_SHA256_SIZE]; SAFE_HEARTBEAT(cx_hash_sha256(message, messageLen, message_digest, CX_SHA256_SIZE)); - // Generate keys cx_ecfp_private_key_t cx_privateKey; uint8_t privateKeyData[32]; - SAFE_HEARTBEAT(os_perso_derive_node_bip32(CX_CURVE_256K1, - bip32Path, - BIP32_LEN_DEFAULT, - privateKeyData, NULL)); - - SAFE_HEARTBEAT(cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &cx_privateKey)); - - // Sign - unsigned int info = 0; - SAFE_HEARTBEAT( - int signatureLength = cx_ecdsa_sign(&cx_privateKey, - CX_RND_RFC6979 | CX_LAST, - CX_SHA256, - message_digest, - CX_SHA256_SIZE, - signature, - signatureMaxlen, - &info)) - - MEMSET(&cx_privateKey, 0, sizeof(cx_privateKey)); - MEMSET(privateKeyData, 0, 32); + int signatureLength; + BEGIN_TRY + { + TRY { + // Generate keys + SAFE_HEARTBEAT(os_perso_derive_node_bip32(CX_CURVE_256K1, + bip32Path, + BIP32_LEN_DEFAULT, + privateKeyData, NULL)); + + SAFE_HEARTBEAT(cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &cx_privateKey)); + + // Sign + unsigned int info = 0; + SAFE_HEARTBEAT( + signatureLength = cx_ecdsa_sign(&cx_privateKey, + CX_RND_RFC6979 | CX_LAST, + CX_SHA256, + message_digest, + CX_SHA256_SIZE, + signature, + signatureMaxlen, + &info)) + } + FINALLY { + explicit_bzero(&cx_privateKey, sizeof(cx_privateKey)); + explicit_bzero(privateKeyData, 32); + } + } + END_TRY; return signatureLength; } diff --git a/src/lib/json/json_parser.c b/src/lib/json/json_parser.c index 66ec8cb6..eeddf175 100644 --- a/src/lib/json/json_parser.c +++ b/src/lib/json/json_parser.c @@ -35,7 +35,7 @@ parser_error_t json_parse_s(parsed_json_t *parsed_json, jsmn_parser parser; jsmn_init(&parser); - MEMSET(parsed_json, 0, sizeof(parsed_json_t)); + explicit_bzero(parsed_json, sizeof(parsed_json_t)); parsed_json->buffer = buffer; parsed_json->bufferLen = bufferLen; diff --git a/src/lib/json/tx_display.c b/src/lib/json/tx_display.c index 64c6b4e5..2840adcb 100644 --- a/src/lib/json/tx_display.c +++ b/src/lib/json/tx_display.c @@ -69,7 +69,7 @@ void _indexRootFields() { } // Clear cache - memset(&display_cache, 0, sizeof(display_cache_t)); + explicit_bzero(&display_cache, sizeof(display_cache_t)); // Calculate pages for (int8_t idx = 0; idx < NUM_REQUIRED_ROOT_PAGES; idx++) { diff --git a/src/lib/json/tx_display.h b/src/lib/json/tx_display.h index 82fee99c..3a9055ea 100644 --- a/src/lib/json/tx_display.h +++ b/src/lib/json/tx_display.h @@ -23,7 +23,7 @@ extern "C" { #endif #define STRNCPY_S(DST, SRC, DST_SIZE) \ - MEMSET(DST, 0, DST_SIZE); \ + explicit_bzero(DST, DST_SIZE); \ strncpy(DST, SRC, DST_SIZE - 1); /// This is the main function called from ledger that updates key and value strings From 5f4aa0cfda9e18f39cfbbfdc38e5259cda9cfc74 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Mon, 14 Oct 2019 17:59:52 +0200 Subject: [PATCH 29/78] refactoring + security improvements --- deps/ledger-zxlib/.circleci/config.yml | 19 +++ deps/ledger-zxlib/CMakeLists.txt | 2 +- deps/ledger-zxlib/include/hexutils.h | 30 ++++ deps/ledger-zxlib/include/zxmacros.h | 17 +- deps/ledger-zxlib/include/zxtypes.h | 30 ++++ deps/ledger-zxlib/src/hexutils.c | 54 +++++++ deps/ledger-zxlib/src/zxmacros.c | 1 - deps/ledger-zxlib/tests/hexutils.cpp | 57 +++++++ docs/APDUSPEC.md | 48 ++---- docs/UISPEC.md | 207 ------------------------- src/.gitignore | 46 ++++++ src/actions.c | 2 +- src/app_main.c | 63 ++++---- src/app_main.h | 10 +- src/lib/cosmos.h | 12 +- src/lib/crypto.c | 59 +++---- src/lib/crypto.h | 12 +- src/lib/parser.c | 21 +-- src/lib/parser_common.h | 2 +- src/lib/parser_impl.c | 7 +- src/view_s.c | 2 +- 21 files changed, 346 insertions(+), 355 deletions(-) create mode 100644 deps/ledger-zxlib/.circleci/config.yml create mode 100644 deps/ledger-zxlib/include/hexutils.h create mode 100644 deps/ledger-zxlib/include/zxtypes.h create mode 100644 deps/ledger-zxlib/src/hexutils.c create mode 100644 deps/ledger-zxlib/tests/hexutils.cpp delete mode 100644 docs/UISPEC.md create mode 100644 src/.gitignore diff --git a/deps/ledger-zxlib/.circleci/config.yml b/deps/ledger-zxlib/.circleci/config.yml new file mode 100644 index 00000000..64441bcb --- /dev/null +++ b/deps/ledger-zxlib/.circleci/config.yml @@ -0,0 +1,19 @@ +version: 2 +jobs: + build: + docker: + - image: ubuntu:18.04 + steps: + - run: + name: Install dependencies + command: apt update && apt-get -y install build-essential git sudo wget cmake libssl-dev libgmp-dev autoconf libtool + - checkout + - run: git submodule update --init --recursive + - run: cmake -DDISABLE_DOCKER_BUILDS=ON . && make + - run: export GTEST_COLOR=1 && ctest -VV + +workflows: + version: 2 + build_all: + jobs: + - build diff --git a/deps/ledger-zxlib/CMakeLists.txt b/deps/ledger-zxlib/CMakeLists.txt index cad1ebb3..2e140f7d 100644 --- a/deps/ledger-zxlib/CMakeLists.txt +++ b/deps/ledger-zxlib/CMakeLists.txt @@ -55,4 +55,4 @@ target_include_directories(zxlib_tests PRIVATE target_link_libraries(zxlib_tests gtest_main zxlib) -add_test(ZXLIB_TESTS ledger_qrl_tests) +add_test(ZXLIB_TESTS zxlib_tests) diff --git a/deps/ledger-zxlib/include/hexutils.h b/deps/ledger-zxlib/include/hexutils.h new file mode 100644 index 00000000..e8010842 --- /dev/null +++ b/deps/ledger-zxlib/include/hexutils.h @@ -0,0 +1,30 @@ +/******************************************************************************* +* (c) 2018 ZondaX GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +size_t parseHexString(const char *s, uint8_t *out); + +#ifdef __cplusplus +} +#endif diff --git a/deps/ledger-zxlib/include/zxmacros.h b/deps/ledger-zxlib/include/zxmacros.h index 6522b8a7..a2938fe0 100644 --- a/deps/ledger-zxlib/include/zxmacros.h +++ b/deps/ledger-zxlib/include/zxmacros.h @@ -16,13 +16,13 @@ #pragma once -#include -#include - #ifdef __cplusplus extern "C" { #endif +#include "string.h" +extern void explicit_bzero (void *__s, size_t __n) __THROW __nonnull ((1)); + #if defined(LEDGER_SPECIFIC) #include "bolos_target.h" #endif @@ -71,6 +71,7 @@ extern "C" { #define MEMSET os_memset #define MEMCPY os_memcpy #define MEMCPY_NV nvm_write +#define MEMZERO explicit_bzero void debug_printf(void* buffer); @@ -87,6 +88,7 @@ void __logstack(); #define MEMSET memset #define MEMCPY memcpy #define MEMCPY_NV memcpy +#define MEMZERO explicit_bzero #define LOG(str) #define LOGSTACK() #endif @@ -113,7 +115,7 @@ void __logstack(); #define NUM_TO_STR(TYPE) __Z_INLINE const char * TYPE##_to_str(char *data, int dataLen, TYPE##_t number) { \ if (dataLen < 2) return "Buffer too small"; \ - explicit_bzero(data, dataLen); \ + MEMZERO(data, dataLen); \ char *p = data; \ if (number < 0) { *(p++) = '-'; data++; } \ else if (number == 0) { *(p++) = '0'; } \ @@ -240,7 +242,7 @@ __Z_INLINE void array_to_hexstr(char *dst, const uint8_t *src, uint8_t count) { __Z_INLINE void pageStringExt(char *outValue, uint16_t outValueLen, const char *inValue, uint16_t inValueLen, uint8_t pageIdx, uint8_t *pageCount) { - explicit_bzero(outValue, outValueLen); + MEMZERO(outValue, outValueLen); outValueLen--; // leave space for NULL termination *pageCount = (inValueLen / outValueLen); const uint16_t lastChunkLen = (inValueLen % outValueLen); @@ -269,11 +271,6 @@ __Z_INLINE void pageString(char *outValue, uint16_t outValueLen, /////////////////////// /////////////////////// -typedef enum { - FALSE, - TRUE -} bool_t; - size_t asciify(char *utf8_in); size_t asciify_ext(const char *utf8_in, char *ascii_only_out); diff --git a/deps/ledger-zxlib/include/zxtypes.h b/deps/ledger-zxlib/include/zxtypes.h new file mode 100644 index 00000000..1712c46f --- /dev/null +++ b/deps/ledger-zxlib/include/zxtypes.h @@ -0,0 +1,30 @@ +/******************************************************************************* +* (c) 2018 ZondaX GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + bool_false = 0, + bool_true = 1, +} bool_t; + +#ifdef __cplusplus +} +#endif diff --git a/deps/ledger-zxlib/src/hexutils.c b/deps/ledger-zxlib/src/hexutils.c new file mode 100644 index 00000000..7080266e --- /dev/null +++ b/deps/ledger-zxlib/src/hexutils.c @@ -0,0 +1,54 @@ +/******************************************************************************* +* (c) 2018 ZondaX GmbH +* +* 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 +#include +#include "hexutils.h" + +uint8_t hex2dec(char c, char *out) { + c = tolower(c); + + if (!isxdigit(c)) { + return -1; + } + + if (isdigit(c)) { + *out = c - '0'; + return 0; + } + + *out = c - 'a' + 10; + return 0; +} + +size_t parseHexString(const char *s, uint8_t *out) { + size_t len = strlen(s); + if (len % 2 == 1) { + return 0; + } + + for (size_t i = 0; i < len; i += 2) { + char tmp1, tmp2; + if (hex2dec(s[i], &tmp1)) + return 0; + if (hex2dec(s[i + 1], &tmp2)) + return 0; + + out[i >> 1u] = (tmp1 << 4u) + tmp2; + } + + return len >> 1u; +}; diff --git a/deps/ledger-zxlib/src/zxmacros.c b/deps/ledger-zxlib/src/zxmacros.c index 99187a6a..65fb22c4 100644 --- a/deps/ledger-zxlib/src/zxmacros.c +++ b/deps/ledger-zxlib/src/zxmacros.c @@ -15,7 +15,6 @@ ********************************************************************************/ #include "zxmacros.h" #include "utf8.h" -#include #ifdef LEDGER_SPECIFIC #include diff --git a/deps/ledger-zxlib/tests/hexutils.cpp b/deps/ledger-zxlib/tests/hexutils.cpp new file mode 100644 index 00000000..58e5ca1a --- /dev/null +++ b/deps/ledger-zxlib/tests/hexutils.cpp @@ -0,0 +1,57 @@ +/******************************************************************************* +* (c) 2018 ZondaX GmbH +* +* 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 "hexutils.h" + +TEST(HEXUTILS, parseHexString) { + char s[] = "1234567890"; + uint8_t data[100]; + + auto length = parseHexString(s, data); + + ASSERT_THAT(length, testing::Eq(5)); + + ASSERT_THAT(data[0], testing::Eq(0x12)); + ASSERT_THAT(data[1], testing::Eq(0x34)); + ASSERT_THAT(data[2], testing::Eq(0x56)); + ASSERT_THAT(data[3], testing::Eq(0x78)); + ASSERT_THAT(data[4], testing::Eq(0x90)); +} + +TEST(HEXUTILS, parseHexString2) { + char s[] = "be333be7ee"; + uint8_t data[100]; + + auto length = parseHexString(s, data); + + ASSERT_THAT(length, testing::Eq(5)); + + ASSERT_THAT(data[0], testing::Eq(0xbe)); + ASSERT_THAT(data[1], testing::Eq(0x33)); + ASSERT_THAT(data[2], testing::Eq(0x3b)); + ASSERT_THAT(data[3], testing::Eq(0xe7)); + ASSERT_THAT(data[4], testing::Eq(0xee)); +} diff --git a/docs/APDUSPEC.md b/docs/APDUSPEC.md index 4d81bded..c7cdefb2 100644 --- a/docs/APDUSPEC.md +++ b/docs/APDUSPEC.md @@ -63,34 +63,6 @@ The general structure of commands and responses is as follows: -------------- -### PUBLIC_KEY_SECP256K1 (deprecated) - -#### Command - -| Field | Type | Content | Expected | -| ---------- | -------- | ---------------------- | --------- | -| CLA | byte (1) | Application Identifier | 0x55 | -| INS | byte (1) | Instruction ID | 0x01 | -| P1 | byte (1) | Parameter 1 | ignored | -| P2 | byte (1) | Parameter 2 | ignored | -| L | byte (1) | Bytes in payload | (depends) | -| PL | byte (1) | Derivation Path Length | 3<=PL<=10 | -| Path[0] | byte (4) | Derivation Path Data | 44 | -| Path[1] | byte (4) | Derivation Path Data | 118 | -| .. | byte (4) | Derivation Path Data | | -| Path[PL-1] | byte (4) | Derivation Path Data | | - -First three items in the derivation path will be hardened automatically hardened - -#### Response - -| Field | Type | Content | Note | -| ------- | --------- | ----------- | ------------------------ | -| PK | byte (65) | Public Key | | -| SW1-SW2 | byte (2) | Return code | see list of return codes | - --------------- - ### SIGN_SECP256K1 #### Command @@ -99,9 +71,10 @@ First three items in the derivation path will be hardened automatically hardened | ----- | -------- | ---------------------- | --------- | | CLA | byte (1) | Application Identifier | 0x55 | | INS | byte (1) | Instruction ID | 0x02 | -| P1 | byte (1) | Packet Current Index | | -| P2 | byte (1) | Packet Total Count | -| | +| P1 | byte (1) | Payload desc | 0 = init | +| | | | 1 = add | +| | | | 2 = last | +| P2 | byte (1) | ---- | not used | | L | byte (1) | Bytes in payload | (depends) | The first packet/chunk includes only the derivation path @@ -112,12 +85,11 @@ All other packets/chunks should contain message to sign | Field | Type | Content | Expected | | ---------- | -------- | ---------------------- | --------- | -| PL | byte (1) | Derivation Path Length | 3<=PL<=10 | | Path[0] | byte (4) | Derivation Path Data | 44 | | Path[1] | byte (4) | Derivation Path Data | 118 | -| .. | byte (4) | Derivation Path Data | | -| Path[PL-1] | byte (4) | Derivation Path Data | | -| Message | bytes... | Message to Sign | | +| Path[2] | byte (4) | Derivation Path Data | ? | +| Path[3] | byte (4) | Derivation Path Data | ? | +| Path[4] | byte (4) | Derivation Path Data | ? | *Other Chunks/Packets* @@ -147,11 +119,11 @@ All other packets/chunks should contain message to sign | L | byte (1) | Bytes in payload | (depends) | | HRP_LEN | byte(1) | Bech32 HRP Length | 1<=HRP_LEN<=83 | | HRP | byte (HRP_LEN) | Bech32 HRP | | -| PL | byte (1) | Derivation Path Length | 3<=PL<=10 | | Path[0] | byte (4) | Derivation Path Data | 44 | | Path[1] | byte (4) | Derivation Path Data | 118 | -| .. | byte (4) | Derivation Path Data | | -| Path[PL-1] | byte (4) | Derivation Path Data | | +| Path[2] | byte (4) | Derivation Path Data | ? | +| Path[3] | byte (4) | Derivation Path Data | ? | +| Path[4] | byte (4) | Derivation Path Data | ? | First three items in the derivation path will be hardened automatically hardened diff --git a/docs/UISPEC.md b/docs/UISPEC.md deleted file mode 100644 index ad39e0f4..00000000 --- a/docs/UISPEC.md +++ /dev/null @@ -1,207 +0,0 @@ -UI Specification -------------------------- - -## Terminology: - -**Left click** - clicking and releasing left button - -**Right click** - clicking and releasing right button - -**Double click** - clicking and releasing both left and right - -**Left hold** - clicking and holding left button - -**Right hold** - clicking and holding right button - -**Double hold** - clicking and holding both left and right - -## UI pages: -### A. 'Welcome' page - -#### Layout: -Screen 1. (default) -``` -LINE1: -LINE2: Tendermint -LINE3: [tendermint icon] Cosmos TEST! [down icon] -``` - -Note: Test only appears when the app has been compiled in test mode. - -Screen 2. -``` -LINE1: -LINE2: [up icon] About [down icon] -LINE3: -``` - -Screen 3. -``` -LINE1: -LINE2: [up icon] [quit icon] Quit app -LINE3: -``` -#### Interface: -##### Left click -Moves to the previous screen. -##### Right click -Moves to the next screen. -##### Left and Right clicked together on the Screen #3 -Exits the app. - -### B. 'Received Transaction' page - -#### Layout: -Screen 1. (default) -``` -LINE1: -LINE2: View transaction [down icon] -LINE3: -``` -Screen 2. -``` -LINE1: -LINE2: [up icon] Sign transaction [down icon] -LINE3: -``` - -Screen 3. -``` -LINE1: -LINE2: [up icon] [reject icon] Reject -LINE3: -``` - -#### Interface: -##### Left click -Moves to the previous screen. -##### Right click -Moves to the next screen. -##### Double click on the Screen #1 -Switches UI to the 'View Transaction' page -##### Double click on the Screen #2 -- signs transaction -- returns signed transaction to the client app -- switches UI to the 'Welcome' page -##### Double click on the Screen #3 -- rejects transaction -- switches UI to the 'Welcome' page - -### C. 'View Transaction' page - -#### Layout: - -Screen (template) -``` -LINE1: [left icon] SECP256K1 - 0n/0k [right icon] -LINE2: title (json element key) -LINE3: value (json element value) -``` - -Line1 contains the page info - the signature type (here SECP256K1) followed by the page index (n) and the total number of pages (k). - -Line2 contains the title - key element from the json transaction for a particular page. - -Line3 contains the value - value element from the json transaction for a particular page. - -##### Here's the page breakdown for a simple json transaction: - -Screen 1. (default) -``` -LINE1: [left icon] SECP256K1 - 01/08 [right icon] -LINE2: chain_id -LINE3: test-chain-1 -``` - -Screen 2. -``` -LINE1: [left icon] SECP256K1 - 02/09 [right icon] -LINE2: account_number -LINE3: 238 -``` - -Screen 3. -``` -LINE1: [left icon] SECP256K1 - 03/09 [right icon] -LINE2: sequence -LINE3: 1 -``` - -Screen 4. -``` -LINE1: [left icon] SECP256K1 - 04/09 [right icon] -LINE2: fee -LINE3: {"amount":[{"denom":"photon", "amount":5}], "gas":10000} -``` - -Screen 5. -``` -LINE1: [left icon] SECP256K1 - 05/09 [right icon] -LINE2: msgs/0/inputs/address -LINE3: 69FE2314BAC34EF -``` - -Screen 6. -``` -LINE1: [left icon] SECP256K1 - 06/08 [right icon] -LINE2: msgs/0/inputs/coins -LINE3: 69FE2314BAC34EF -``` - -Screen 7. -``` -LINE1: [left icon] SECP256K1 - 07/08 [right icon] -LINE2: msgs/0/outputs/address -LINE3: 69FE2314BAC34EF -``` - -Screen 8. -``` -LINE1: [left icon] SECP256K1 - 08/09 [right icon] -LINE2: msgs/0/outputs/coins -LINE3: 69FE2314BAC34EF -``` - -Screen 9. -``` -LINE1: [left icon] SECP256K1 - 09/09 [right icon] -LINE2: memo -LINE3: for_coffee -``` - -#### Scrolling -Ledger screen can only fit around 20 characters and therefore we need a way for displaying the key and the value strings that are longer. Here we assume that the page info will always fit the screen line. - -Ledger supports the smooth pixel scrolling which is enabled by default for the line #3 (i.e. the line that holds the json value). If a text is longer than (around) 20 characters, then Ledger will automatically start scrolling the line. - -We are currently using 256 bytes long buffer to keep the value string. If the value read from the json transaction is longer than that, then we split it into 256 chunks and display and scroll each chunk individually. - -Here's an example: - -Screen 3. -``` -LINE1: [left icon] SECP256K1 - 03/08 [right icon] -LINE2: fee - 01/02 -LINE3: {"amount":[{"denom":"photon", "a [first 256 bytes of the value] -``` - -Screen 3a. -``` -LINE1: [left icon] SECP256K1 - 03/08 [right icon] -LINE2: fee - 02/02 -LINE3: mount":5}], "gas":10000} [the next 256 bytes of the value] -``` - -We'll use term: 'Value Chunk Preview' to describe this mode. - -#### Interface: -##### Left click -Moves to the previous screen, unless the current screen is #1, in which case it switches back to the 'Received Transaction' page. -##### Right click -Moves to the next screen, unless the current screen is the last one, in which case it switches back to the 'Received Transaction' page. -##### Double click -Switches back to the 'View Transaction' page -##### Left hold -Same as Left click, unless we are in the 'Value Chunk Preview', in which case we step out from the 'Value Chunk Preview' and skip to the previous screen. -##### Right hold -Same as Right click, unless we are in the 'Value Chunk Preview', in which case we step out from the 'Value Chunk Preview' and skip to the next screen. diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 00000000..3faea27d --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,46 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# OS related files +.DS_Store + +# Others +cmake-build-debug/ +\.idea/workspace\.xml +\.idea/ + +src/glyphs\.h +src/glyphs\.c +obj/ +bin/ +debug/ diff --git a/src/actions.c b/src/actions.c index e6bf493f..b15ecd79 100644 --- a/src/actions.c +++ b/src/actions.c @@ -35,7 +35,7 @@ void app_set_hrp(char *p) { uint8_t app_fill_address() { // Put data directly in the apdu buffer - return crypto_fillAddress(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE); + return crypto_fillAddress(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 2); } void app_reply_address() { diff --git a/src/app_main.c b/src/app_main.c index 1e908446..ca30b550 100644 --- a/src/app_main.c +++ b/src/app_main.c @@ -90,49 +90,50 @@ unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) { return 0; } -void extractBip32(uint32_t rx, uint32_t offset) { - if ((rx - offset) < 1 + 4 * BIP32_LEN_DEFAULT) { +void extractBip44(uint32_t rx, uint32_t offset) { + if ((rx - offset) < sizeof(uint32_t) * BIP44_LEN_DEFAULT) { THROW(APDU_CODE_DATA_INVALID); } - uint8_t depth = G_io_apdu_buffer[offset]; - if (depth != BIP32_LEN_DEFAULT) { - THROW(APDU_CODE_DATA_INVALID); - } - - uint8_t *p = (uint8_t * )(G_io_apdu_buffer + offset + 1); - memcpy(bip32Path, p, 4 * BIP32_LEN_DEFAULT); + MEMCPY(bip44Path, G_io_apdu_buffer + offset, sizeof(uint32_t) * BIP44_LEN_DEFAULT); - if (bip32Path[0] != BIP32_0_DEFAULT || - bip32Path[1] != BIP32_1_DEFAULT || - bip32Path[3] != BIP32_3_DEFAULT) { + // Check values + if (bip44Path[0] != BIP44_0_DEFAULT || + bip44Path[1] != BIP44_1_DEFAULT || + bip44Path[3] != BIP44_3_DEFAULT) { THROW(APDU_CODE_DATA_INVALID); } } bool process_chunk(volatile uint32_t *tx, uint32_t rx) { - int packageIndex = G_io_apdu_buffer[OFFSET_PCK_INDEX]; - int packageCount = G_io_apdu_buffer[OFFSET_PCK_COUNT]; + const uint8_t payloadType = G_io_apdu_buffer[OFFSET_PAYLOAD_TYPE]; - uint16_t offset = OFFSET_DATA; - if (rx < offset) { + if (rx < OFFSET_DATA) { THROW(APDU_CODE_DATA_INVALID); } - if (packageIndex == 1) { - tx_initialize(); - tx_reset(); - - extractBip32(rx, OFFSET_DATA); - - return packageIndex == packageCount; - } - - if (tx_append(&(G_io_apdu_buffer[offset]), rx - offset) != rx - offset) { - THROW(APDU_CODE_OUTPUT_BUFFER_TOO_SMALL); + uint32_t added; + switch(payloadType) { + case 0: + tx_initialize(); + tx_reset(); + extractBip44(rx, OFFSET_DATA); + return false; + case 1: + added = tx_append(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA); + if (added != rx - OFFSET_DATA) { + THROW(APDU_CODE_OUTPUT_BUFFER_TOO_SMALL); + } + return false; + case 2: + added = tx_append(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA); + if (added != rx - OFFSET_DATA) { + THROW(APDU_CODE_OUTPUT_BUFFER_TOO_SMALL); + } + return true; } - return packageIndex == packageCount; + THROW(APDU_CODE_DATA_INVALID); } void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { @@ -174,7 +175,7 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { case INS_GET_ADDR_SECP256K1: { uint8_t len = extractHRP(rx, OFFSET_DATA); - extractBip32(rx, OFFSET_DATA + 1 + len); + extractBip44(rx, OFFSET_DATA + 1 + len); uint8_t requireConfirmation = G_io_apdu_buffer[OFFSET_P1]; @@ -198,9 +199,9 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { if (error_msg != NULL) { int error_msg_length = strlen(error_msg); - os_memmove(G_io_apdu_buffer, error_msg, error_msg_length); + MEMCPY(G_io_apdu_buffer, error_msg, error_msg_length); *tx += (error_msg_length); - THROW(APDU_CODE_BAD_KEY_HANDLE); + THROW(APDU_CODE_DATA_INVALID); } view_sign_show(); diff --git a/src/app_main.h b/src/app_main.h index c8d30a05..724e97d1 100644 --- a/src/app_main.h +++ b/src/app_main.h @@ -28,20 +28,14 @@ #define OFFSET_DATA_LEN 4 //< Data Length #define OFFSET_DATA 5 //< Data offset -#define OFFSET_PCK_INDEX OFFSET_P1 //< Package index offset -#define OFFSET_PCK_COUNT OFFSET_P2 //< Package count offset #define APDU_MIN_LENGTH 5 +#define OFFSET_PAYLOAD_TYPE OFFSET_P1 + #define INS_GET_VERSION 0 #define INS_SIGN_SECP256K1 2 #define INS_GET_ADDR_SECP256K1 4 -#ifdef TESTING_ENABLED -#define INS_HASH_TEST 100 -#define INS_PUBLIC_KEY_SECP256K1_TEST 101 -#define INS_SIGN_SECP256K1_TEST 102 -#endif - void app_init(); void app_main(); diff --git a/src/lib/cosmos.h b/src/lib/cosmos.h index f52d9f5b..0eb4f433 100644 --- a/src/lib/cosmos.h +++ b/src/lib/cosmos.h @@ -22,13 +22,11 @@ extern "C" { #include #include -#define BIP32_LEN_DEFAULT 5 - -#define BIP32_0_DEFAULT (0x80000000 | 0x2c) -#define BIP32_1_DEFAULT (0x80000000 | 0x76) -#define BIP32_2_DEFAULT (0x80000000 | 0) -#define BIP32_3_DEFAULT (0) -#define BIP32_4_DEFAULT (0) +#define BIP44_0_DEFAULT (0x80000000 | 0x2c) +#define BIP44_1_DEFAULT (0x80000000 | 0x76) +#define BIP44_2_DEFAULT (0x80000000 | 0) +#define BIP44_3_DEFAULT (0) +#define BIP44_4_DEFAULT (0) #ifdef __cplusplus } diff --git a/src/lib/crypto.c b/src/lib/crypto.c index 9a1b26cb..6e59aec7 100644 --- a/src/lib/crypto.c +++ b/src/lib/crypto.c @@ -1,6 +1,5 @@ /******************************************************************************* -* (c) 2018, 2019 ZondaX GmbH -* (c) 2016 Ledger +* (c) 2019 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. ********************************************************************************/ + #include "crypto.h" -#include +#include "cosmos.h" +#include #include "apdu_codes.h" #include "zxmacros.h" -#include "cosmos.h" - -uint32_t bip32Path[BIP32_LEN_DEFAULT]; +uint32_t bip44Path[BIP44_LEN_DEFAULT]; uint8_t bech32_hrp_len; char bech32_hrp[MAX_BECH32_HRP_LEN + 1]; @@ -35,16 +34,19 @@ char bech32_hrp[MAX_BECH32_HRP_LEN + 1]; #endif #if defined(TARGET_NANOS) || defined(TARGET_NANOX) -void crypto_extractPublicKey(uint32_t bip32Path[BIP32_LEN_DEFAULT], uint8_t *pubKey){ - cx_ecfp_public_key_t cx_publicKey; - cx_ecfp_private_key_t cx_privateKey; - uint8_t privateKeyData[32]; +#include "cx.h" + +void crypto_extractPublicKey(uint32_t bip44Path[BIP44_LEN_DEFAULT], uint8_t *pubKey){ + cx_ecfp_public_key_t cx_publicKey; + cx_ecfp_private_key_t cx_privateKey; + uint8_t privateKeyData[32]; + BEGIN_TRY { TRY { SAFE_HEARTBEAT(os_perso_derive_node_bip32(CX_CURVE_256K1, - bip32Path, - BIP32_LEN_DEFAULT, + bip44Path, + BIP44_LEN_DEFAULT, privateKeyData, NULL)); ////////////////////// @@ -53,9 +55,9 @@ void crypto_extractPublicKey(uint32_t bip32Path[BIP32_LEN_DEFAULT], uint8_t *pub SAFE_HEARTBEAT(cx_ecfp_generate_pair(CX_CURVE_256K1, &cx_publicKey, &cx_privateKey, 1)); } FINALLY { - explicit_bzero(&cx_privateKey, sizeof(cx_privateKey)); - explicit_bzero(privateKeyData, 32); - }; + MEMZERO(&cx_privateKey, sizeof(cx_privateKey)); + MEMZERO(privateKeyData, 32); + } } END_TRY; @@ -72,7 +74,6 @@ void crypto_extractPublicKey(uint32_t bip32Path[BIP32_LEN_DEFAULT], uint8_t *pub } uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t *message, uint16_t messageLen) { - // Hash uint8_t message_digest[CX_SHA256_SIZE]; SAFE_HEARTBEAT(cx_hash_sha256(message, messageLen, message_digest, CX_SHA256_SIZE)); @@ -81,11 +82,12 @@ uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t int signatureLength; BEGIN_TRY { - TRY { + TRY + { // Generate keys SAFE_HEARTBEAT(os_perso_derive_node_bip32(CX_CURVE_256K1, - bip32Path, - BIP32_LEN_DEFAULT, + bip44Path, + BIP44_LEN_DEFAULT, privateKeyData, NULL)); SAFE_HEARTBEAT(cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &cx_privateKey)); @@ -100,11 +102,11 @@ uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t CX_SHA256_SIZE, signature, signatureMaxlen, - &info)) + &info)); } FINALLY { - explicit_bzero(&cx_privateKey, sizeof(cx_privateKey)); - explicit_bzero(privateKeyData, 32); + MEMZERO(&cx_privateKey, sizeof(cx_privateKey)); + MEMZERO(privateKeyData, 32); } } END_TRY; @@ -115,7 +117,7 @@ uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t void crypto_extractPublicKey(uint32_t path[BIP32_LEN_DEFAULT], uint8_t *pubKey) { // Empty version for non-Ledger devices - MEMSET(pubKey, 0, 32); + MEMZERO(pubKey, 32); } uint16_t crypto_sign(uint8_t *signature, @@ -129,11 +131,10 @@ uint16_t crypto_sign(uint8_t *signature, #endif uint8_t extractHRP(uint32_t rx, uint32_t offset) { - MEMSET(bech32_hrp, 0, MAX_BECH32_HRP_LEN); - if (rx < offset + 1) { THROW(APDU_CODE_DATA_INVALID); } + MEMZERO(bech32_hrp, MAX_BECH32_HRP_LEN); bech32_hrp_len = G_io_apdu_buffer[offset]; @@ -161,12 +162,12 @@ void crypto_set_hrp(char *p) { } uint16_t crypto_fillAddress(uint8_t *buffer, uint16_t buffer_len) { - if (buffer_len < PUBKEY_LEN + 50) { + if (buffer_len < PK_COMPRESSED_LEN + 50) { return 0; } // extract pubkey - crypto_extractPublicKey(bip32Path, buffer); + crypto_extractPublicKey(bip44Path, buffer); // Hash it uint8_t hashed1_pk[CX_SHA256_SIZE]; @@ -175,8 +176,8 @@ uint16_t crypto_fillAddress(uint8_t *buffer, uint16_t buffer_len) { uint8_t hashed2_pk[CX_RIPEMD160_SIZE]; ripemd160_32(hashed2_pk, hashed1_pk); - char *addr = (char *) (buffer + PUBKEY_LEN); + char *addr = (char *) (buffer + PK_COMPRESSED_LEN); bech32EncodeFromBytes(addr, bech32_hrp, hashed2_pk, CX_RIPEMD160_SIZE); - return PUBKEY_LEN + strlen(addr); + return PK_COMPRESSED_LEN + strlen(addr); } diff --git a/src/lib/crypto.h b/src/lib/crypto.h index 3eb70d36..cb20119a 100644 --- a/src/lib/crypto.h +++ b/src/lib/crypto.h @@ -23,22 +23,20 @@ extern "C" { #endif -#define MAX_BECH32_HRP_LEN 83 -#define PK_COMPRESSED_LEN 33 +#define BIP44_LEN_DEFAULT 5u +#define MAX_BECH32_HRP_LEN 83u +#define PK_COMPRESSED_LEN 33u -extern uint32_t bip32Path[BIP32_LEN_DEFAULT]; +extern uint32_t bip44Path[BIP44_LEN_DEFAULT]; extern char *hrp; -#define PUBKEY_LEN 33 - uint8_t extractHRP(uint32_t rx, uint32_t offset); void crypto_set_hrp(char *p); uint16_t crypto_fillAddress(uint8_t *buffer, uint16_t buffer_len); -uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, - const uint8_t *message, uint16_t messageLen); +uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t *message, uint16_t messageLen); #ifdef __cplusplus } diff --git a/src/lib/parser.c b/src/lib/parser.c index 9f665c3f..11542c98 100644 --- a/src/lib/parser.c +++ b/src/lib/parser.c @@ -15,8 +15,9 @@ ********************************************************************************/ #include -#include #include +#include +#include #include "json/tx_parser.h" #include "json/tx_display.h" #include "lib/parser_impl.h" @@ -40,38 +41,38 @@ uint8_t parser_getNumItems(parser_context_t *ctx) { __Z_INLINE bool_t parser_areEqual(uint16_t tokenidx, char *expected) { if (parser_tx_obj.json.tokens[tokenidx].type != JSMN_STRING) { - return FALSE; + return false; } uint16_t len = parser_tx_obj.json.tokens[tokenidx].end - parser_tx_obj.json.tokens[tokenidx].start; if (strlen(expected) != len) { - return FALSE; + return false; } const char *p = parser_tx_obj.tx + parser_tx_obj.json.tokens[tokenidx].start; for (uint16_t i = 0; i < len; i++) { if (expected[i] != *(p + i)) { - return FALSE; + return false; } } - return TRUE; + return true; } __Z_INLINE bool_t parser_isAmount(char *key) { if (strcmp(parser_tx_obj.query.out_key, "fee/amount") == 0) - return TRUE; + return true; if (strcmp(parser_tx_obj.query.out_key, "msgs/inputs/coins") == 0) - return TRUE; + return true; if (strcmp(parser_tx_obj.query.out_key, "msgs/outputs/coins") == 0) - return TRUE; + return true; if (strcmp(parser_tx_obj.query.out_key, "msgs/value/amount") == 0) - return TRUE; + return true; - return FALSE; + return false; } __Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, diff --git a/src/lib/parser_common.h b/src/lib/parser_common.h index 7bc4fe66..c8a2c27f 100644 --- a/src/lib/parser_common.h +++ b/src/lib/parser_common.h @@ -47,7 +47,7 @@ typedef enum { typedef struct { const uint8_t *buffer; - uint16_t bufferSize; + uint16_t bufferLen; uint16_t offset; } parser_context_t; diff --git a/src/lib/parser_impl.c b/src/lib/parser_impl.c index 9f24aedf..88a55b5a 100644 --- a/src/lib/parser_impl.c +++ b/src/lib/parser_impl.c @@ -27,12 +27,12 @@ parser_error_t parser_init_context(parser_context_t *ctx, if (bufferSize == 0 || buffer == NULL) { // Not available, use defaults ctx->buffer = NULL; - ctx->bufferSize = 0; + ctx->bufferLen = 0; return parser_no_data; } ctx->buffer = buffer; - ctx->bufferSize = bufferSize; + ctx->bufferLen = bufferSize; return parser_ok; } @@ -41,6 +41,7 @@ parser_error_t parser_init(parser_context_t *ctx, const uint8_t *buffer, uint16_ parser_error_t err = parser_init_context(ctx, buffer, bufferSize); if (err != parser_ok) return err; + return err; } @@ -94,7 +95,7 @@ const char *parser_getErrorDescription(parser_error_t err) { parser_error_t _readTx(parser_context_t *c, parser_tx_t *v) { parser_error_t err = json_parse_s(&parser_tx_obj.json, - (const char *) c->buffer, c->bufferSize); + (const char *) c->buffer, c->bufferLen); if (err != parser_ok) { return err; } diff --git a/src/view_s.c b/src/view_s.c index f87fafd7..27046121 100644 --- a/src/view_s.c +++ b/src/view_s.c @@ -197,7 +197,7 @@ void view_idle_show_impl() { void view_address_show_impl() { #define KEYCHUNKSIZE 12 // move first part to key - MEMSET(viewdata.key, 0, MAX_CHARS_PER_KEY_LINE); + MEMZERO(viewdata.key, MAX_CHARS_PER_KEY_LINE); MEMCPY(viewdata.key, address, KEYCHUNKSIZE); // move the remainder to From 8ff615ba44e6304838c1492488e16831a1b3444f Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Mon, 14 Oct 2019 18:31:36 +0200 Subject: [PATCH 30/78] bump to v2.0.3 --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 31804ab1..8a633da5 100755 --- a/Makefile +++ b/Makefile @@ -24,8 +24,8 @@ include $(BOLOS_SDK)/Makefile.defines # Main app configuration APPNAME = "Cosmos" APPVERSION_M=2 -APPVERSION_N=0 -APPVERSION_P=1 +APPVERSION_N=1 +APPVERSION_P=0 APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path "44'/118'" From ef97f84dc8707bd4f130b96d3490bf3e3186b1ac Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 22 Oct 2019 15:16:54 +0200 Subject: [PATCH 31/78] Accept empty fee field --- Makefile | 2 +- src/lib/parser.c | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 8a633da5..56e0a05d 100755 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ include $(BOLOS_SDK)/Makefile.defines APPNAME = "Cosmos" APPVERSION_M=2 APPVERSION_N=1 -APPVERSION_P=0 +APPVERSION_P=1 APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path "44'/118'" diff --git a/src/lib/parser.c b/src/lib/parser.c index 11542c98..98a9fb37 100644 --- a/src/lib/parser.c +++ b/src/lib/parser.c @@ -79,11 +79,16 @@ __Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, char *outVal, uint16_t outValLen, uint8_t pageIdx, uint8_t *pageCount) { uint16_t numElements = array_get_element_count(amountToken, &parser_tx_obj.json); - if (parser_tx_obj.json.tokens[amountToken].type == JSMN_ARRAY){ + if (parser_tx_obj.json.tokens[amountToken].type == JSMN_ARRAY) { amountToken++; } numElements = array_get_element_count(amountToken, &parser_tx_obj.json); + if (numElements != 4) { + snprintf(outVal, outValLen, "Empty"); + return parser_ok; + } + if (numElements != 4) return parser_unexpected_field; From e16f2819b8b7c48c2a652cba01f0e6fc5c015197 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 22 Oct 2019 15:32:48 +0200 Subject: [PATCH 32/78] force validation before showing --- Makefile | 2 +- src/lib/parser.c | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 56e0a05d..fd0d8714 100755 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ include $(BOLOS_SDK)/Makefile.defines APPNAME = "Cosmos" APPVERSION_M=2 APPVERSION_N=1 -APPVERSION_P=1 +APPVERSION_P=2 APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path "44'/118'" diff --git a/src/lib/parser.c b/src/lib/parser.c index 98a9fb37..4ad37bc8 100644 --- a/src/lib/parser.c +++ b/src/lib/parser.c @@ -23,6 +23,7 @@ #include "lib/parser_impl.h" #include "view_internal.h" #include "jsmn.h" +#include "parser.h" parser_error_t parser_parse(parser_context_t *ctx, const uint8_t *data, @@ -32,7 +33,26 @@ parser_error_t parser_parse(parser_context_t *ctx, } parser_error_t parser_validate(parser_context_t *ctx) { - return tx_validate(&parser_tx_obj.json); + parser_error_t err = tx_validate(&parser_tx_obj.json); + if (err != parser_ok) + return err; + + // Iterate through all items to check that all can be shown and are valid + + uint8_t numItems = parser_getNumItems(ctx); + + char tmpKey[40]; + char tmpVal[40]; + + for (uint8_t idx = 0; idx < numItems; idx++) { + uint8_t pageCount; + err = parser_getItem(ctx, idx, tmpKey, sizeof(tmpKey), tmpVal, sizeof(tmpVal), idx, &pageCount); + if (err != parser_ok) { + return err; + } + } + + return parser_ok; } uint8_t parser_getNumItems(parser_context_t *ctx) { @@ -84,7 +104,7 @@ __Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, } numElements = array_get_element_count(amountToken, &parser_tx_obj.json); - if (numElements != 4) { + if (numElements == 0) { snprintf(outVal, outValLen, "Empty"); return parser_ok; } From 60d9b7045ddcbd6bd18c5a698ea4d371af32fc1d Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sun, 27 Oct 2019 12:58:07 +0100 Subject: [PATCH 33/78] Improve tx validation + tests --- deps/ledger-zxlib/include/zxmacros.h | 17 ++++++++++++++--- src/app_main.c | 10 +++++++--- src/lib/json/json_parser.c | 8 ++++---- src/lib/json/tx_display.c | 2 +- src/lib/json/tx_parser.c | 13 +++++++++---- src/lib/parser.c | 2 +- src/lib/parser_common.h | 10 ++++++++-- src/lib/parser_impl.c | 22 ++++++++++++++++------ 8 files changed, 60 insertions(+), 24 deletions(-) diff --git a/deps/ledger-zxlib/include/zxmacros.h b/deps/ledger-zxlib/include/zxmacros.h index a2938fe0..01682817 100644 --- a/deps/ledger-zxlib/include/zxmacros.h +++ b/deps/ledger-zxlib/include/zxmacros.h @@ -21,7 +21,8 @@ extern "C" { #endif #include "string.h" -extern void explicit_bzero (void *__s, size_t __n) __THROW __nonnull ((1)); + +extern void explicit_bzero(void *__s, size_t __n) __THROW __nonnull ((1)); #if defined(LEDGER_SPECIFIC) #include "bolos_target.h" @@ -240,10 +241,20 @@ __Z_INLINE void array_to_hexstr(char *dst, const uint8_t *src, uint8_t count) { } __Z_INLINE void pageStringExt(char *outValue, uint16_t outValueLen, - const char *inValue, uint16_t inValueLen, - uint8_t pageIdx, uint8_t *pageCount) { + const char *inValue, uint16_t inValueLen, + uint8_t pageIdx, uint8_t *pageCount) { MEMZERO(outValue, outValueLen); + outValueLen--; // leave space for NULL termination + if (outValueLen == 0) { + return; + } + + if (inValueLen == 0) { + *pageCount = 1; + return; + } + *pageCount = (inValueLen / outValueLen); const uint16_t lastChunkLen = (inValueLen % outValueLen); diff --git a/src/app_main.c b/src/app_main.c index ca30b550..7d33e48d 100644 --- a/src/app_main.c +++ b/src/app_main.c @@ -92,7 +92,7 @@ unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) { void extractBip44(uint32_t rx, uint32_t offset) { if ((rx - offset) < sizeof(uint32_t) * BIP44_LEN_DEFAULT) { - THROW(APDU_CODE_DATA_INVALID); + THROW(APDU_CODE_WRONG_LENGTH); } MEMCPY(bip44Path, G_io_apdu_buffer + offset, sizeof(uint32_t) * BIP44_LEN_DEFAULT); @@ -108,8 +108,12 @@ void extractBip44(uint32_t rx, uint32_t offset) { bool process_chunk(volatile uint32_t *tx, uint32_t rx) { const uint8_t payloadType = G_io_apdu_buffer[OFFSET_PAYLOAD_TYPE]; + if (G_io_apdu_buffer[OFFSET_P2] != 0){ + THROW(APDU_CODE_INVALIDP1P2); + } + if (rx < OFFSET_DATA) { - THROW(APDU_CODE_DATA_INVALID); + THROW(APDU_CODE_WRONG_LENGTH); } uint32_t added; @@ -133,7 +137,7 @@ bool process_chunk(volatile uint32_t *tx, uint32_t rx) { return true; } - THROW(APDU_CODE_DATA_INVALID); + THROW(APDU_CODE_INVALIDP1P2); } void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { diff --git a/src/lib/json/json_parser.c b/src/lib/json/json_parser.c index eeddf175..9ddf1369 100644 --- a/src/lib/json/json_parser.c +++ b/src/lib/json/json_parser.c @@ -48,11 +48,11 @@ parser_error_t json_parse_s(parsed_json_t *parsed_json, switch (num_tokens) { case JSMN_ERROR_NOMEM: - return parser_too_many_tokens; + return parser_json_too_many_tokens; case JSMN_ERROR_INVAL: return parser_unexpected_characters; case JSMN_ERROR_PART: - return parser_incomplete_json; + return parser_json_incomplete_json; } parsed_json->numberOfTokens = 0; @@ -60,12 +60,12 @@ parser_error_t json_parse_s(parsed_json_t *parsed_json, // Parsing error if (num_tokens <= 0) { - return parser_no_data; + return parser_json_zero_tokens; } // We cannot support if number of tokens exceeds the limit if (num_tokens > MAX_NUMBER_OF_TOKENS) { - return parser_too_many_tokens; + return parser_json_too_many_tokens; } parsed_json->numberOfTokens = num_tokens; diff --git a/src/lib/json/tx_display.c b/src/lib/json/tx_display.c index 2840adcb..cc7da610 100644 --- a/src/lib/json/tx_display.c +++ b/src/lib/json/tx_display.c @@ -128,7 +128,7 @@ parser_error_t tx_display_set_query(uint16_t displayIdx, uint16_t *outStartToken _indexRootFields(); if (displayIdx < 0 || displayIdx >= display_cache.numItems) { - return parser_no_data; + return parser_display_idx_out_of_range; } parser_tx_obj.query.item_index = 0; diff --git a/src/lib/json/tx_parser.c b/src/lib/json/tx_parser.c index 6296861b..74fef4f8 100644 --- a/src/lib/json/tx_parser.c +++ b/src/lib/json/tx_parser.c @@ -56,12 +56,17 @@ parser_error_t tx_getToken(uint16_t token_index, const int16_t token_start = parser_tx_obj.json.tokens[token_index].start; const int16_t token_end = parser_tx_obj.json.tokens[token_index].end; + if (token_start > token_end) { + return parser_unexpected_buffer_end; + } + pageStringExt(out_val, out_val_len, - parser_tx_obj.tx + token_start, token_end - token_start, + parser_tx_obj.tx + token_start, + token_end - token_start, pageIdx, pageCount); if (pageIdx >= *pageCount) - return parser_no_data; + return parser_display_page_out_of_range; return parser_ok; } @@ -102,7 +107,7 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to return parser_ok; } parser_tx_obj.query.item_index_current++; - return parser_no_data; + return parser_query_no_results; } const int16_t el_count = object_get_element_count(root_token_index, &parser_tx_obj.json); @@ -152,5 +157,5 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to break; } - return parser_no_data; + return parser_query_no_results; } diff --git a/src/lib/parser.c b/src/lib/parser.c index 4ad37bc8..40a69831 100644 --- a/src/lib/parser.c +++ b/src/lib/parser.c @@ -46,7 +46,7 @@ parser_error_t parser_validate(parser_context_t *ctx) { for (uint8_t idx = 0; idx < numItems; idx++) { uint8_t pageCount; - err = parser_getItem(ctx, idx, tmpKey, sizeof(tmpKey), tmpVal, sizeof(tmpVal), idx, &pageCount); + err = parser_getItem(ctx, idx, tmpKey, sizeof(tmpKey), tmpVal, sizeof(tmpVal), 0, &pageCount); if (err != parser_ok) { return err; } diff --git a/src/lib/parser_common.h b/src/lib/parser_common.h index c8a2c27f..18722e64 100644 --- a/src/lib/parser_common.h +++ b/src/lib/parser_common.h @@ -25,6 +25,7 @@ extern "C" { typedef enum { parser_ok = 0, parser_no_data, + parser_init_context_empty, parser_unexpected_buffer_end, parser_unexpected_version, parser_unexpected_characters, @@ -32,9 +33,14 @@ typedef enum { parser_duplicated_field, parser_value_out_of_range, parser_unexpected_chain, - parser_too_many_tokens, // "NOMEM: JSON string contains too many tokens" - parser_incomplete_json, // "JSON string is not complete"; + parser_query_no_results, + ///// + parser_display_idx_out_of_range, + parser_display_page_out_of_range, //// + parser_json_zero_tokens, + parser_json_too_many_tokens, // "NOMEM: JSON string contains too many tokens" + parser_json_incomplete_json, // "JSON string is not complete"; parser_json_contains_whitespace, parser_json_is_not_sorted, parser_json_missing_chain_id, diff --git a/src/lib/parser_impl.c b/src/lib/parser_impl.c index 88a55b5a..e7e1fb0f 100644 --- a/src/lib/parser_impl.c +++ b/src/lib/parser_impl.c @@ -28,7 +28,7 @@ parser_error_t parser_init_context(parser_context_t *ctx, // Not available, use defaults ctx->buffer = NULL; ctx->bufferLen = 0; - return parser_no_data; + return parser_init_context_empty; } ctx->buffer = buffer; @@ -51,6 +51,8 @@ const char *parser_getErrorDescription(parser_error_t err) { return "No error"; case parser_no_data: return "No more data"; + case parser_init_context_empty: + return "Initialized empty context"; case parser_unexpected_buffer_end: return "Unexpected buffer end"; case parser_unexpected_version: @@ -65,12 +67,20 @@ const char *parser_getErrorDescription(parser_error_t err) { return "Value out of range"; case parser_unexpected_chain: return "Unexpected chain"; - case parser_too_many_tokens: - return "NOMEM: JSON string contains too many tokens"; - case parser_incomplete_json: - return "JSON string is not complete"; - + case parser_query_no_results: + return "item query returned no results"; ////// + case parser_display_idx_out_of_range: + return "display index out of range"; + case parser_display_page_out_of_range: + return "display page out of range"; +////// + case parser_json_zero_tokens: + return "JSON. Zero tokens"; + case parser_json_too_many_tokens: + return "JSON. Too many tokens"; + case parser_json_incomplete_json: + return "JSON string is not complete"; case parser_json_contains_whitespace: return "JSON Contains whitespace in the corpus"; case parser_json_is_not_sorted: From 8784e97adc16577e8c3f007ef0f3b94d04f166ab Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sun, 27 Oct 2019 13:13:35 +0100 Subject: [PATCH 34/78] converting errors correctly --- Makefile | 2 +- src/tx.c | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index fd0d8714..35d9bb14 100755 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ include $(BOLOS_SDK)/Makefile.defines APPNAME = "Cosmos" APPVERSION_M=2 APPVERSION_N=1 -APPVERSION_P=2 +APPVERSION_P=3 APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path "44'/118'" diff --git a/src/tx.c b/src/tx.c index be143129..fc8ea66d 100644 --- a/src/tx.c +++ b/src/tx.c @@ -101,7 +101,7 @@ tx_error_t tx_getItem(int8_t displayIdx, uint8_t pageIdx, uint8_t *pageCount) { tx_error_t err = tx_no_error; - if (displayIdx < 0 || displayIdx > tx_getNumItems() ) { + if (displayIdx < 0 || displayIdx > tx_getNumItems()) { return tx_no_data; } @@ -112,7 +112,9 @@ tx_error_t tx_getItem(int8_t displayIdx, pageIdx, pageCount); // Convert error codes - if (err == parser_no_data) + if (err == parser_no_data || + err == parser_display_idx_out_of_range || + err == parser_display_page_out_of_range) return tx_no_data; if (err == parser_ok) From bf0b4fcd53c138e4c504ec6a80eee059d0e9fa5c Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sun, 27 Oct 2019 14:41:38 +0100 Subject: [PATCH 35/78] improve handling of empty memo fields --- deps/ledger-zxlib/include/zxmacros.h | 2 +- src/lib/json/tx_parser.c | 14 ++++++++++---- src/lib/parser.c | 22 ++++++++++++++-------- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/deps/ledger-zxlib/include/zxmacros.h b/deps/ledger-zxlib/include/zxmacros.h index 01682817..d0470fb6 100644 --- a/deps/ledger-zxlib/include/zxmacros.h +++ b/deps/ledger-zxlib/include/zxmacros.h @@ -251,7 +251,7 @@ __Z_INLINE void pageStringExt(char *outValue, uint16_t outValueLen, } if (inValueLen == 0) { - *pageCount = 1; + *pageCount = 0; return; } diff --git a/src/lib/json/tx_parser.c b/src/lib/json/tx_parser.c index 74fef4f8..1a3e8984 100644 --- a/src/lib/json/tx_parser.c +++ b/src/lib/json/tx_parser.c @@ -53,6 +53,10 @@ __always_inline void strcat_chunk_s(char *dst, uint16_t dst_max, const char *src parser_error_t tx_getToken(uint16_t token_index, char *out_val, uint16_t out_val_len, uint8_t pageIdx, uint8_t *pageCount) { + *pageCount = 1; + MEMZERO(out_val, out_val_len); + snprintf(out_val, out_val_len, " "); + const int16_t token_start = parser_tx_obj.json.tokens[token_index].start; const int16_t token_end = parser_tx_obj.json.tokens[token_index].end; @@ -60,10 +64,12 @@ parser_error_t tx_getToken(uint16_t token_index, return parser_unexpected_buffer_end; } - pageStringExt(out_val, out_val_len, - parser_tx_obj.tx + token_start, - token_end - token_start, - pageIdx, pageCount); + if (token_start < token_end) { + pageStringExt(out_val, out_val_len, + parser_tx_obj.tx + token_start, + token_end - token_start, + pageIdx, pageCount); + } if (pageIdx >= *pageCount) return parser_display_page_out_of_range; diff --git a/src/lib/parser.c b/src/lib/parser.c index 40a69831..b2c98a85 100644 --- a/src/lib/parser.c +++ b/src/lib/parser.c @@ -122,8 +122,8 @@ __Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, return parser_unexpected_field; char bufferUI[160]; - MEMSET(outVal, 0, outValLen); - MEMSET(bufferUI, 0, sizeof(bufferUI)); + MEMZERO(outVal, outValLen); + MEMZERO(bufferUI, sizeof(bufferUI)); const char *amountPtr = parser_tx_obj.tx + parser_tx_obj.json.tokens[amountToken + 2].start; const uint16_t amountLen = parser_tx_obj.json.tokens[amountToken + 2].end - @@ -136,6 +136,14 @@ __Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, return parser_unexpected_buffer_end; } + if (amountLen == 0) { + return parser_unexpected_buffer_end; + } + + if (denomLen == 0) { + return parser_unexpected_buffer_end; + } + MEMCPY(bufferUI, amountPtr, amountLen); bufferUI[amountLen] = ' '; MEMCPY(bufferUI + 1 + amountLen, denomPtr, denomLen); @@ -151,11 +159,11 @@ parser_error_t parser_getItem(parser_context_t *ctx, char *outVal, uint16_t outValLen, uint8_t pageIdx, uint8_t *pageCount) { - MEMSET(outKey, 0, outKeyLen); - MEMSET(outVal, 0, outValLen); + MEMZERO(outKey, outKeyLen); + MEMZERO(outVal, outValLen); INIT_QUERY(outKey, outKeyLen, outVal, outValLen, pageIdx) snprintf(outKey, outKeyLen, "?"); - snprintf(outVal, outValLen, "?"); + snprintf(outVal, outValLen, " "); uint16_t displayStartToken; parser_error_t err = tx_display_set_query(displayIdx, &displayStartToken); @@ -174,9 +182,7 @@ parser_error_t parser_getItem(parser_context_t *ctx, if (parser_isAmount(parser_tx_obj.query.out_key)) { err = parser_formatAmount(ret_value_token_index, outVal, outValLen, pageIdx, pageCount); } else { - err = tx_getToken(ret_value_token_index, - parser_tx_obj.query.out_val, parser_tx_obj.query.out_val_len, - parser_tx_obj.query.chunk_index, pageCount); + err = tx_getToken(ret_value_token_index, outVal, outValLen, parser_tx_obj.query.chunk_index, pageCount); } tx_display_make_friendly(); From 90416b40eb78605a46629967c8f098afa42d4ff5 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sun, 17 Nov 2019 15:39:13 +0100 Subject: [PATCH 36/78] upgrading to SDK v1.6 --- .gitignore | 4 ++++ Makefile | 10 ++++------ deps/ledger-zxlib/include/zxmacros.h | 1 - script.ld | 12 +++++++----- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index eaf7e6bd..6c65850a 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,7 @@ bin/app\.elf src/glyphs\.h src/glyphs\.c + +bin/app.sha256 + +bin/app.apdu diff --git a/Makefile b/Makefile index 35d9bb14..683b37a1 100755 --- a/Makefile +++ b/Makefile @@ -84,7 +84,8 @@ DEFINES += HAVE_UX_FLOW SDK_SOURCE_PATH += lib_ux else # Assume Nano S -DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=128 +DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=128 +DEFINES += HAVE_BOLOS_UX COMPLIANCE_UX_160 HAVE_UX_LEGACY endif # X specific @@ -135,13 +136,10 @@ SDK_SOURCE_PATH += lib_ux endif load: - python -m ledgerblue.loadApp $(APP_LOAD_PARAMS) + sudo -E python -m ledgerblue.loadApp $(APP_LOAD_PARAMS) delete: - python -m ledgerblue.deleteApp $(COMMON_DELETE_PARAMS) - -package: - ./pkgdemo.sh ${APPNAME} ${APPVERSION} ${ICONNAME} + sudo -E python -m ledgerblue.deleteApp $(COMMON_DELETE_PARAMS) # Import generic rules from the SDK include $(BOLOS_SDK)/Makefile.rules diff --git a/deps/ledger-zxlib/include/zxmacros.h b/deps/ledger-zxlib/include/zxmacros.h index d0470fb6..9f2ff72f 100644 --- a/deps/ledger-zxlib/include/zxmacros.h +++ b/deps/ledger-zxlib/include/zxmacros.h @@ -96,7 +96,6 @@ void __logstack(); #include #include -#include #define __Z_INLINE inline __attribute__((always_inline)) static diff --git a/script.ld b/script.ld index 7eae328e..2de1b378 100644 --- a/script.ld +++ b/script.ld @@ -72,10 +72,10 @@ SECTIONS *(.bss.N_* .rodata.N_*) . = ALIGN(PAGE_SIZE); + _envram_data = .; + _install_parameters = .; - PROVIDE(N_install_parameters = .); _envram = .; - _nvram_data_size = _envram - _nvram_data; } > FLASH = 0x00 @@ -112,9 +112,11 @@ SECTIONS app_stack_canary = .; PROVIDE(app_stack_canary = .); . += 4; - _stack = .; - . = _stack + STACK_SIZE; - PROVIDE( _stack_size = STACK_SIZE ); + _stack_validation = .; + . = _stack_validation + STACK_SIZE; + _stack = ABSOLUTE(END_STACK) - STACK_SIZE; + PROVIDE( _stack = ABSOLUTE(END_STACK) - STACK_SIZE); + _estack = ABSOLUTE(END_STACK); PROVIDE( _estack = ABSOLUTE(END_STACK) ); } > SRAM = 0x00 From 9b74962059b8ac8045f1c81c726bb3eb5df11519 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sun, 17 Nov 2019 15:48:26 +0100 Subject: [PATCH 37/78] cleaning templates --- deps/ledger-zxlib/include/view_templates.h | 24 ---------------------- 1 file changed, 24 deletions(-) diff --git a/deps/ledger-zxlib/include/view_templates.h b/deps/ledger-zxlib/include/view_templates.h index 5d6db0d2..97efd03b 100644 --- a/deps/ledger-zxlib/include/view_templates.h +++ b/deps/ledger-zxlib/include/view_templates.h @@ -49,12 +49,6 @@ 0 /* icon_id */ \ }, \ NULL, /* text */ \ - 0, /* touch_area_brim */ \ - 0, /* overfgcolor */ \ - 0, /* overbgcolor */ \ - NULL, /* tap */ \ - NULL, /* out */ \ - NULL, /* over */ \ } #define UI_LabelLine(id, x, y, w, h, fgcolor, bgcolor, text) \ @@ -75,12 +69,6 @@ 0 /* icon_id */ \ }, \ text, /* text */ \ - 0, /* touch_area_brim */ \ - 0, /* overfgcolor */ \ - 0, /* overbgcolor */ \ - NULL, /* tap */ \ - NULL, /* out */ \ - NULL, /* over */ \ } #define UI_LabelLineScrolling(id, x, y, w, h, fgcolor, bgcolor, text) \ @@ -101,12 +89,6 @@ 50 /* icon_id / scroll speed */ \ }, \ text, /* text */ \ - 0, /* touch_area_brim */ \ - 0, /* overfgcolor */ \ - 0, /* overbgcolor */ \ - NULL, /* tap */ \ - NULL, /* out */ \ - NULL, /* over */ \ } #if defined(TARGET_NANOX) @@ -171,12 +153,6 @@ icon /* icon_id */ \ }, \ NULL, /* text */ \ - 0, /* touch_area_brim */ \ - 0, /* overfgcolor */ \ - 0, /* overbgcolor */ \ - NULL, /* tap */ \ - NULL, /* out */ \ - NULL, /* over */ \ } #define UI_BACKGROUND_LEFT_RIGHT_ICONS \ From 6855aefd18dbd1960b857a516bdb83e82d6f82b5 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sun, 17 Nov 2019 15:51:29 +0100 Subject: [PATCH 38/78] bump to 2.1.4 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 683b37a1..f4ef0319 100755 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ include $(BOLOS_SDK)/Makefile.defines APPNAME = "Cosmos" APPVERSION_M=2 APPVERSION_N=1 -APPVERSION_P=3 +APPVERSION_P=4 APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path "44'/118'" From 22509e2a7bf73980ce7e191172b9faf7d37a2650 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Mon, 18 Nov 2019 15:13:12 +0100 Subject: [PATCH 39/78] clarify repo purpose --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1441a217..f34ba795 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ -# Tendermint/Cosmos: Ledger Nano S - User App +# Cosmos Ledger Nano S/X [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +**THIS IS A DEVELOPMENT REPO** + +*THE OFFICIAL RELEASE IS PUBLISHED AND AVAILABLE IN LEDGER LIVE* + This is a submodule that only contains the user app according to Ledger specs. -Please refer to the [Ledger-Cosmos](https://github.com/cosmos/ledger-cosmos) for the complete source code, build instructions, etc. (unit tests, integration tests, documentation, etc.) +Please refer to the [Ledger-Cosmos](https://github.com/cosmos/ledger-cosmos) for the complete source code, build instructions, etc. (unit tests, integration tests, documentation, etc.) From 235b5ee578830a176a0efa513ac836cde7e9d36b Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 26 Nov 2019 15:53:02 +0100 Subject: [PATCH 40/78] partially upgrade to 1.6 FlowUI --- Makefile | 8 +- deps/ledger-zxlib/include/bignum.h | 35 +++++++ deps/ledger-zxlib/include/zxmacros.h | 12 ++- deps/ledger-zxlib/src/bignum.c | 148 +++++++++++++++++++++++++++ deps/ledger-zxlib/src/hexutils.c | 6 +- src/actions.c | 1 + src/app_main.c | 2 +- src/lib/{cosmos.h => coin.h} | 2 +- src/lib/crypto.c | 12 +-- src/lib/crypto.h | 4 +- src/tx.h | 2 +- src/view.c | 17 ++- src/view_internal.h | 21 +++- src/view_s.c | 57 +++++------ src/view_x.c | 12 ++- 15 files changed, 274 insertions(+), 65 deletions(-) create mode 100644 deps/ledger-zxlib/include/bignum.h create mode 100644 deps/ledger-zxlib/src/bignum.c rename src/lib/{cosmos.h => coin.h} (92%) diff --git a/Makefile b/Makefile index f4ef0319..c910a0d0 100755 --- a/Makefile +++ b/Makefile @@ -24,8 +24,8 @@ include $(BOLOS_SDK)/Makefile.defines # Main app configuration APPNAME = "Cosmos" APPVERSION_M=2 -APPVERSION_N=1 -APPVERSION_P=4 +APPVERSION_N=2 +APPVERSION_P=1 APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path "44'/118'" @@ -85,7 +85,7 @@ SDK_SOURCE_PATH += lib_ux else # Assume Nano S DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=128 -DEFINES += HAVE_BOLOS_UX COMPLIANCE_UX_160 HAVE_UX_LEGACY +DEFINES += HAVE_BOLOS_UX COMPLIANCE_UX_160 HAVE_UX_LEGACY HAVE_UX_FLOW endif # X specific @@ -130,10 +130,8 @@ include $(BOLOS_SDK)/Makefile.glyphs APP_SOURCE_PATH += src deps/jsmn/src deps/ledger-zxlib/include deps/ledger-zxlib/src SDK_SOURCE_PATH += lib_stusb lib_u2f lib_stusb_impl -ifeq ($(TARGET_NAME),TARGET_NANOX) #SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl SDK_SOURCE_PATH += lib_ux -endif load: sudo -E python -m ledgerblue.loadApp $(APP_LOAD_PARAMS) diff --git a/deps/ledger-zxlib/include/bignum.h b/deps/ledger-zxlib/include/bignum.h new file mode 100644 index 00000000..08bdcb5e --- /dev/null +++ b/deps/ledger-zxlib/include/bignum.h @@ -0,0 +1,35 @@ +/******************************************************************************* +* (c) 2019 ZondaX GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +bool_t bignumLittleEndian_bcdprint(char *outBuffer, uint16_t outBufferLen, const uint8_t *inBCD, uint16_t inBCDLen); +void bignumLittleEndian_to_bcd(uint8_t *bcdOut, uint16_t bcdOutLen, const uint8_t *binValue, uint16_t binValueLen); + +bool_t bignumBigEndian_bcdprint(char *outBuffer, uint16_t outBufferLen, const uint8_t *bcdIn, uint16_t bcdInLen); +void bignumBigEndian_to_bcd(uint8_t *bcdOut, uint16_t bcdOutLen, const uint8_t *binValue, uint16_t binValueLen); + + +#ifdef __cplusplus +} +#endif diff --git a/deps/ledger-zxlib/include/zxmacros.h b/deps/ledger-zxlib/include/zxmacros.h index 9f2ff72f..4f2a9176 100644 --- a/deps/ledger-zxlib/include/zxmacros.h +++ b/deps/ledger-zxlib/include/zxmacros.h @@ -21,8 +21,7 @@ extern "C" { #endif #include "string.h" - -extern void explicit_bzero(void *__s, size_t __n) __THROW __nonnull ((1)); +extern void explicit_bzero (void *__s, size_t __n) __THROW __nonnull ((1)); #if defined(LEDGER_SPECIFIC) #include "bolos_target.h" @@ -139,6 +138,15 @@ NUM_TO_STR(int64) NUM_TO_STR(uint64) +__Z_INLINE void bip44_to_str(char *s, uint32_t max, const uint32_t path[5]) { + snprintf(s, max, "%d%s%d%s%d%s%d%s%d%s", + path[0] & 0x7FFFFFFFu, (path[0] & 0x80000000u) != 0 ? "'/" : "/", + path[1] & 0x7FFFFFFFu, (path[0] & 0x80000000u) != 0 ? "'/" : "/", + path[2] & 0x7FFFFFFFu, (path[0] & 0x80000000u) != 0 ? "'/" : "/", + path[3] & 0x7FFFFFFFu, (path[0] & 0x80000000u) != 0 ? "'/" : "/", + path[4] & 0x7FFFFFFFu, (path[0] & 0x80000000u) != 0 ? "'" : ""); +} + __Z_INLINE int8_t str_to_int8(const char *start, const char *end, char *error) { int sign = 1; diff --git a/deps/ledger-zxlib/src/bignum.c b/deps/ledger-zxlib/src/bignum.c new file mode 100644 index 00000000..56a33160 --- /dev/null +++ b/deps/ledger-zxlib/src/bignum.c @@ -0,0 +1,148 @@ +/******************************************************************************* +* (c) 2019 ZondaX GmbH +* +* 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 "zxtypes.h" +#include "bignum.h" + +bool_t bignumLittleEndian_bcdprint(char *outBuffer, uint16_t outBufferLen, + const uint8_t *inBCD, uint16_t inBCDLen) { + static const char hexchars[] = "0123456789ABCDEF"; + uint8_t started = 0; + MEMZERO(outBuffer, outBufferLen); + + if (outBufferLen < 4) { + return bool_false; + } + + if (inBCDLen * 2 > outBufferLen) { + strcpy(outBuffer, "ERR"); + return bool_false; + } + + for (uint8_t i = 0; i < inBCDLen; i++, inBCD++) { + if (started || *inBCD != 0) { + if (started || (*inBCD >> 4u) != 0) { + *outBuffer = hexchars[*inBCD >> 4u]; + outBuffer++; + }; + *outBuffer = hexchars[*inBCD & 0x0Fu]; + outBuffer++; + started = 1; + } + } + + if (!started) { + strcpy(outBuffer, "0"); + } + + return bool_true; +} + +void bignumLittleEndian_to_bcd(uint8_t *bcdOut, uint16_t bcdOutLen, + const uint8_t *binValue, uint16_t binValueLen) { + MEMZERO(bcdOut, bcdOutLen); + + uint8_t carry = 0; + for (uint16_t bitIdx = 0; bitIdx < binValueLen * 8; bitIdx++) { + // Fix bcd + for (uint16_t j = 0; j < bcdOutLen; j++) { + if ((bcdOut[j] & 0x0Fu) > 0x04u) { + bcdOut[j] += 0x03u; + } + if ((bcdOut[j] & 0xF0u) > 0x40u) { + bcdOut[j] += 0x30u; + } + } + + // get bit + const uint16_t byteIdx = bitIdx >> 3u; + const uint8_t mask = 0x80u >> (bitIdx & 0b111u); + carry = (binValue[binValueLen - byteIdx - 1] & mask) > 0; + + // Shift bcd + for (uint16_t j = 0; j < bcdOutLen; j++) { + uint8_t carry2 = bcdOut[bcdOutLen - j - 1] > 127u; + bcdOut[bcdOutLen - j - 1] <<= 1u; + bcdOut[bcdOutLen - j - 1] += carry; + carry = carry2; + } + } +} + +bool_t bignumBigEndian_bcdprint(char *outBuffer, uint16_t outBufferLen, + const uint8_t *bcdIn, uint16_t bcdInLen) { + static const char hexchars[] = "0123456789ABCDEF"; + uint8_t started = 0; + MEMZERO(outBuffer, outBufferLen); + + if (outBufferLen < 4) { + return bool_false; + } + + if (bcdInLen * 2 > outBufferLen) { + strcpy(outBuffer, "ERR"); + return bool_false; + } + + for (uint8_t i = 0; i < bcdInLen; i++) { + uint8_t v = bcdIn[bcdInLen - i - 1]; + if (started || v != 0) { + if (started || (v >> 4u) != 0) { + *outBuffer = hexchars[v >> 4u]; + outBuffer++; + }; + *outBuffer = hexchars[v & 0x0Fu]; + outBuffer++; + started = 1; + } + } + + if (!started) { + strcpy(outBuffer, "0"); + } + + return bool_true; +} + +void bignumBigEndian_to_bcd(uint8_t *bcdOut, uint16_t bcdOutLen, + const uint8_t *binValue, uint16_t binValueLen) { + MEMZERO(bcdOut, bcdOutLen); + + uint8_t carry = 0; + for (uint16_t bitIdx = 0; bitIdx < binValueLen * 8; bitIdx++) { + // Fix bcd + for (uint16_t j = 0; j < bcdOutLen; j++) { + if ((bcdOut[j] & 0x0Fu) > 0x04u) { + bcdOut[j] += 0x03u; + } + if ((bcdOut[j] & 0xF0u) > 0x40u) { + bcdOut[j] += 0x30u; + } + } + + // get bit + const uint16_t byteIdx = bitIdx >> 3u; + const uint8_t mask = 0x80u >> (bitIdx & 0b111u); + carry = (binValue[byteIdx] & mask) > 0; + + // Shift bcd + for (uint16_t j = 0; j < bcdOutLen; j++) { + uint8_t carry2 = bcdOut[j] > 127u; + bcdOut[j] <<= 1u; + bcdOut[j] += carry; + carry = carry2; + } + } +} diff --git a/deps/ledger-zxlib/src/hexutils.c b/deps/ledger-zxlib/src/hexutils.c index 7080266e..6ea676fd 100644 --- a/deps/ledger-zxlib/src/hexutils.c +++ b/deps/ledger-zxlib/src/hexutils.c @@ -19,13 +19,13 @@ #include "hexutils.h" uint8_t hex2dec(char c, char *out) { - c = tolower(c); + c = (char) tolower((int)c); - if (!isxdigit(c)) { + if (!isxdigit((int)c)) { return -1; } - if (isdigit(c)) { + if (isdigit((int)c)) { *out = c - '0'; return 0; } diff --git a/src/actions.c b/src/actions.c index b15ecd79..f8f238d4 100644 --- a/src/actions.c +++ b/src/actions.c @@ -35,6 +35,7 @@ void app_set_hrp(char *p) { uint8_t app_fill_address() { // Put data directly in the apdu buffer + MEMZERO(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE); return crypto_fillAddress(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 2); } diff --git a/src/app_main.c b/src/app_main.c index 7d33e48d..5862c939 100644 --- a/src/app_main.c +++ b/src/app_main.c @@ -25,7 +25,7 @@ #include "actions.h" #include "tx.h" #include "lib/crypto.h" -#include "cosmos.h" +#include "coin.h" #include "zxmacros.h" unsigned char G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B]; diff --git a/src/lib/cosmos.h b/src/lib/coin.h similarity index 92% rename from src/lib/cosmos.h rename to src/lib/coin.h index 0eb4f433..6bd6150d 100644 --- a/src/lib/cosmos.h +++ b/src/lib/coin.h @@ -23,7 +23,7 @@ extern "C" { #include #define BIP44_0_DEFAULT (0x80000000 | 0x2c) -#define BIP44_1_DEFAULT (0x80000000 | 0x76) +#define BIP44_1_DEFAULT (0x80000000 | 0x76) // FIXME: Change derivation path #define BIP44_2_DEFAULT (0x80000000 | 0) #define BIP44_3_DEFAULT (0) #define BIP44_4_DEFAULT (0) diff --git a/src/lib/crypto.c b/src/lib/crypto.c index 6e59aec7..bedc13fd 100644 --- a/src/lib/crypto.c +++ b/src/lib/crypto.c @@ -15,7 +15,7 @@ ********************************************************************************/ #include "crypto.h" -#include "cosmos.h" +#include "coin.h" #include #include "apdu_codes.h" @@ -70,7 +70,7 @@ void crypto_extractPublicKey(uint32_t bip44Path[BIP44_LEN_DEFAULT], uint8_t *pub pubKey[31] |= 0x80; } ////////////////////// - memcpy(pubKey, cx_publicKey.W, PK_COMPRESSED_LEN); + memcpy(pubKey, cx_publicKey.W, PK_LEN); } uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t *message, uint16_t messageLen) { @@ -162,7 +162,7 @@ void crypto_set_hrp(char *p) { } uint16_t crypto_fillAddress(uint8_t *buffer, uint16_t buffer_len) { - if (buffer_len < PK_COMPRESSED_LEN + 50) { + if (buffer_len < PK_LEN + 50) { return 0; } @@ -171,13 +171,13 @@ uint16_t crypto_fillAddress(uint8_t *buffer, uint16_t buffer_len) { // Hash it uint8_t hashed1_pk[CX_SHA256_SIZE]; - cx_hash_sha256(buffer, PK_COMPRESSED_LEN, hashed1_pk, CX_SHA256_SIZE); + cx_hash_sha256(buffer, PK_LEN, hashed1_pk, CX_SHA256_SIZE); uint8_t hashed2_pk[CX_RIPEMD160_SIZE]; ripemd160_32(hashed2_pk, hashed1_pk); - char *addr = (char *) (buffer + PK_COMPRESSED_LEN); + char *addr = (char *) (buffer + PK_LEN); bech32EncodeFromBytes(addr, bech32_hrp, hashed2_pk, CX_RIPEMD160_SIZE); - return PK_COMPRESSED_LEN + strlen(addr); + return PK_LEN + strlen(addr); } diff --git a/src/lib/crypto.h b/src/lib/crypto.h index cb20119a..4b31f9ec 100644 --- a/src/lib/crypto.h +++ b/src/lib/crypto.h @@ -17,7 +17,7 @@ #pragma once #include -#include "cosmos.h" +#include "coin.h" #ifdef __cplusplus extern "C" { @@ -25,7 +25,7 @@ extern "C" { #define BIP44_LEN_DEFAULT 5u #define MAX_BECH32_HRP_LEN 83u -#define PK_COMPRESSED_LEN 33u +#define PK_LEN 33u extern uint32_t bip44Path[BIP44_LEN_DEFAULT]; extern char *hrp; diff --git a/src/tx.h b/src/tx.h index bb96dec1..e59b247c 100644 --- a/src/tx.h +++ b/src/tx.h @@ -16,7 +16,7 @@ #pragma once #include "os.h" -#include "cosmos.h" +#include "coin.h" typedef enum { tx_no_error = 0, diff --git a/src/view.c b/src/view.c index 0d174695..19f01941 100644 --- a/src/view.c +++ b/src/view.c @@ -17,6 +17,7 @@ #include "view.h" #include "view_internal.h" +#include "crypto.h" #include "actions.h" #include "apdu_codes.h" @@ -30,7 +31,6 @@ #include view_t viewdata; -const char *address; void h_address_accept(unsigned int _) { UNUSED(_); @@ -115,6 +115,19 @@ view_error_t h_review_update_data() { return view_no_error; } +view_error_t h_addr_update_item(uint8_t idx) { + MEMZERO(viewdata.addr, MAX_CHARS_ADDR); + switch (idx) { + case 0: + snprintf(viewdata.addr, MAX_CHARS_ADDR, "%s", (char *) (G_io_apdu_buffer + PK_LEN)); + break; + case 1: + bip44_to_str(viewdata.addr, MAX_CHARS_ADDR, bip44Path); + break; + } + return view_no_error; +} + void io_seproxyhal_display(const bagl_element_t *element) { io_seproxyhal_display_default((bagl_element_t *) element); } @@ -128,8 +141,6 @@ void view_idle_show(unsigned int ignored) { } void view_address_show() { - // Address has been placed in the output buffer - address = (char *) (G_io_apdu_buffer + 32); view_address_show_impl(); } diff --git a/src/view_internal.h b/src/view_internal.h index e53cfb0b..89ae9835 100644 --- a/src/view_internal.h +++ b/src/view_internal.h @@ -26,11 +26,12 @@ #define MENU_MAIN_APP_LINE2 "Cosmos" #endif +#define CUR_FLOW G_ux.flow_stack[G_ux.stack_count-1] + #if defined(TARGET_NANOX) #define MAX_CHARS_PER_KEY_LINE 64 #define MAX_CHARS_PER_VALUE1_LINE 4096 #define MAX_CHARS_HEXMESSAGE 160 -#define CUR_FLOW G_ux.flow_stack[G_ux.stack_count-1] #else #define MAX_CHARS_PER_KEY_LINE (32+1) #define MAX_CHARS_PER_VALUE_LINE (18) @@ -38,15 +39,23 @@ #define MAX_CHARS_PER_VALUE2_LINE (MAX_CHARS_PER_VALUE_LINE+1) #define MAX_CHARS_HEXMESSAGE 40 #endif +#define MAX_CHARS_ADDR (MAX_CHARS_PER_KEY_LINE + MAX_CHARS_PER_VALUE1_LINE) -extern const char *address; +// This typically will point to G_io_apdu_buffer that is prefilled with the address typedef struct { - char key[MAX_CHARS_PER_KEY_LINE]; - char value[MAX_CHARS_PER_VALUE1_LINE]; + union { + struct { + char key[MAX_CHARS_PER_KEY_LINE]; + char value[MAX_CHARS_PER_VALUE1_LINE]; #if defined(TARGET_NANOS) - char value2[MAX_CHARS_PER_VALUE2_LINE]; + char value2[MAX_CHARS_PER_VALUE2_LINE]; #endif + }; + struct { + char addr[MAX_CHARS_ADDR]; + }; + }; int8_t idx; int8_t pageIdx; uint8_t pageCount; @@ -102,3 +111,5 @@ void h_review_increase(); void h_review_decrease(); view_error_t h_review_update_data(); + +view_error_t h_addr_update_item(uint8_t idx); diff --git a/src/view_s.c b/src/view_s.c index 27046121..d16f95b4 100644 --- a/src/view_s.c +++ b/src/view_s.c @@ -37,20 +37,31 @@ void view_sign_show_s(); ux_state_t ux; +void os_exit(uint32_t id) { + os_sched_exit(0); +} + const ux_menu_entry_t menu_main[] = { {NULL, NULL, 0, &C_icon_app, MENU_MAIN_APP_LINE1, MENU_MAIN_APP_LINE2, 33, 12}, {NULL, NULL, 0, NULL, "v"APPVERSION, NULL, 0, 0}, - {NULL, os_sched_exit, 0, &C_icon_dashboard, "Quit", NULL, 50, 29}, + {NULL, os_exit, 0, &C_icon_dashboard, "Quit", NULL, 50, 29}, UX_MENU_END }; -static const bagl_element_t view_address[] = { - UI_FillRectangle(0, 0, 0, UI_SCREEN_WIDTH, UI_SCREEN_HEIGHT, 0x000000, 0xFFFFFF), - UI_Icon(0, 128 - 7, 0, 7, 7, BAGL_GLYPH_ICON_CHECK), - UI_LabelLine(UIID_LABEL + 0, 0, 8, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, viewdata.key), - UI_LabelLine(UIID_LABEL + 0, 0, 19, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, viewdata.value), - UI_LabelLineScrolling(UIID_LABELSCROLL, 0, 30, 128, UI_11PX, UI_WHITE, UI_BLACK, viewdata.value2), -}; +UX_STEP_NOCB_INIT(ux_addr_flow_1_step, paging, + { h_addr_update_item(CUR_FLOW.index); }, + { .title = "Address", .text = viewdata.addr, }); +UX_STEP_NOCB_INIT(ux_addr_flow_2_step, paging, + { h_addr_update_item(CUR_FLOW.index); }, + { .title = "Path", .text = viewdata.addr, }); +UX_STEP_VALID(ux_addr_flow_3_step, pb, h_address_accept(0), { &C_icon_validate_14, "Ok"}); + +UX_FLOW( + ux_addr_flow, + &ux_addr_flow_1_step, + &ux_addr_flow_2_step, + &ux_addr_flow_3_step +); void h_review(unsigned int _) { UNUSED(_); view_sign_show_impl(); } @@ -76,18 +87,6 @@ static const bagl_element_t view_error[] = { UI_LabelLineScrolling(UIID_LABELSCROLL, 0, 30, 128, UI_11PX, UI_WHITE, UI_BLACK, viewdata.value2), }; -static unsigned int view_address_button(unsigned int button_mask, unsigned int button_mask_counter) { - switch (button_mask) { - case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: - case BUTTON_EVT_RELEASED | BUTTON_LEFT: - break; - case BUTTON_EVT_RELEASED | BUTTON_RIGHT: - h_address_accept(0); - break; - } - return 0; -} - static unsigned int view_error_button(unsigned int button_mask, unsigned int button_mask_counter) { switch (button_mask) { case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: @@ -195,19 +194,11 @@ void view_idle_show_impl() { } void view_address_show_impl() { -#define KEYCHUNKSIZE 12 - // move first part to key - MEMZERO(viewdata.key, MAX_CHARS_PER_KEY_LINE); - MEMCPY(viewdata.key, address, KEYCHUNKSIZE); - - // move the remainder to - snprintf(viewdata.value, - MAX_CHARS_PER_VALUE1_LINE, "%s", - address + KEYCHUNKSIZE); - - splitValueField(); - - UX_DISPLAY(view_address, view_prepro); + ux_layout_paging_reset(); + if(G_ux.stack_count == 0) { + ux_stack_push(); + } + ux_flow_init(0, ux_addr_flow, NULL); } void view_error_show_impl() { diff --git a/src/view_x.c b/src/view_x.c index 0279ecac..5b1e96ee 100644 --- a/src/view_x.c +++ b/src/view_x.c @@ -51,13 +51,19 @@ const ux_flow_step_t *const ux_idle_flow [] = { /////////// -UX_STEP_NOCB(ux_addr_flow_1_step, bnnn_paging, { .title = viewdata.key, .text = viewdata.value, }); -UX_STEP_VALID(ux_addr_flow_2_step, pb, h_address_accept(0), { &C_icon_validate_14, "Ok"}); +UX_STEP_NOCB_INIT(ux_addr_flow_1_step, bnnn_paging, + { h_addr_update_item(CUR_FLOW.index); }, + { .title = "Address", .text = viewdata.addr, }); +UX_STEP_NOCB_INIT(ux_addr_flow_2_step, bnnn_paging, + { h_addr_update_item(CUR_FLOW.index); }, + { .title = "Path", .text = viewdata.addr, }); +UX_STEP_VALID(ux_addr_flow_3_step, pb, h_address_accept(0), { &C_icon_validate_14, "Ok"}); UX_FLOW( ux_addr_flow, &ux_addr_flow_1_step, - &ux_addr_flow_2_step + &ux_addr_flow_2_step, + &ux_addr_flow_3_step ); /////////// From 6358143c5756196e5af90a86ca1c5ed10c6ca922 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Fri, 6 Dec 2019 14:10:52 +0100 Subject: [PATCH 41/78] update zxlib --- deps/ledger-zxlib/include/zxmacros.h | 21 ++++++++++++-------- deps/ledger-zxlib/tests/bip44path.cpp | 28 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 deps/ledger-zxlib/tests/bip44path.cpp diff --git a/deps/ledger-zxlib/include/zxmacros.h b/deps/ledger-zxlib/include/zxmacros.h index 4f2a9176..bebf9e0c 100644 --- a/deps/ledger-zxlib/include/zxmacros.h +++ b/deps/ledger-zxlib/include/zxmacros.h @@ -95,6 +95,7 @@ void __logstack(); #include #include +#include #define __Z_INLINE inline __attribute__((always_inline)) static @@ -206,29 +207,33 @@ __Z_INLINE int64_t str_to_int64(const char *start, const char *end, char *error) return value * sign; } -__Z_INLINE void fpuint64_to_str(char *dst, const uint64_t value, uint8_t decimals) { - char buffer[30]; - - int64_to_str(buffer, 30, value); - size_t digits = strlen(buffer); +__Z_INLINE void fpstr_to_str(char *dst, const char *number, uint8_t decimals) { + size_t digits = strlen(number); if (digits <= decimals) { *dst++ = '0'; *dst++ = '.'; for (uint16_t i = 0; i < decimals - digits; i++, dst++) *dst = '0'; - strcpy(dst, buffer); + strcpy(dst, number); } else { - strcpy(dst, buffer); + strcpy(dst, number); const size_t shift = digits - decimals; dst = dst + shift; *dst++ = '.'; - char *p = buffer + shift; + const char *p = number + shift; strcpy(dst, p); } } +__Z_INLINE void fpuint64_to_str(char *dst, const uint64_t value, uint8_t decimals) { + char buffer[30]; + MEMZERO(buffer, sizeof(buffer)); + int64_to_str(buffer, 30, value); + fpstr_to_str(dst, buffer, decimals); +} + __Z_INLINE uint64_t uint64_from_BEarray(const uint8_t data[8]) { uint64_t result = 0; for (uint8_t i = 0; i < 8u; i++) { diff --git a/deps/ledger-zxlib/tests/bip44path.cpp b/deps/ledger-zxlib/tests/bip44path.cpp new file mode 100644 index 00000000..4f94ee28 --- /dev/null +++ b/deps/ledger-zxlib/tests/bip44path.cpp @@ -0,0 +1,28 @@ +/******************************************************************************* +* (c) 2018 ZondaX GmbH +* +* 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 +#include + +namespace { + TEST(MACROS, bip44path) { + uint32_t path[] = {44, 60, 0, 0, 1}; + + char buffer[100]; + bip44_to_str(buffer, sizeof(buffer), path); + + EXPECT_EQ("44/60/0/0/1", std::string(buffer)); + } +} From 57f9294e262ad5663d1fa8baf24ebe10edbf7cd9 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Fri, 6 Dec 2019 14:12:21 +0100 Subject: [PATCH 42/78] Remove Tendermint from main menu --- Makefile | 2 +- src/view_internal.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index c910a0d0..a5ef2445 100755 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ include $(BOLOS_SDK)/Makefile.defines APPNAME = "Cosmos" APPVERSION_M=2 APPVERSION_N=2 -APPVERSION_P=1 +APPVERSION_P=2 APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path "44'/118'" diff --git a/src/view_internal.h b/src/view_internal.h index 89ae9835..a89887fd 100644 --- a/src/view_internal.h +++ b/src/view_internal.h @@ -18,12 +18,12 @@ #include -#define MENU_MAIN_APP_LINE1 "Tendermint" +#define MENU_MAIN_APP_LINE1 "Cosmos" #ifdef TESTING_ENABLED #define MENU_MAIN_APP_LINE2 "Cosmos TEST!" #else -#define MENU_MAIN_APP_LINE2 "Cosmos" +#define MENU_MAIN_APP_LINE2 "App" #endif #define CUR_FLOW G_ux.flow_stack[G_ux.stack_count-1] From 02b5e07e632bb8fc36521a0c4737cb3487279ecc Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Fri, 13 Dec 2019 10:54:30 +0100 Subject: [PATCH 43/78] nanoX fix --- src/view_x.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/view_x.c b/src/view_x.c index 5b1e96ee..b94e706e 100644 --- a/src/view_x.c +++ b/src/view_x.c @@ -193,9 +193,6 @@ void view_idle_show_impl() { } void view_address_show_impl() { - snprintf(viewdata.key, MAX_CHARS_PER_KEY_LINE, "Confirm address"); - snprintf(viewdata.value, MAX_CHARS_PER_VALUE1_LINE, "%s", address); - ux_layout_bnnn_paging_reset(); if(G_ux.stack_count == 0) { ux_stack_push(); From a7f7eb46e54e565c9fa3438a128811ebd75d69a6 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sun, 29 Dec 2019 02:13:24 +0100 Subject: [PATCH 44/78] clean up --- Makefile | 2 +- deps/ledger-zxlib/include/zxmacros.h | 2 + src/lib/json/json_parser.c | 16 +--- src/lib/json/json_parser.h | 11 +-- src/lib/json/tx_display.c | 132 +++++++++++++-------------- src/lib/json/tx_parser.c | 5 +- src/lib/parser_impl.c | 4 +- 7 files changed, 78 insertions(+), 94 deletions(-) diff --git a/Makefile b/Makefile index a5ef2445..74078251 100755 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ include $(BOLOS_SDK)/Makefile.defines APPNAME = "Cosmos" APPVERSION_M=2 APPVERSION_N=2 -APPVERSION_P=2 +APPVERSION_P=3 APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path "44'/118'" diff --git a/deps/ledger-zxlib/include/zxmacros.h b/deps/ledger-zxlib/include/zxmacros.h index bebf9e0c..21258892 100644 --- a/deps/ledger-zxlib/include/zxmacros.h +++ b/deps/ledger-zxlib/include/zxmacros.h @@ -70,6 +70,7 @@ extern void explicit_bzero (void *__s, size_t __n) __THROW __nonnull ((1)); #define MEMMOVE os_memmove #define MEMSET os_memset #define MEMCPY os_memcpy +#define MEMCMP os_memcmp #define MEMCPY_NV nvm_write #define MEMZERO explicit_bzero @@ -87,6 +88,7 @@ void __logstack(); #define MEMMOVE memmove #define MEMSET memset #define MEMCPY memcpy +#define MEMCMP memcmp #define MEMCPY_NV memcpy #define MEMZERO explicit_bzero #define LOG(str) diff --git a/src/lib/json/json_parser.c b/src/lib/json/json_parser.c index 9ddf1369..cd81202d 100644 --- a/src/lib/json/json_parser.c +++ b/src/lib/json/json_parser.c @@ -19,23 +19,13 @@ #include #include "json_parser.h" -#if defined(TARGET_NANOS) || defined(TARGET_NANOX) -#include "os.h" -#define EQUALS(_P, _Q, _LEN) (os_memcmp( PIC(_P), PIC(_Q), (_LEN))==0) -#else -#define EQUALS(_P, _Q, _LEN) (memcmp( (_P), (_Q), (_LEN))==0) -#endif - -parser_error_t json_parse(parsed_json_t *parsed_json, const char *buffer) { - return json_parse_s(parsed_json, buffer, strlen(buffer)); -} +#define EQUALS(_P, _Q, _LEN) (MEMCMP( PIC(_P), PIC(_Q), (_LEN))==0) -parser_error_t json_parse_s(parsed_json_t *parsed_json, - const char *buffer, uint16_t bufferLen) { +parser_error_t json_parse(parsed_json_t *parsed_json, const char *buffer, uint16_t bufferLen) { jsmn_parser parser; jsmn_init(&parser); - explicit_bzero(parsed_json, sizeof(parsed_json_t)); + MEMZERO(parsed_json, sizeof(parsed_json_t)); parsed_json->buffer = buffer; parsed_json->bufferLen = bufferLen; diff --git a/src/lib/json/json_parser.h b/src/lib/json/json_parser.h index 79d92267..3d5da6f2 100644 --- a/src/lib/json/json_parser.h +++ b/src/lib/json/json_parser.h @@ -62,16 +62,9 @@ typedef struct { /// \param transaction /// \param transaction_length /// \return Error message -parser_error_t json_parse_s(parsed_json_t *parsed_json, - const char *transaction, - uint16_t transaction_length); - -/// Parse json to create a token representation -/// \param parsed_json -/// \param transaction -/// \return Error message parser_error_t json_parse(parsed_json_t *parsed_json, - const char *transaction); + const char *transaction, + uint16_t transaction_length); /// Get the number of elements in the array /// \param array_token_index diff --git a/src/lib/json/tx_display.c b/src/lib/json/tx_display.c index cc7da610..b7f6da33 100644 --- a/src/lib/json/tx_display.c +++ b/src/lib/json/tx_display.c @@ -20,10 +20,6 @@ #include "lib/parser_impl.h" #include -#if defined(TARGET_NANOS) || defined(TARGET_NANOX) -#include "os.h" -#endif - #define NUM_REQUIRED_ROOT_PAGES 6 // Required pages @@ -47,12 +43,12 @@ const char *get_required_root_item(uint8_t i) { } static const uint16_t root_max_level[NUM_REQUIRED_ROOT_PAGES] = { - 2, // "chain_id", - 2, // "account_number", - 2, // "sequence", - 1, // "fee", - 2, // "memo" - 2, // "msgs" + 2, // "chain_id", + 2, // "account_number", + 2, // "sequence", + 1, // "fee", + 2, // "memo" + 2, // "msgs" }; typedef struct { @@ -69,14 +65,14 @@ void _indexRootFields() { } // Clear cache - explicit_bzero(&display_cache, sizeof(display_cache_t)); + MEMZERO(&display_cache, sizeof(display_cache_t)); // Calculate pages for (int8_t idx = 0; idx < NUM_REQUIRED_ROOT_PAGES; idx++) { - const int16_t subroot_token_idx = object_get_value( - &parser_tx_obj.json, - ROOT_TOKEN_INDEX, - get_required_root_item(idx)); + const int16_t subroot_token_idx = object_get_value(&parser_tx_obj.json, + ROOT_TOKEN_INDEX, + get_required_root_item(idx)); + if (subroot_token_idx < 0) { break; } @@ -175,70 +171,70 @@ typedef struct { } key_subst_t; static const key_subst_t key_substitutions[NUM_KEY_SUBSTITUTIONS] = { - {"chain_id", "Chain ID"}, - {"account_number", "Account"}, - {"sequence", "Sequence"}, - {"memo", "Memo"}, - {"fee/amount", "Fee"}, - {"fee/gas", "Gas"}, - {"msgs/type", "Type"}, - - // FIXME: Are these obsolete?? multisend? - {"msgs/inputs/address", "Source Address"}, - {"msgs/inputs/coins", "Source Coins"}, - {"msgs/outputs/address", "Dest Address"}, - {"msgs/outputs/coins", "Dest Coins"}, - - // MsgSend - {"msgs/value/from_address", "From"}, - {"msgs/value/to_address", "To"}, - {"msgs/value/amount", "Amount"}, - - // MsgDelegate - {"msgs/value/delegator_address", "Delegator"}, - {"msgs/value/validator_address", "Validator"}, - - // MsgUndelegate + {"chain_id", "Chain ID"}, + {"account_number", "Account"}, + {"sequence", "Sequence"}, + {"memo", "Memo"}, + {"fee/amount", "Fee"}, + {"fee/gas", "Gas"}, + {"msgs/type", "Type"}, + + // FIXME: Are these obsolete?? multisend? + {"msgs/inputs/address", "Source Address"}, + {"msgs/inputs/coins", "Source Coins"}, + {"msgs/outputs/address", "Dest Address"}, + {"msgs/outputs/coins", "Dest Coins"}, + + // MsgSend + {"msgs/value/from_address", "From"}, + {"msgs/value/to_address", "To"}, + {"msgs/value/amount", "Amount"}, + + // MsgDelegate + {"msgs/value/delegator_address", "Delegator"}, + {"msgs/value/validator_address", "Validator"}, + + // MsgUndelegate // {"msgs/value/delegator_address", "Delegator"}, // {"msgs/value/validator_address", "Validator"}, - // MsgBeginRedelegate + // MsgBeginRedelegate // {"msgs/value/delegator_address", "Delegator"}, - {"msgs/value/validator_src_address", "Validator Source"}, - {"msgs/value/validator_dst_address", "Validator Dest"}, - - // MsgSubmitProposal - {"msgs/value/description", "Description"}, - {"msgs/value/initial_deposit/amount", "Deposit Amount"}, - {"msgs/value/initial_deposit/denom", "Deposit Denom"}, - {"msgs/value/proposal_type", "Proposal"}, - {"msgs/value/proposer", "Proposer"}, - {"msgs/value/title", "Title"}, - - // MsgDeposit - {"msgs/value/depositer", "Sender"}, - {"msgs/value/proposal_id", "Proposal ID"}, - {"msgs/value/amount", "Amount"}, - - // MsgVote - {"msgs/value/voter", "Description"}, + {"msgs/value/validator_src_address", "Validator Source"}, + {"msgs/value/validator_dst_address", "Validator Dest"}, + + // MsgSubmitProposal + {"msgs/value/description", "Description"}, + {"msgs/value/initial_deposit/amount", "Deposit Amount"}, + {"msgs/value/initial_deposit/denom", "Deposit Denom"}, + {"msgs/value/proposal_type", "Proposal"}, + {"msgs/value/proposer", "Proposer"}, + {"msgs/value/title", "Title"}, + + // MsgDeposit + {"msgs/value/depositer", "Sender"}, + {"msgs/value/proposal_id", "Proposal ID"}, + {"msgs/value/amount", "Amount"}, + + // MsgVote + {"msgs/value/voter", "Description"}, // {"msgs/value/proposal_id", "Proposal ID"}, - {"msgs/value/option", "Option"}, + {"msgs/value/option", "Option"}, - // MsgWithdrawDelegationReward + // MsgWithdrawDelegationReward // {"msgs/value/delegator_address", "Delegator"}, // duplicated // {"msgs/value/validator_address", "Validator"}, // duplicated }; static const key_subst_t value_substitutions[NUM_VALUE_SUBSTITUTIONS] = { - {"cosmos-sdk/MsgSend", "Send"}, - {"cosmos-sdk/MsgDelegate", "Delegate"}, - {"cosmos-sdk/MsgUndelegate", "Undelegate"}, - {"cosmos-sdk/MsgBeginRedelegate", "Redelegate"}, - {"cosmos-sdk/MsgSubmitProposal", "Propose"}, - {"cosmos-sdk/MsgDeposit", "Deposit"}, - {"cosmos-sdk/MsgVote", "Vote"}, - {"cosmos-sdk/MsgWithdrawDelegationReward", "Withdraw Reward"}, + {"cosmos-sdk/MsgSend", "Send"}, + {"cosmos-sdk/MsgDelegate", "Delegate"}, + {"cosmos-sdk/MsgUndelegate", "Undelegate"}, + {"cosmos-sdk/MsgBeginRedelegate", "Redelegate"}, + {"cosmos-sdk/MsgSubmitProposal", "Propose"}, + {"cosmos-sdk/MsgDeposit", "Deposit"}, + {"cosmos-sdk/MsgVote", "Vote"}, + {"cosmos-sdk/MsgWithdrawDelegationReward", "Withdraw Reward"}, }; void tx_display_make_friendly() { diff --git a/src/lib/json/tx_parser.c b/src/lib/json/tx_parser.c index 1a3e8984..964a854c 100644 --- a/src/lib/json/tx_parser.c +++ b/src/lib/json/tx_parser.c @@ -55,7 +55,6 @@ parser_error_t tx_getToken(uint16_t token_index, uint8_t pageIdx, uint8_t *pageCount) { *pageCount = 1; MEMZERO(out_val, out_val_len); - snprintf(out_val, out_val_len, " "); const int16_t token_start = parser_tx_obj.json.tokens[token_index].start; const int16_t token_end = parser_tx_obj.json.tokens[token_index].end; @@ -103,6 +102,10 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to parser_error_t err; const jsmntype_t token_type = parser_tx_obj.json.tokens[root_token_index].type; + if (parser_tx_obj.tx == NULL) { + return parser_no_data; + } + if (parser_tx_obj.query.max_level <= 0 || parser_tx_obj.query.max_depth <= 0 || token_type == JSMN_STRING || token_type == JSMN_PRIMITIVE) { diff --git a/src/lib/parser_impl.c b/src/lib/parser_impl.c index e7e1fb0f..95e0ae82 100644 --- a/src/lib/parser_impl.c +++ b/src/lib/parser_impl.c @@ -104,8 +104,8 @@ const char *parser_getErrorDescription(parser_error_t err) { } parser_error_t _readTx(parser_context_t *c, parser_tx_t *v) { - parser_error_t err = json_parse_s(&parser_tx_obj.json, - (const char *) c->buffer, c->bufferLen); + parser_error_t err = json_parse(&parser_tx_obj.json, + (const char *) c->buffer, c->bufferLen); if (err != parser_ok) { return err; } From 9e7d9ef1d5305d63073021b70762c7698ecaed9a Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Mon, 30 Dec 2019 15:22:08 +0100 Subject: [PATCH 45/78] updating zxlib --- deps/ledger-zxlib/include/zxmacros.h | 22 ++- deps/ledger-zxlib/tests/bip44path.cpp | 20 ++- deps/ledger-zxlib/tests/doubledabble.cpp | 164 +++++++++++++++++++++++ deps/ledger-zxlib/tests/macros.cpp | 8 +- 4 files changed, 202 insertions(+), 12 deletions(-) create mode 100644 deps/ledger-zxlib/tests/doubledabble.cpp diff --git a/deps/ledger-zxlib/include/zxmacros.h b/deps/ledger-zxlib/include/zxmacros.h index 21258892..e875aaef 100644 --- a/deps/ledger-zxlib/include/zxmacros.h +++ b/deps/ledger-zxlib/include/zxmacros.h @@ -21,7 +21,10 @@ extern "C" { #endif #include "string.h" +#ifndef __APPLE__ extern void explicit_bzero (void *__s, size_t __n) __THROW __nonnull ((1)); +#endif +#define __Z_INLINE inline __attribute__((always_inline)) static #if defined(LEDGER_SPECIFIC) #include "bolos_target.h" @@ -90,7 +93,14 @@ void __logstack(); #define MEMCPY memcpy #define MEMCMP memcmp #define MEMCPY_NV memcpy + +#ifndef __APPLE__ #define MEMZERO explicit_bzero +#else +__Z_INLINE void __memzero(void *buffer, size_t s) { memset(buffer, 0, s); } +#define MEMZERO __memzero +#endif + #define LOG(str) #define LOGSTACK() #endif @@ -99,8 +109,6 @@ void __logstack(); #include #include -#define __Z_INLINE inline __attribute__((always_inline)) static - #define SET_NV(DST, TYPE, VAL) { \ TYPE nvset_tmp=(VAL); \ MEMCPY_NV((void*) PIC(DST), (void *) PIC(&nvset_tmp), sizeof(TYPE)); \ @@ -117,7 +125,7 @@ void __logstack(); #define NUM_TO_STR(TYPE) __Z_INLINE const char * TYPE##_to_str(char *data, int dataLen, TYPE##_t number) { \ if (dataLen < 2) return "Buffer too small"; \ - MEMZERO(data, dataLen); \ + MEMZERO(data, dataLen); \ char *p = data; \ if (number < 0) { *(p++) = '-'; data++; } \ else if (number == 0) { *(p++) = '0'; } \ @@ -144,10 +152,10 @@ NUM_TO_STR(uint64) __Z_INLINE void bip44_to_str(char *s, uint32_t max, const uint32_t path[5]) { snprintf(s, max, "%d%s%d%s%d%s%d%s%d%s", path[0] & 0x7FFFFFFFu, (path[0] & 0x80000000u) != 0 ? "'/" : "/", - path[1] & 0x7FFFFFFFu, (path[0] & 0x80000000u) != 0 ? "'/" : "/", - path[2] & 0x7FFFFFFFu, (path[0] & 0x80000000u) != 0 ? "'/" : "/", - path[3] & 0x7FFFFFFFu, (path[0] & 0x80000000u) != 0 ? "'/" : "/", - path[4] & 0x7FFFFFFFu, (path[0] & 0x80000000u) != 0 ? "'" : ""); + path[1] & 0x7FFFFFFFu, (path[1] & 0x80000000u) != 0 ? "'/" : "/", + path[2] & 0x7FFFFFFFu, (path[2] & 0x80000000u) != 0 ? "'/" : "/", + path[3] & 0x7FFFFFFFu, (path[3] & 0x80000000u) != 0 ? "'/" : "/", + path[4] & 0x7FFFFFFFu, (path[4] & 0x80000000u) != 0 ? "'" : ""); } __Z_INLINE int8_t str_to_int8(const char *start, const char *end, char *error) { diff --git a/deps/ledger-zxlib/tests/bip44path.cpp b/deps/ledger-zxlib/tests/bip44path.cpp index 4f94ee28..18da83f0 100644 --- a/deps/ledger-zxlib/tests/bip44path.cpp +++ b/deps/ledger-zxlib/tests/bip44path.cpp @@ -17,7 +17,7 @@ #include namespace { - TEST(MACROS, bip44path) { + TEST(MACROS, bip44path1) { uint32_t path[] = {44, 60, 0, 0, 1}; char buffer[100]; @@ -25,4 +25,22 @@ namespace { EXPECT_EQ("44/60/0/0/1", std::string(buffer)); } + + TEST(MACROS, bip44path2) { + uint32_t path[] = {0x8000002c, 60, 0, 0, 1}; + + char buffer[100]; + bip44_to_str(buffer, sizeof(buffer), path); + + EXPECT_EQ("44'/60/0/0/1", std::string(buffer)); + } + + TEST(MACROS, bip44path3) { + uint32_t path[] = {0x8000002c, 60, 0, 0, 0x80000001}; + + char buffer[100]; + bip44_to_str(buffer, sizeof(buffer), path); + + EXPECT_EQ("44'/60/0/0/1'", std::string(buffer)); + } } diff --git a/deps/ledger-zxlib/tests/doubledabble.cpp b/deps/ledger-zxlib/tests/doubledabble.cpp new file mode 100644 index 00000000..dfad45b2 --- /dev/null +++ b/deps/ledger-zxlib/tests/doubledabble.cpp @@ -0,0 +1,164 @@ +/******************************************************************************* +* (c) 2019 ZondaX GmbH +* +* 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 "bignum.h" + +using ::testing::TestWithParam; +using ::testing::Values; + +typedef struct { + std::string hex; + std::string expectedOutput; +} bignum_testcase_t; + +class BignumLittleEndianTests : public ::testing::TestWithParam { +}; + +class BignumBigEndianTests : public ::testing::TestWithParam { +}; + +INSTANTIATE_TEST_CASE_P + +( + BignumTestCases, BignumLittleEndianTests, testing::Values( + bignum_testcase_t{"00", "0"}, + bignum_testcase_t{"01", "1"}, + bignum_testcase_t{"0001", "256"}, + bignum_testcase_t{"03E8", "59395"}, + bignum_testcase_t{"E803", "1000"}, + bignum_testcase_t{"10", "16"}, + bignum_testcase_t{"FF01", "511"}, + bignum_testcase_t{"0102", "513"}, + bignum_testcase_t{"FFFF01", "131071"}, + bignum_testcase_t{"a08601", "100000"}, + bignum_testcase_t{"40420f", "1000000"}, + bignum_testcase_t{"809698", "10000000"}, + bignum_testcase_t{"002d3101", "20000000"}, + bignum_testcase_t{"00e1f505", "100000000"}, + bignum_testcase_t{"00407a10f35a", "100000000000000"}, + bignum_testcase_t{"d2029649", "1234567890"}, + bignum_testcase_t{"d20a3fce96f1cf8c9cb4378c37a4873f17621ebce404f5aa13", + "123456789012345678901234567890123456789012345678901234567890"} +)); + +// Check that bignums are printed properly (parametric tests) +TEST_P(BignumLittleEndianTests, print) { + auto testcase = GetParam(); + + uint8_t inBuffer[100]; + auto inBufferLen = parseHexString(testcase.hex.c_str(), inBuffer); + + uint8_t bcdOut[100]; + uint16_t bcdOutLen = sizeof(bcdOut); + + bignumLittleEndian_to_bcd(bcdOut, bcdOutLen, inBuffer, inBufferLen); + + char bufferUI[300]; + bignumLittleEndian_bcdprint(bufferUI, sizeof(bufferUI), bcdOut, bcdOutLen); + EXPECT_THAT(std::string(bufferUI), testing::Eq(testcase.expectedOutput)); +} + +// Check that bignums are printed properly (range tests) +TEST(BignumLittleEndianTests, range) { + uint8_t inBuffer[100]; + + for (uint64_t i = 0; i < 10000; i++) { + std::stringstream s; + uint64_t tmp = i; + while (tmp != 0) { + s << std::setfill('0') << std::setw(2) << std::hex << tmp % 256; + tmp /= 256; + } + auto inBufferLen = parseHexString(s.str().c_str(), inBuffer); + + uint8_t bcdOut[100]; + uint16_t bcdOutLen = sizeof(bcdOut); + bignumLittleEndian_to_bcd(bcdOut, bcdOutLen, inBuffer, inBufferLen); + char bufferUI[300]; + bignumLittleEndian_bcdprint(bufferUI, sizeof(bufferUI), bcdOut, bcdOutLen); + + std::stringstream expected; + expected << i; + EXPECT_THAT(std::string(bufferUI), testing::Eq(expected.str())) << s.str(); + } +} + +INSTANTIATE_TEST_CASE_P + +( + BignumTestCases, BignumBigEndianTests, testing::Values( + bignum_testcase_t{"00", "0"}, + bignum_testcase_t{"01", "1"}, + bignum_testcase_t{"0001", "1"}, + bignum_testcase_t{"000001", "1"}, + bignum_testcase_t{"03E8", "1000"}, + bignum_testcase_t{"E803", "59395"}, + bignum_testcase_t{"10", "16"}, + bignum_testcase_t{"FF01", "65281"}, + bignum_testcase_t{"01FF", "511"}, + bignum_testcase_t{"0102", "258"}, + bignum_testcase_t{"FFFF01", "16776961"}, + bignum_testcase_t{"a08601", "10520065"}, + bignum_testcase_t{"40420f", "4211215"}, + bignum_testcase_t{"809698", "8427160"}, + bignum_testcase_t{"002d3101", "2961665"}, + bignum_testcase_t{"00e1f505", "14808325"}, + bignum_testcase_t{"00407a10f35a", "276925838170"}, + bignum_testcase_t{"d2029649", "3523384905"}, + bignum_testcase_t{"d20a3fce96f1cf8c9cb4378c37a4873f17621ebce404f5aa13", + "1318442675213289749221432902819395197389189473307425559128595"} +)); + +// Check that bignums are printed properly (parametric tests) +TEST_P(BignumBigEndianTests, print) { + auto testcase = GetParam(); + + uint8_t inBuffer[100]; + auto inBufferLen = parseHexString(testcase.hex.c_str(), inBuffer); + + uint8_t bcdOut[100]; + uint16_t bcdOutLen = sizeof(bcdOut); + bignumBigEndian_to_bcd(bcdOut, bcdOutLen, inBuffer, inBufferLen); + + char bufferUI[300]; + bignumBigEndian_bcdprint(bufferUI, sizeof(bufferUI), bcdOut, bcdOutLen); + EXPECT_THAT(std::string(bufferUI), testing::Eq(testcase.expectedOutput)); +} + +// Check that bignums are printed properly (range tests) +TEST(BignumBigEndianTests, range) { + uint8_t inBuffer[100]; + + for (uint64_t i = 0; i < 2500; i += 7) { + std::stringstream s; + s << std::setfill('0') << std::setw(10) << std::hex << i; + std::cout << s.str() << std::endl; + auto inBufferLen = parseHexString(s.str().c_str(), inBuffer); + + uint8_t bcdOut[100]; + uint16_t bcdOutLen = sizeof(bcdOut); + bignumBigEndian_to_bcd(bcdOut, bcdOutLen, inBuffer, inBufferLen); + char bufferUI[300]; + bignumBigEndian_bcdprint(bufferUI, sizeof(bufferUI), bcdOut, bcdOutLen); + + std::stringstream expected; + expected << i; + EXPECT_THAT(std::string(bufferUI), testing::Eq(expected.str())) << s.str(); + } +} diff --git a/deps/ledger-zxlib/tests/macros.cpp b/deps/ledger-zxlib/tests/macros.cpp index 4d5cdc2f..86c9ab70 100644 --- a/deps/ledger-zxlib/tests/macros.cpp +++ b/deps/ledger-zxlib/tests/macros.cpp @@ -104,7 +104,7 @@ TEST(INT64_TO_STR, TooSmall_0) { char temp[1]; const char* error = int64_to_str(temp, sizeof(temp), int64_t(0)); - EXPECT_STREQ("Size too small", error); + EXPECT_STREQ("Buffer too small", error); } TEST(INT64_TO_STR, FitsJust) { @@ -119,7 +119,7 @@ TEST(INT64_TO_STR, TooSmall_10) { char temp[2]; const char* error = int64_to_str(temp, sizeof(temp), int64_t(10)); - EXPECT_STREQ("Size too small", error); + EXPECT_STREQ("Buffer too small", error); } TEST(INT64_TO_STR, Max) { @@ -218,10 +218,10 @@ TEST(STR_TO_INT8, DummyData_Negative) { TEST(STR_TO_INT64, Min) { - char numberStr[] = "-9223372036854775808"; + char numberStr[] = "-9223372036854775807"; char error = 0; int64_t number = str_to_int64(numberStr, numberStr + strlen(numberStr), &error); - EXPECT_EQ(-9223372036854775808, number); + EXPECT_EQ(-9223372036854775807, number); EXPECT_EQ(0, error); } From d96adcb3230f402f790276cece3a7190556cb681 Mon Sep 17 00:00:00 2001 From: Thomas Julien <59569970+thomas-rj@users.noreply.github.com> Date: Wed, 12 Feb 2020 13:30:45 +0100 Subject: [PATCH 46/78] Update Makefile Removed sudo command from makefile for loading and deleting apps --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 74078251..31b9b9bb 100755 --- a/Makefile +++ b/Makefile @@ -134,10 +134,10 @@ SDK_SOURCE_PATH += lib_stusb lib_u2f lib_stusb_impl SDK_SOURCE_PATH += lib_ux load: - sudo -E python -m ledgerblue.loadApp $(APP_LOAD_PARAMS) + python -m ledgerblue.loadApp $(APP_LOAD_PARAMS) delete: - sudo -E python -m ledgerblue.deleteApp $(COMMON_DELETE_PARAMS) + python -m ledgerblue.deleteApp $(COMMON_DELETE_PARAMS) # Import generic rules from the SDK include $(BOLOS_SDK)/Makefile.rules From dd6968d637ce12170cab11dc8521ab76eb4b7c47 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Mon, 17 Feb 2020 20:47:40 +0100 Subject: [PATCH 47/78] improve app package --- .gitignore | 21 ++++---------- Makefile | 22 ++++++++------ {pkgdemo => pkg}/.gitkeep | 0 pkgdemo.sh | 38 ------------------------- scripts/template.sh | 60 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 79 insertions(+), 62 deletions(-) rename {pkgdemo => pkg}/.gitkeep (100%) delete mode 100755 pkgdemo.sh create mode 100755 scripts/template.sh diff --git a/.gitignore b/.gitignore index 6c65850a..8bdda311 100644 --- a/.gitignore +++ b/.gitignore @@ -41,28 +41,17 @@ cmake-build-debug/ src/ledger/bin/ src/ledger/debug/ src/ledger/obj/ -src/goclient/vendor -pkg/ -src/goclient/goclient - -src/ledger/pkgdemo/loaddemo\.sh - -src/ledger/pkgdemo/app\.hex - obj/app\.elf - debug/app\.map - debug/app\.asm - bin/app\.hex - bin/app\.elf - src/glyphs\.h - src/glyphs\.c - bin/app.sha256 - bin/app.apdu + +pkg/bin/app.hex +pkg/zxtool.sh + +pkg/demo.zip diff --git a/Makefile b/Makefile index 31b9b9bb..ecd1be8e 100755 --- a/Makefile +++ b/Makefile @@ -25,9 +25,10 @@ include $(BOLOS_SDK)/Makefile.defines APPNAME = "Cosmos" APPVERSION_M=2 APPVERSION_N=2 -APPVERSION_P=3 +APPVERSION_P=4 -APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path "44'/118'" +APPPATH = "44'/118'" +APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path $(APPPATH) ifeq ($(TARGET_NAME),TARGET_NANOS) SCRIPT_LD:=$(CURDIR)/script.ld @@ -43,6 +44,17 @@ $(error ICONNAME is not set) endif all: default + @echo "#!/usr/bin/env bash" > $(CURDIR)/pkg/zxtool.sh + @echo "APPNAME=\"${APPNAME}\"" >> $(CURDIR)/pkg/zxtool.sh + @echo "APPVERSION=\"${APPVERSION}\"" >> $(CURDIR)/pkg/zxtool.sh + @echo "APPPATH=\""${APPPATH}"\"" >> $(CURDIR)/pkg/zxtool.sh + @echo "LOAD_PARAMS=\"${COMMON_LOAD_PARAMS}\"" >> $(CURDIR)/pkg/zxtool.sh + @echo "DELETE_PARAMS=\"${COMMON_DELETE_PARAMS}\"" >> $(CURDIR)/pkg/zxtool.sh + @echo "APPHEX=\"" >> $(CURDIR)/pkg/zxtool.sh + @cat $(CURDIR)/bin/app.hex >> $(CURDIR)/pkg/zxtool.sh + @echo "\"" >> $(CURDIR)/pkg/zxtool.sh + @cat $(CURDIR)/scripts/template.sh >> $(CURDIR)/pkg/zxtool.sh + @chmod +x $(CURDIR)/pkg/zxtool.sh ############ # Platform @@ -133,12 +145,6 @@ SDK_SOURCE_PATH += lib_stusb lib_u2f lib_stusb_impl #SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl SDK_SOURCE_PATH += lib_ux -load: - python -m ledgerblue.loadApp $(APP_LOAD_PARAMS) - -delete: - python -m ledgerblue.deleteApp $(COMMON_DELETE_PARAMS) - # Import generic rules from the SDK include $(BOLOS_SDK)/Makefile.rules diff --git a/pkgdemo/.gitkeep b/pkg/.gitkeep similarity index 100% rename from pkgdemo/.gitkeep rename to pkg/.gitkeep diff --git a/pkgdemo.sh b/pkgdemo.sh deleted file mode 100755 index c061c255..00000000 --- a/pkgdemo.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash -#******************************************************************************* -#* (c) 2018 ZondaX GmbH -#* -#* 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. -#******************************************************************************** -SCRIPT_DIR=$(cd $(dirname $0) && pwd) - -# Copy hex file -cp ${SCRIPT_DIR}/bin/app.hex ${SCRIPT_DIR}/pkgdemo/app.hex - -APPNAME=$1 -APPVERSION=$2 -ICONNAME=$3 - -TARGET_ID=0x31100003 -DATASIZE=$(cat ${SCRIPT_DIR}/debug/app.map |grep _nvram_data_size | tr -s ' ' | cut -f2 -d' ') -ICONHEX=$(python ${BOLOS_SDK}/icon.py ${ICONNAME} hexbitmaponly) - -cat >${SCRIPT_DIR}/pkgdemo/loaddemo.sh </dev/null; then + echo Python 3 is not installed + exit +fi + +python3 -m ledgerblue.loadApp -h &>/dev/null; +if [ $? -ne 0 ]; then + echo + echo "ERR: ledgerblue pip package not found." + echo "please install using 'pip3 install ledgerblue'" + echo + exit +fi + +TMP_HEX_DIR=$(mktemp -d -t ci-XXXXXXXXXX) +mkdir -p ${TMP_HEX_DIR}/bin +BIN_HEX_FILE=${TMP_HEX_DIR}/bin/app.hex +echo -e "${APPHEX}" > ${BIN_HEX_FILE} + +case "$1" in + 'load') + cd "$TMP_HEX_DIR" || exit + python3 -m ledgerblue.loadApp --appFlags 0x200 --delete ${LOAD_PARAMS} --path ${APPPATH} --path "44'/1'" + ;; + 'delete') + python3 -m ledgerblue.deleteApp ${DELETE_PARAMS} + ;; + 'version') + echo "v${APPVERSION}" + ;; + *) + echo "Zondax Installer [$APPNAME-$APPVERSION] [Warning: use only for test/demo apps]" + echo " load - Load $APPNAME app" + echo " delete - Delete $APPNAME app" + echo " version - Show $APPNAME app version" +esac From 835e708435ef40d030fa980b9e9a1be07241cf93 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Mon, 2 Mar 2020 12:30:31 +0100 Subject: [PATCH 48/78] reintroduce load and delete --- Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Makefile b/Makefile index ecd1be8e..c2767ec6 100755 --- a/Makefile +++ b/Makefile @@ -151,5 +151,12 @@ include $(BOLOS_SDK)/Makefile.rules #add dependency on custom makefile filename dep/%.d: %.c Makefile +# load, delete and listvariants are provided to comply with Ledger requirements +load: + python -m ledgerblue.loadApp $(APP_LOAD_PARAMS) + +delete: + python -m ledgerblue.deleteApp $(COMMON_DELETE_PARAMS) + listvariants: @echo VARIANTS COIN cosmos From 50935ee0ba7d419d3804e0bc86a6b47e419b0bff Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Wed, 4 Mar 2020 14:31:45 +0100 Subject: [PATCH 49/78] workaround for empty strings in nano X --- Makefile | 2 +- src/view_x.c | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c2767ec6..f1101302 100755 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ include $(BOLOS_SDK)/Makefile.defines APPNAME = "Cosmos" APPVERSION_M=2 APPVERSION_N=2 -APPVERSION_P=4 +APPVERSION_P=5 APPPATH = "44'/118'" APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path $(APPPATH) diff --git a/src/view_x.c b/src/view_x.c index b94e706e..99c3e482 100644 --- a/src/view_x.c +++ b/src/view_x.c @@ -177,7 +177,12 @@ void h_review_loop_end() { ux_flow_relayout(); } -void splitValueField() {} +void splitValueField() { + uint16_t vlen = strlen(viewdata.value); + if (vlen == 0 ) { + strcpy(viewdata.value, " "); + } +} ////////////////////////// ////////////////////////// From fde83f4a2c21e1a5bb7887ab73a0a812432c80d3 Mon Sep 17 00:00:00 2001 From: Gerry Agbobada <10496163+gagbo@users.noreply.github.com> Date: Wed, 11 Mar 2020 17:23:19 +0100 Subject: [PATCH 50/78] Add P1 documentation in INS_GET_ADDR_SECP256K1 Behaviour inferred from https://github.com/cosmos/ledger-cosmos-app/blob/03529e29cb408a027bba9038c69d1deccff6edc2/src/app_main.c#L184-L191 --- docs/APDUSPEC.md | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/docs/APDUSPEC.md b/docs/APDUSPEC.md index c7cdefb2..6a4fbb1b 100644 --- a/docs/APDUSPEC.md +++ b/docs/APDUSPEC.md @@ -110,20 +110,21 @@ All other packets/chunks should contain message to sign #### Command -| Field | Type | Content | Expected | -| ---------- | -------------- | ---------------------- | -------------- | -| CLA | byte (1) | Application Identifier | 0x55 | -| INS | byte (1) | Instruction ID | 0x04 | -| P1 | byte (1) | Parameter 1 | ignored | -| P2 | byte (1) | Parameter 2 | ignored | -| L | byte (1) | Bytes in payload | (depends) | -| HRP_LEN | byte(1) | Bech32 HRP Length | 1<=HRP_LEN<=83 | -| HRP | byte (HRP_LEN) | Bech32 HRP | | -| Path[0] | byte (4) | Derivation Path Data | 44 | -| Path[1] | byte (4) | Derivation Path Data | 118 | -| Path[2] | byte (4) | Derivation Path Data | ? | -| Path[3] | byte (4) | Derivation Path Data | ? | -| Path[4] | byte (4) | Derivation Path Data | ? | +| Field | Type | Content | Expected | +| ---------- | -------------- | ------------------------------ | -------------- | +| CLA | byte (1) | Application Identifier | 0x55 | +| INS | byte (1) | Instruction ID | 0x04 | +| P1 | byte (1) | Display address/path on device | 0x00 No | +| | | | 0x01 Yes | +| P2 | byte (1) | Parameter 2 | ignored | +| L | byte (1) | Bytes in payload | (depends) | +| HRP_LEN | byte(1) | Bech32 HRP Length | 1<=HRP_LEN<=83 | +| HRP | byte (HRP_LEN) | Bech32 HRP | | +| Path[0] | byte (4) | Derivation Path Data | 44 | +| Path[1] | byte (4) | Derivation Path Data | 118 | +| Path[2] | byte (4) | Derivation Path Data | ? | +| Path[3] | byte (4) | Derivation Path Data | ? | +| Path[4] | byte (4) | Derivation Path Data | ? | First three items in the derivation path will be hardened automatically hardened From 0df86e47ca8037b0a490602cc04202fbd90c423e Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Thu, 5 Mar 2020 18:09:56 +0100 Subject: [PATCH 51/78] remove warnings --- Makefile | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index f1101302..4851bfbc 100755 --- a/Makefile +++ b/Makefile @@ -19,13 +19,16 @@ ifeq ($(BOLOS_SDK),) $(error BOLOS_SDK is not set) endif + +MY_DIR := $(dir $(lastword $(MAKEFILE_LIST))) + include $(BOLOS_SDK)/Makefile.defines # Main app configuration APPNAME = "Cosmos" APPVERSION_M=2 APPVERSION_N=2 -APPVERSION_P=5 +APPVERSION_P=6 APPPATH = "44'/118'" APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path $(APPPATH) @@ -76,6 +79,7 @@ DEFINES += U2F_PROXY_MAGIC=\"CSM\" DEFINES += USB_SEGMENT_SIZE=64 DEFINES += U2F_MAX_MESSAGE_SIZE=264 #257+5+2 DEFINES += HAVE_BOLOS_APP_STACK_CANARY +DEFINES += NDEBUG WEBUSB_URL = www.ledgerwallet.com DEFINES += HAVE_WEBUSB WEBUSB_URL_SIZE_B=$(shell echo -n $(WEBUSB_URL) | wc -c) WEBUSB_URL=$(shell echo -n $(WEBUSB_URL) | sed -e "s/./\\\'\0\\\',/g") @@ -104,12 +108,11 @@ endif #Feature temporarily disabled DEFINES += LEDGER_SPECIFIC -#DEFINES += TESTING_ENABLED # Compiler, assembler, and linker ifneq ($(BOLOS_ENV),) -$(info BOLOS_ENV=$(BOLOS_ENV)) +$(info BOLOS_ENV is $(BOLOS_ENV)) CLANGPATH := $(BOLOS_ENV)/clang-arm-fropi/bin/ GCCPATH := $(BOLOS_ENV)/gcc-arm-none-eabi-5_3-2016q1/bin/ else @@ -127,7 +130,7 @@ endif ######################### CC := $(CLANGPATH)clang -CFLAGS += -O3 -Os +CFLAGS += -O3 -Os -Wno-unknown-pragmas AS := $(GCCPATH)arm-none-eabi-gcc AFLAGS += From a60bd1389a3d8ee3a5a5a633e891664c1a70e172 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Thu, 26 Mar 2020 21:35:02 +0100 Subject: [PATCH 52/78] Upgrade project structure --- .circleci/config.yml | 63 ++ .editorconfig | 16 + .gitignore | 105 ++- .gitmodules | 3 + CMakeLists.txt | 104 +++ LICENSE | 2 +- Makefile | 184 +--- README.md | 156 +++- app/.gitignore | 57 ++ app/LICENSE | 201 ++++ app/Makefile | 173 ++++ cosmos.png => app/cosmos.png | Bin {glyphs => app/glyphs}/digit_dot.gif | Bin {glyphs => app/glyphs}/icon_app.gif | Bin {glyphs => app/glyphs}/icon_back.gif | Bin {glyphs => app/glyphs}/icon_close.gif | Bin {glyphs => app/glyphs}/icon_crossmark.gif | Bin {glyphs => app/glyphs}/icon_dashboard.gif | Bin {glyphs => app/glyphs}/icon_eye.gif | Bin {glyphs => app/glyphs}/icon_validate.gif | Bin {glyphs => app/glyphs}/icon_validate_14.gif | Bin nanos_icon.gif => app/nanos_icon.gif | Bin nanox_icon.gif => app/nanox_icon.gif | Bin {pkg => app/pkg}/.gitkeep | 0 script.ld => app/script.ld | 0 {src => app/src}/.gitignore | 2 + src/lib/parser_txdef.h => app/src/coin.h | 32 +- {src => app/src/common}/actions.c | 4 +- {src => app/src/common}/actions.h | 3 +- {src => app/src/common}/app_main.c | 30 +- {src => app/src/common}/app_main.h | 2 +- {src => app/src/common}/main.c | 2 +- {src/lib => app/src/common}/parser.h | 14 +- {src/lib => app/src/common}/parser_common.h | 23 +- {src => app/src/common}/tx.c | 4 +- {src => app/src/common}/tx.h | 2 +- {src/lib => app/src}/crypto.c | 65 +- {src/lib => app/src}/crypto.h | 15 +- {src/lib => app/src}/json/json_parser.c | 36 +- {src/lib => app/src}/json/json_parser.h | 8 +- {src/lib => app/src}/parser.c | 121 ++- {src/lib => app/src}/parser_impl.c | 15 +- {src/lib => app/src}/parser_impl.h | 11 +- app/src/parser_txdef.h | 73 ++ app/src/tx_display.c | 297 ++++++ {src/lib/json => app/src}/tx_display.h | 35 +- {src/lib/json => app/src}/tx_parser.c | 72 +- {src/lib/json => app/src}/tx_parser.h | 32 +- {src/lib/json => app/src}/tx_validate.c | 46 +- {src/lib/json => app/src}/tx_validate.h | 6 +- cmake/conan/CMakeLists.txt | 12 + cmake/gtest/CMakeLists.txt | 31 + cmake/gtest/CMakeLists.txt.gtest.in | 16 + conanfile.txt | 6 + deps/jsmn/src/jsmn.c | 490 +++++----- deps/ledger-zxlib/CMakeLists.txt | 2 +- deps/ledger-zxlib/LICENSE | 2 +- deps/ledger-zxlib/README.md | 6 +- {src => deps/ledger-zxlib/app/common}/view.c | 16 +- {src => deps/ledger-zxlib/app/common}/view.h | 2 +- .../ledger-zxlib/app/common}/view_internal.h | 11 +- .../ledger-zxlib/app/common}/view_s.c | 2 +- .../ledger-zxlib/app/common}/view_x.c | 2 +- deps/ledger-zxlib/cmake/dockerized_build.mk | 131 +++ deps/ledger-zxlib/include/apdu_codes.h | 11 +- deps/ledger-zxlib/include/bech32.h | 2 +- deps/ledger-zxlib/include/bignum.h | 2 +- deps/ledger-zxlib/include/bittools.h | 6 +- deps/ledger-zxlib/include/buffering.h | 2 +- deps/ledger-zxlib/include/hexutils.h | 4 +- deps/ledger-zxlib/include/sigutils.h | 41 + deps/ledger-zxlib/include/utf8.h | 6 +- deps/ledger-zxlib/include/view_templates.h | 2 +- deps/ledger-zxlib/include/zxformat.h | 249 +++++ deps/ledger-zxlib/include/zxmacros.h | 208 +---- deps/ledger-zxlib/include/zxtypes.h | 2 +- deps/ledger-zxlib/scripts/install_deps.sh | 32 + .../ledger-zxlib/scripts}/template.sh | 0 deps/ledger-zxlib/src/bech32.c | 2 +- deps/ledger-zxlib/src/bignum.c | 2 +- deps/ledger-zxlib/src/buffering.c | 2 +- deps/ledger-zxlib/src/hexutils.c | 27 +- deps/ledger-zxlib/src/segwit_addr.c | 18 +- deps/ledger-zxlib/src/sigutils.c | 98 ++ deps/ledger-zxlib/src/zxmacros.c | 2 +- deps/ledger-zxlib/templates/Makefile.root | 29 + deps/ledger-zxlib/tests/asciify.cpp | 2 +- deps/ledger-zxlib/tests/bech32.cpp | 2 +- deps/ledger-zxlib/tests/bip44path.cpp | 44 +- deps/ledger-zxlib/tests/buffering_tests.cpp | 11 +- deps/ledger-zxlib/tests/doubledabble.cpp | 92 +- deps/ledger-zxlib/tests/hexutils.cpp | 12 +- deps/ledger-zxlib/tests/macros.cpp | 488 ++++++---- deps/ledger-zxlib/tests/sigutils.cpp | 46 + deps/nanos-secure-sdk | 1 + docs/BUILD.md | 5 - docs/img/cosmos_app1.png | Bin 0 -> 15105 bytes docs/img/cosmos_app2.png | Bin 0 -> 20554 bytes docs/img/cosmos_app3.png | Bin 0 -> 13197 bytes docs/img/tendermint_app.png | Bin 0 -> 11365 bytes fuzzing/Makefile | 34 + fuzzing/fuzzing.md | 24 + fuzzing/fuzzingMain.cpp | 60 ++ fuzzing/inputs/input1.txt | 1 + fuzzing/inputs/input2.txt | 1 + fuzzing/inputs/input3.txt | 1 + fuzzing/inputs/input4.txt | 1 + fuzzing/inputs/input5.txt | 1 + fuzzing/inputs/input6.txt | 1 + fuzzing/inputs/input7.txt | 1 + fuzzing/inputs/input8.txt | 1 + fuzzing/scripts/build.sh | 28 + fuzzing/scripts/login.sh | 30 + fuzzing/scripts/plot.sh | 21 + fuzzing/scripts/run.sh | 9 + fuzzing/scripts/run_slaves.sh | 10 + src/lib/json/tx_display.c | 262 ------ tests/json_parser.cpp | 355 ++++++++ tests/testcases/manual.json | 859 ++++++++++++++++++ tests/tx_parse.cpp | 153 ++++ tests/tx_validate.cpp | 300 ++++++ tests/ui_output.cpp | 99 ++ tests/util/common.cpp | 58 ++ tests/util/common.h | 31 + tests/util/testcases.cpp | 67 ++ src/lib/coin.h => tests/util/testcases.h | 27 +- 126 files changed, 5142 insertions(+), 1486 deletions(-) create mode 100644 .circleci/config.yml create mode 100644 .editorconfig create mode 100644 .gitmodules create mode 100644 CMakeLists.txt mode change 100755 => 100644 Makefile create mode 100644 app/.gitignore create mode 100644 app/LICENSE create mode 100755 app/Makefile rename cosmos.png => app/cosmos.png (100%) rename {glyphs => app/glyphs}/digit_dot.gif (100%) rename {glyphs => app/glyphs}/icon_app.gif (100%) rename {glyphs => app/glyphs}/icon_back.gif (100%) rename {glyphs => app/glyphs}/icon_close.gif (100%) rename {glyphs => app/glyphs}/icon_crossmark.gif (100%) rename {glyphs => app/glyphs}/icon_dashboard.gif (100%) rename {glyphs => app/glyphs}/icon_eye.gif (100%) rename {glyphs => app/glyphs}/icon_validate.gif (100%) rename {glyphs => app/glyphs}/icon_validate_14.gif (100%) rename nanos_icon.gif => app/nanos_icon.gif (100%) rename nanox_icon.gif => app/nanox_icon.gif (100%) rename {pkg => app/pkg}/.gitkeep (100%) rename script.ld => app/script.ld (100%) rename {src => app/src}/.gitignore (96%) rename src/lib/parser_txdef.h => app/src/coin.h (66%) rename {src => app/src/common}/actions.c (97%) rename {src => app/src/common}/actions.h (95%) rename {src => app/src/common}/app_main.c (93%) rename {src => app/src/common}/app_main.h (98%) rename {src => app/src/common}/main.c (97%) rename {src/lib => app/src/common}/parser.h (79%) rename {src/lib => app/src/common}/parser_common.h (80%) rename {src => app/src/common}/tx.c (98%) rename {src => app/src/common}/tx.h (98%) rename {src/lib => app/src}/crypto.c (77%) rename {src/lib => app/src}/crypto.h (71%) rename {src/lib => app/src}/json/json_parser.c (91%) rename {src/lib => app/src}/json/json_parser.h (97%) rename {src/lib => app/src}/parser.c (61%) rename {src/lib => app/src}/parser_impl.c (90%) rename {src/lib => app/src}/parser_impl.h (85%) create mode 100644 app/src/parser_txdef.h create mode 100644 app/src/tx_display.c rename {src/lib/json => app/src}/tx_display.h (58%) rename {src/lib/json => app/src}/tx_parser.c (69%) rename {src/lib/json => app/src}/tx_parser.h (67%) rename {src/lib/json => app/src}/tx_validate.c (86%) rename {src/lib/json => app/src}/tx_validate.h (91%) create mode 100644 cmake/conan/CMakeLists.txt create mode 100644 cmake/gtest/CMakeLists.txt create mode 100644 cmake/gtest/CMakeLists.txt.gtest.in create mode 100644 conanfile.txt rename {src => deps/ledger-zxlib/app/common}/view.c (87%) rename {src => deps/ledger-zxlib/app/common}/view.h (97%) rename {src => deps/ledger-zxlib/app/common}/view_internal.h (94%) rename {src => deps/ledger-zxlib/app/common}/view_s.c (99%) rename {src => deps/ledger-zxlib/app/common}/view_x.c (99%) create mode 100644 deps/ledger-zxlib/cmake/dockerized_build.mk create mode 100644 deps/ledger-zxlib/include/sigutils.h create mode 100644 deps/ledger-zxlib/include/zxformat.h create mode 100755 deps/ledger-zxlib/scripts/install_deps.sh rename {scripts => deps/ledger-zxlib/scripts}/template.sh (100%) create mode 100644 deps/ledger-zxlib/src/sigutils.c create mode 100644 deps/ledger-zxlib/templates/Makefile.root create mode 100644 deps/ledger-zxlib/tests/sigutils.cpp create mode 160000 deps/nanos-secure-sdk delete mode 100644 docs/BUILD.md create mode 100644 docs/img/cosmos_app1.png create mode 100644 docs/img/cosmos_app2.png create mode 100644 docs/img/cosmos_app3.png create mode 100644 docs/img/tendermint_app.png create mode 100644 fuzzing/Makefile create mode 100644 fuzzing/fuzzing.md create mode 100644 fuzzing/fuzzingMain.cpp create mode 100644 fuzzing/inputs/input1.txt create mode 100644 fuzzing/inputs/input2.txt create mode 100644 fuzzing/inputs/input3.txt create mode 100644 fuzzing/inputs/input4.txt create mode 100644 fuzzing/inputs/input5.txt create mode 100644 fuzzing/inputs/input6.txt create mode 100644 fuzzing/inputs/input7.txt create mode 100644 fuzzing/inputs/input8.txt create mode 100755 fuzzing/scripts/build.sh create mode 100755 fuzzing/scripts/login.sh create mode 100755 fuzzing/scripts/plot.sh create mode 100755 fuzzing/scripts/run.sh create mode 100755 fuzzing/scripts/run_slaves.sh delete mode 100644 src/lib/json/tx_display.c create mode 100644 tests/json_parser.cpp create mode 100644 tests/testcases/manual.json create mode 100644 tests/tx_parse.cpp create mode 100644 tests/tx_validate.cpp create mode 100644 tests/ui_output.cpp create mode 100644 tests/util/common.cpp create mode 100644 tests/util/common.h create mode 100644 tests/util/testcases.cpp rename src/lib/coin.h => tests/util/testcases.h (66%) diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..7dbf4a7f --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,63 @@ +version: 2 +jobs: + build: + docker: + - image: zondax/circleci:latest + steps: + - checkout + - run: git submodule update --init --recursive + - run: cmake -DDISABLE_DOCKER_BUILDS=ON -DCMAKE_BUILD_TYPE=Debug . && make + # Unfortunately need to disable leak sanitizer https://github.com/google/sanitizers/issues/916 + # Still run all other ASAN components + - run: GTEST_COLOR=1 ASAN_OPTIONS=detect_leaks=0 ctest -VV + + build_ledger: + docker: + - image: zondax/ledger-docker-bolos:v1.0 + environment: + - BOLOS_SDK=/home/zondax/project/deps/nanos-secure-sdk + - BOLOS_ENV=/opt/bolos + steps: + - checkout + # Docker entrypoint is not considered + - run: git submodule update --init --recursive + - run: + name: Build + command: | + source /home/zondax/.cargo/env + cd /home/zondax/project + make + + build_package: + docker: + - image: zondax/ledger-docker-bolos:v1.0 + environment: + - BOLOS_SDK=/home/zondax/project/deps/nanos-secure-sdk + - BOLOS_ENV=/opt/bolos + steps: + - checkout + - run: git submodule update --init --recursive + - run: + name: Build + command: | + source /home/zondax/.cargo/env + cd /home/zondax/project + make + - store_artifacts: + path: /home/zondax/project/app/pkg/zxtool.sh + - run: /home/zondax/go/bin/ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete $(/home/zondax/project/app/pkg/zxtool.sh version) /home/zondax/project/app/pkg/zxtool.sh + +workflows: + version: 2 + build_all: + jobs: + - build + - build_ledger + - build_package: + requires: + - build + - build_ledger + filters: + branches: + only: + - master diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..2d7db14f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +trim_trailing_whitespace = true +end_of_line = lf +insert_final_newline = true + +[*.{c,h,cpp,hpp}] +indent_style = space +indent_size = 4 + +[*.{yml,sh}] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore index 8bdda311..ed1588b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,57 +1,88 @@ +# Created by .ignore support plugin (hsz.mobi) +### C template # Prerequisites *.d -# Compiled Object files -*.slo -*.lo +# Object files *.o +*.ko *.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp # Precompiled Headers *.gch *.pch -# Compiled Dynamic libraries +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll *.so +*.so.* *.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib # Executables *.exe *.out *.app +*.i*86 +*.x86_64 +*.hex -# OS related files -.DS_Store +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb -# Others -cmake-build-debug/ -\.idea/workspace\.xml +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +\cmake-build-debug \.idea/ -src/ledger/bin/ -src/ledger/debug/ -src/ledger/obj/ -obj/app\.elf -debug/app\.map -debug/app\.asm -bin/app\.hex -bin/app\.elf -src/glyphs\.h -src/glyphs\.c -bin/app.sha256 -bin/app.apdu - -pkg/bin/app.hex -pkg/zxtool.sh - -pkg/demo.zip +/tmp/ +/deps/nano2-sdk/ + +# Created by cmake +googletest-download/ +googletest-src/ +googletest-build/ +CMakeFiles/ +CMakeCache.txt +unittests +*.cmake +Testing/ +cmake-build-fuzz/ + +# Others +/cmake-build-debug/ +/cmake-build-fuzz/ +\.idea +/app/bin/ +/app/debug/ +/app/obj/ + +\deps/* +!\deps/nanos-secure-sdk +!\deps/ledger-zxlib +!\deps/tinycbor +!\deps/BLAKE + +app/src/glyphs.c + +app/src/glyphs.h diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..f207938b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/nanos-secure-sdk"] + path = deps/nanos-secure-sdk + url = https://github.com/LedgerHQ/nanos-secure-sdk.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..16ab114b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,104 @@ +#******************************************************************************* +#* (c) 2018 Zondax GmbH +#* +#* 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. +#******************************************************************************** +cmake_minimum_required(VERSION 3.0) +project(ledger-cosmos VERSION 0.0.0) +enable_testing() + +cmake_policy(SET CMP0025 NEW) +set(CMAKE_CXX_STANDARD 11) + +include(cmake/conan/CMakeLists.txt) +add_subdirectory(cmake/gtest) + +string(APPEND CMAKE_CXX_FLAGS " -fsanitize=address -fno-omit-frame-pointer") +string(APPEND CMAKE_LINKER_FLAGS " -fsanitize=address -fno-omit-frame-pointer") + +############################################################## +############################################################## +# static libs +file(GLOB_RECURSE JSMN_SRC + deps/jsmn/src/jsmn.c + ) + +file(GLOB_RECURSE LIB_SRC + app/src/json/json_parser.c + app/src/tx_parser.c + app/src/tx_display.c + app/src/tx_validate.c + app/src/parser.c + app/src/parser_impl.c + ) + +add_library(app_lib STATIC + ${LIB_SRC} + ${JSMN_SRC} + ) + +target_include_directories(app_lib PUBLIC + deps/ledger-zxlib/include + deps/jsmn/src + app/src + ) + +############################################################## +############################################################## +# Tests +file(GLOB_RECURSE TESTS_SRC + ${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp) + +add_executable(unittests ${TESTS_SRC}) +target_include_directories(unittests PUBLIC + ${gtest_SOURCE_DIR}/include + ${gmock_SOURCE_DIR}/include + ${CONAN_INCLUDE_DIRS_FMT} + ${CONAN_INCLUDE_DIRS_JSONCPP} + deps/jsmn/src + ) + +target_link_libraries(unittests PRIVATE + gtest_main + app_lib + CONAN_PKG::fmt + CONAN_PKG::jsoncpp) + +add_test(unittests ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unittests) +set_tests_properties(unittests PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests) + +file(GLOB_RECURSE FUZZING_SRC + ${CMAKE_CURRENT_SOURCE_DIR}/fuzzing/fuzzingMain.cpp + tests/util/common.cpp + ) + +add_executable(fuzzing_stub ${FUZZING_SRC}) +target_include_directories(fuzzing_stub PUBLIC + app/src + deps/jsmn/src + ) +target_link_libraries(fuzzing_stub app_lib) + +############################################################### +# Force tests to depend from app compiling +############################################################### + +#set(DISABLE_DOCKER_BUILDS OFF CACHE BOOL "Disables Docker Builds") +# +#if (NOT DISABLE_DOCKER_BUILDS) +# add_custom_target(ledger_app +# COMMAND make build +# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +# ) +# add_dependencies(unittests ledger_app) +#endif () diff --git a/LICENSE b/LICENSE index c8e8e95a..0fa613e8 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2018 ZondaX GmbH + Copyright 2018-2020 Zondax GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/Makefile b/Makefile old mode 100755 new mode 100644 index 4851bfbc..a3fcae5f --- a/Makefile +++ b/Makefile @@ -1,165 +1,29 @@ #******************************************************************************* -# Ledger App -# (c) 2017 Ledger -# (c) 2018 ZondaX GmbH -# -# 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. -#******************************************************************************* +#* (c) 2019 Zondax GmbH +#* +#* 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. +#******************************************************************************** + +# We use BOLOS_SDK to determine the develoment environment that is being used +# BOLOS_SDK IS DEFINED We use the plain Makefile for Ledger +# BOLOS_SDK NOT DEFINED We use a containerized build approach ifeq ($(BOLOS_SDK),) -$(error BOLOS_SDK is not set) -endif - -MY_DIR := $(dir $(lastword $(MAKEFILE_LIST))) - -include $(BOLOS_SDK)/Makefile.defines - -# Main app configuration -APPNAME = "Cosmos" -APPVERSION_M=2 -APPVERSION_N=2 -APPVERSION_P=6 - -APPPATH = "44'/118'" -APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path $(APPPATH) - -ifeq ($(TARGET_NAME),TARGET_NANOS) -SCRIPT_LD:=$(CURDIR)/script.ld -ICONNAME:=$(CURDIR)/nanos_icon.gif -endif - -ifeq ($(TARGET_NAME),TARGET_NANOX) -ICONNAME:=$(CURDIR)/nanox_icon.gif -endif - -ifndef ICONNAME -$(error ICONNAME is not set) -endif - -all: default - @echo "#!/usr/bin/env bash" > $(CURDIR)/pkg/zxtool.sh - @echo "APPNAME=\"${APPNAME}\"" >> $(CURDIR)/pkg/zxtool.sh - @echo "APPVERSION=\"${APPVERSION}\"" >> $(CURDIR)/pkg/zxtool.sh - @echo "APPPATH=\""${APPPATH}"\"" >> $(CURDIR)/pkg/zxtool.sh - @echo "LOAD_PARAMS=\"${COMMON_LOAD_PARAMS}\"" >> $(CURDIR)/pkg/zxtool.sh - @echo "DELETE_PARAMS=\"${COMMON_DELETE_PARAMS}\"" >> $(CURDIR)/pkg/zxtool.sh - @echo "APPHEX=\"" >> $(CURDIR)/pkg/zxtool.sh - @cat $(CURDIR)/bin/app.hex >> $(CURDIR)/pkg/zxtool.sh - @echo "\"" >> $(CURDIR)/pkg/zxtool.sh - @cat $(CURDIR)/scripts/template.sh >> $(CURDIR)/pkg/zxtool.sh - @chmod +x $(CURDIR)/pkg/zxtool.sh - -############ -# Platform - -DEFINES += UNUSED\(x\)=\(void\)x -DEFINES += PRINTF\(...\)= - -APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) -DEFINES += APPVERSION=\"$(APPVERSION)\" - -DEFINES += OS_IO_SEPROXYHAL -DEFINES += HAVE_BAGL HAVE_SPRINTF -DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=7 IO_HID_EP_LENGTH=64 HAVE_USB_APDU - -DEFINES += LEDGER_MAJOR_VERSION=$(APPVERSION_M) LEDGER_MINOR_VERSION=$(APPVERSION_N) LEDGER_PATCH_VERSION=$(APPVERSION_P) - -DEFINES += HAVE_U2F HAVE_IO_U2F -DEFINES += U2F_PROXY_MAGIC=\"CSM\" -DEFINES += USB_SEGMENT_SIZE=64 -DEFINES += U2F_MAX_MESSAGE_SIZE=264 #257+5+2 -DEFINES += HAVE_BOLOS_APP_STACK_CANARY -DEFINES += NDEBUG - -WEBUSB_URL = www.ledgerwallet.com -DEFINES += HAVE_WEBUSB WEBUSB_URL_SIZE_B=$(shell echo -n $(WEBUSB_URL) | wc -c) WEBUSB_URL=$(shell echo -n $(WEBUSB_URL) | sed -e "s/./\\\'\0\\\',/g") - -ifeq ($(TARGET_NAME),TARGET_NANOX) -DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=300 - -DEFINES += HAVE_GLO096 -DEFINES += HAVE_BAGL BAGL_WIDTH=128 BAGL_HEIGHT=64 -DEFINES += HAVE_BAGL_ELLIPSIS # long label truncation feature -DEFINES += HAVE_BAGL_FONT_OPEN_SANS_REGULAR_11PX -DEFINES += HAVE_BAGL_FONT_OPEN_SANS_EXTRABOLD_11PX -DEFINES += HAVE_BAGL_FONT_OPEN_SANS_LIGHT_16PX - -DEFINES += HAVE_UX_FLOW - -#SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl -SDK_SOURCE_PATH += lib_ux -else -# Assume Nano S -DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=128 -DEFINES += HAVE_BOLOS_UX COMPLIANCE_UX_160 HAVE_UX_LEGACY HAVE_UX_FLOW -endif - -# X specific - -#Feature temporarily disabled -DEFINES += LEDGER_SPECIFIC - -# Compiler, assembler, and linker - -ifneq ($(BOLOS_ENV),) -$(info BOLOS_ENV is $(BOLOS_ENV)) -CLANGPATH := $(BOLOS_ENV)/clang-arm-fropi/bin/ -GCCPATH := $(BOLOS_ENV)/gcc-arm-none-eabi-5_3-2016q1/bin/ +include $(CURDIR)/deps/ledger-zxlib/cmake/dockerized_build.mk else -$(info BOLOS_ENV is not set: falling back to CLANGPATH and GCCPATH) -endif - -ifeq ($(CLANGPATH),) -$(info CLANGPATH is not set: clang will be used from PATH) +default: + $(MAKE) -C app +%: + $(info "Calling app Makefile for target $@") + $(MAKE) -C app $@ endif - -ifeq ($(GCCPATH),) -$(info GCCPATH is not set: arm-none-eabi-* will be used from PATH) -endif - -######################### - -CC := $(CLANGPATH)clang -CFLAGS += -O3 -Os -Wno-unknown-pragmas - -AS := $(GCCPATH)arm-none-eabi-gcc -AFLAGS += - -LD := $(GCCPATH)arm-none-eabi-gcc -LDFLAGS += -O3 -Os -LDLIBS += -lm -lgcc -lc - -########################## -include $(BOLOS_SDK)/Makefile.glyphs - -APP_SOURCE_PATH += src deps/jsmn/src deps/ledger-zxlib/include deps/ledger-zxlib/src -SDK_SOURCE_PATH += lib_stusb lib_u2f lib_stusb_impl - -#SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl -SDK_SOURCE_PATH += lib_ux - -# Import generic rules from the SDK -include $(BOLOS_SDK)/Makefile.rules - -#add dependency on custom makefile filename -dep/%.d: %.c Makefile - -# load, delete and listvariants are provided to comply with Ledger requirements -load: - python -m ledgerblue.loadApp $(APP_LOAD_PARAMS) - -delete: - python -m ledgerblue.deleteApp $(COMMON_DELETE_PARAMS) - -listvariants: - @echo VARIANTS COIN cosmos diff --git a/README.md b/README.md index f34ba795..a40e55b9 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,156 @@ -# Cosmos Ledger Nano S/X +# Ledger Cosmos app [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![CircleCI](https://circleci.com/gh/Zondax/ledger-cosmos.svg?style=shield)](https://circleci.com/gh/Zondax/ledger-cosmos) +[![CodeFactor](https://www.codefactor.io/repository/github/zondax/ledger-cosmos/badge)](https://www.codefactor.io/repository/github/zondax/ledger-cosmos) -**THIS IS A DEVELOPMENT REPO** +This repository contains: -*THE OFFICIAL RELEASE IS PUBLISHED AND AVAILABLE IN LEDGER LIVE* +- Ledger Nano S/X Cosmos app +- Specs / Documentation +- C++ unit tests -This is a submodule that only contains the user app according to Ledger specs. +## Installing -Please refer to the [Ledger-Cosmos](https://github.com/cosmos/ledger-cosmos) for the complete source code, build instructions, etc. (unit tests, integration tests, documentation, etc.) +### Cosmos app + +The Cosmos app is already available in [Ledger Live](https://www.ledger.com/pages/ledger-live). Our preferred and recommended hardware wallet! + +- Open Ledger Live and go to Settings (gear icon on the right): + +![](docs/img/cosmos_app1.png) + +- Enable developer mode (last option): + +![](docs/img/cosmos_app2.png) + +- Now go back to manager and search for Cosmos: + +![](docs/img/cosmos_app3.png) + +# Building + +**Please only use a TEST DEVICE!** + +**We strongly recommend using Linux as your development environment.** + +## Get source +Apart from cloning, be sure you get all the submodules: +``` +git submodule update --init --recursive +``` + +## Dependencies + +#### Ledger Nano S + +- This project requires Ledger firmware 1.6 + +- The current repository keeps track of Ledger's SDK but it is possible to override it by changing the git submodule. + +#### Docker CE + +- Please install docker CE. The instructions can be found here: https://docs.docker.com/install/ + +#### Ubuntu Dependencies +- Install the following packages: + ``` + sudo apt update && apt-get -y install build-essential git wget cmake \ + libssl-dev libgmp-dev autoconf libtool + ``` + +#### Other dependencies + +- You need Python 3. In most cases, `make deps` will be able to install all additional dependencies: + + ```bash + make deps + ``` + +- You also need to install [Conan](https://conan.io/) + + ```bash + pip install conan + ``` + +*Warning*: Some IDEs may not use the same python interpreter or virtual enviroment as the one you used when running `pip`. +If you see conan is not found, check that you installed the package in the same interpreter as the one that launches `cmake`. + +# Prepare your development device + + **Please do not use a Ledger device with funds for development purposes.** + + **Have a second device that is used ONLY for development and testing** + + There are a few additional steps that increase reproducibility and simplify development: + +**1 - Ensure your device works in your OS** +- In Linux hosts it might be necessary to adjust udev rules, etc. Refer to Ledger documentation: https://support.ledger.com/hc/en-us/articles/115005165269-Fix-connection-issues + +**2 - Set a test mnemonic** + +All our tests expect the device to be configured with a known test mnemonic. + +- Plug your device while pressing the right button + +- Your device will show "Recovery" in the screen + +- Double click + +- Run `make dev_init`. This will take about 2 minutes. The device will be initialized to: + + ``` + PIN: 5555 + Mnemonic: equip will roof matter pink blind book anxiety banner elbow sun young + ``` + +**3 - Add a development certificate** + +- Plug your device while pressing the right button + +- Your device will show "Recovery" in the screen + +- Click both buttons at the same time + +- Enter your pin if necessary + +- Run `make dev_ca`. The device will receive a development certificate to avoid constant manual confirmations. + + +# Building the Ledger App + +The Makefile will build the firmware in a docker container and leave the binary in the correct directory. + +- Build + + The following command will build the app firmware inside a container and load to your device: + ``` + make # Builds the app + ``` + +- Upload to a device + The following command will upload the application to the ledger. _Warning: The application will be deleted before uploading._ + ``` + make load # Builds and loads the app to the device + ``` + +# Development (building C++ Code / Tests) + +This is useful when you want to make changes to libraries, run unit tests, etc. It will build all common libraries and unit tests. + +## Building unit tests +While we recommend you configure your preferred development environment, the minimum steps are as follows: + + ``` + mkdir build + cd build + cmake .. && make + ``` + **Run unit tests** + ``` + export GTEST_COLOR=1 && ctest -VV + ``` + +## Specifications + +- [APDU Protocol](docs/APDUSPEC.md) + - [Transaction format](docs/TXSPEC.md) diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 00000000..1cd0c2d7 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,57 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# OS related files +.DS_Store + +# Others +cmake-build-debug/ +../.idea/workspace.xml +\.idea/ +src/ledger/bin/ +src/ledger/debug/ +src/ledger/obj/ +obj/app\.elf +debug/app\.map +debug/app\.asm +bin/app\.hex +bin/app\.elf +app/src/common/glyphs.h +app/src/common/glyphs.c +bin/app.sha256 +bin/app.apdu + +pkg/bin/app.hex +pkg/zxtool.sh + +pkg/demo.zip diff --git a/app/LICENSE b/app/LICENSE new file mode 100644 index 00000000..dbd648b9 --- /dev/null +++ b/app/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 Zondax GmbH + + 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. diff --git a/app/Makefile b/app/Makefile new file mode 100755 index 00000000..9c27e442 --- /dev/null +++ b/app/Makefile @@ -0,0 +1,173 @@ +#******************************************************************************* +# Ledger App +# (c) 2018-2020 Zondax GmbH +# (c) 2017 Ledger +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#******************************************************************************* + +ifeq ($(BOLOS_SDK),) +$(error BOLOS_SDK is not set) +endif + +MY_DIR := $(dir $(lastword $(MAKEFILE_LIST))) + +include $(BOLOS_SDK)/Makefile.defines + +# Main app configuration +APPNAME = "Cosmos" +APPVERSION_M=3 +APPVERSION_N=0 +APPVERSION_P=0 + +APPPATH = "44'/118'" +APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path $(APPPATH) + +ifeq ($(TARGET_NAME),TARGET_NANOS) +SCRIPT_LD:=$(CURDIR)/script.ld +ICONNAME:=$(CURDIR)/nanos_icon.gif +endif + +ifeq ($(TARGET_NAME),TARGET_NANOX) +ICONNAME:=$(CURDIR)/nanox_icon.gif +endif + +ifndef ICONNAME +$(error ICONNAME is not set) +endif + +all: default + @echo "#!/usr/bin/env bash" > $(CURDIR)/pkg/zxtool.sh + @echo "APPNAME=\"${APPNAME}\"" >> $(CURDIR)/pkg/zxtool.sh + @echo "APPVERSION=\"${APPVERSION}\"" >> $(CURDIR)/pkg/zxtool.sh + @echo "APPPATH=\""${APPPATH}"\"" >> $(CURDIR)/pkg/zxtool.sh + @echo "LOAD_PARAMS=\"${COMMON_LOAD_PARAMS}\"" >> $(CURDIR)/pkg/zxtool.sh + @echo "DELETE_PARAMS=\"${COMMON_DELETE_PARAMS}\"" >> $(CURDIR)/pkg/zxtool.sh + @echo "APPHEX=\"" >> $(CURDIR)/pkg/zxtool.sh + @cat $(CURDIR)/bin/app.hex >> $(CURDIR)/pkg/zxtool.sh + @echo "\"" >> $(CURDIR)/pkg/zxtool.sh + @cat $(CURDIR)/../deps/ledger-zxlib/scripts/template.sh >> $(CURDIR)/pkg/zxtool.sh + @chmod +x $(CURDIR)/pkg/zxtool.sh + +############ +# Platform + +DEFINES += UNUSED\(x\)=\(void\)x +DEFINES += PRINTF\(...\)= + +APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) +DEFINES += APPVERSION=\"$(APPVERSION)\" + +DEFINES += OS_IO_SEPROXYHAL +DEFINES += HAVE_BAGL HAVE_SPRINTF +DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=7 IO_HID_EP_LENGTH=64 HAVE_USB_APDU + +DEFINES += LEDGER_MAJOR_VERSION=$(APPVERSION_M) LEDGER_MINOR_VERSION=$(APPVERSION_N) LEDGER_PATCH_VERSION=$(APPVERSION_P) + +DEFINES += HAVE_U2F HAVE_IO_U2F +DEFINES += U2F_PROXY_MAGIC=\"CSM\" +DEFINES += USB_SEGMENT_SIZE=64 +DEFINES += U2F_MAX_MESSAGE_SIZE=264 #257+5+2 +DEFINES += HAVE_BOLOS_APP_STACK_CANARY +DEFINES += NDEBUG + +WEBUSB_URL = www.ledgerwallet.com +DEFINES += HAVE_WEBUSB WEBUSB_URL_SIZE_B=$(shell echo -n $(WEBUSB_URL) | wc -c) WEBUSB_URL=$(shell echo -n $(WEBUSB_URL) | sed -e "s/./\\\'\0\\\',/g") + +ifeq ($(TARGET_NAME),TARGET_NANOX) +DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=300 + +DEFINES += HAVE_GLO096 +DEFINES += HAVE_BAGL BAGL_WIDTH=128 BAGL_HEIGHT=64 +DEFINES += HAVE_BAGL_ELLIPSIS # long label truncation feature +DEFINES += HAVE_BAGL_FONT_OPEN_SANS_REGULAR_11PX +DEFINES += HAVE_BAGL_FONT_OPEN_SANS_EXTRABOLD_11PX +DEFINES += HAVE_BAGL_FONT_OPEN_SANS_LIGHT_16PX + +DEFINES += HAVE_UX_FLOW + +#SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl +SDK_SOURCE_PATH += lib_ux +else +# Assume Nano S +DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=128 +DEFINES += HAVE_BOLOS_UX COMPLIANCE_UX_160 HAVE_UX_LEGACY HAVE_UX_FLOW +endif + +# X specific + +#Feature temporarily disabled +DEFINES += LEDGER_SPECIFIC + +# Compiler, assembler, and linker + +ifneq ($(BOLOS_ENV),) +$(info BOLOS_ENV is $(BOLOS_ENV)) +CLANGPATH := $(BOLOS_ENV)/clang-arm-fropi/bin/ +GCCPATH := $(BOLOS_ENV)/gcc-arm-none-eabi-5_3-2016q1/bin/ +else +$(info BOLOS_ENV is not set: falling back to CLANGPATH and GCCPATH) +endif + +ifeq ($(CLANGPATH),) +$(info CLANGPATH is not set: clang will be used from PATH) +endif + +ifeq ($(GCCPATH),) +$(info GCCPATH is not set: arm-none-eabi-* will be used from PATH) +endif + +######################### + +CC := $(CLANGPATH)clang +CFLAGS += -O3 -Os -Wno-unknown-pragmas + +AS := $(GCCPATH)arm-none-eabi-gcc +AFLAGS += + +LD := $(GCCPATH)arm-none-eabi-gcc +LDFLAGS += -O3 -Os +LDLIBS += -lm -lgcc -lc + +########################## +include $(BOLOS_SDK)/Makefile.glyphs + +APP_SOURCE_PATH += $(MY_DIR)/src +APP_SOURCE_PATH += $(MY_DIR)/../deps/ledger-zxlib/include +APP_SOURCE_PATH += $(MY_DIR)/../deps/ledger-zxlib/src +APP_SOURCE_PATH += $(MY_DIR)/../deps/ledger-zxlib/app/common +APP_SOURCE_PATH += $(MY_DIR)/../deps/jsmn/src + +SDK_SOURCE_PATH += lib_stusb lib_u2f lib_stusb_impl + +#SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl +SDK_SOURCE_PATH += lib_ux + +# Import generic rules from the SDK +include $(BOLOS_SDK)/Makefile.rules + +#add dependency on custom makefile filename +dep/%.d: %.c Makefile + +rust: + # Do nothing + +# load, delete and listvariants are provided to comply with Ledger requirements +load: + python -m ledgerblue.loadApp $(APP_LOAD_PARAMS) + +delete: + python -m ledgerblue.deleteApp $(COMMON_DELETE_PARAMS) + +listvariants: + @echo VARIANTS COIN cosmos diff --git a/cosmos.png b/app/cosmos.png similarity index 100% rename from cosmos.png rename to app/cosmos.png diff --git a/glyphs/digit_dot.gif b/app/glyphs/digit_dot.gif similarity index 100% rename from glyphs/digit_dot.gif rename to app/glyphs/digit_dot.gif diff --git a/glyphs/icon_app.gif b/app/glyphs/icon_app.gif similarity index 100% rename from glyphs/icon_app.gif rename to app/glyphs/icon_app.gif diff --git a/glyphs/icon_back.gif b/app/glyphs/icon_back.gif similarity index 100% rename from glyphs/icon_back.gif rename to app/glyphs/icon_back.gif diff --git a/glyphs/icon_close.gif b/app/glyphs/icon_close.gif similarity index 100% rename from glyphs/icon_close.gif rename to app/glyphs/icon_close.gif diff --git a/glyphs/icon_crossmark.gif b/app/glyphs/icon_crossmark.gif similarity index 100% rename from glyphs/icon_crossmark.gif rename to app/glyphs/icon_crossmark.gif diff --git a/glyphs/icon_dashboard.gif b/app/glyphs/icon_dashboard.gif similarity index 100% rename from glyphs/icon_dashboard.gif rename to app/glyphs/icon_dashboard.gif diff --git a/glyphs/icon_eye.gif b/app/glyphs/icon_eye.gif similarity index 100% rename from glyphs/icon_eye.gif rename to app/glyphs/icon_eye.gif diff --git a/glyphs/icon_validate.gif b/app/glyphs/icon_validate.gif similarity index 100% rename from glyphs/icon_validate.gif rename to app/glyphs/icon_validate.gif diff --git a/glyphs/icon_validate_14.gif b/app/glyphs/icon_validate_14.gif similarity index 100% rename from glyphs/icon_validate_14.gif rename to app/glyphs/icon_validate_14.gif diff --git a/nanos_icon.gif b/app/nanos_icon.gif similarity index 100% rename from nanos_icon.gif rename to app/nanos_icon.gif diff --git a/nanox_icon.gif b/app/nanox_icon.gif similarity index 100% rename from nanox_icon.gif rename to app/nanox_icon.gif diff --git a/pkg/.gitkeep b/app/pkg/.gitkeep similarity index 100% rename from pkg/.gitkeep rename to app/pkg/.gitkeep diff --git a/script.ld b/app/script.ld similarity index 100% rename from script.ld rename to app/script.ld diff --git a/src/.gitignore b/app/src/.gitignore similarity index 96% rename from src/.gitignore rename to app/src/.gitignore index 3faea27d..4bde63b2 100644 --- a/src/.gitignore +++ b/app/src/.gitignore @@ -44,3 +44,5 @@ src/glyphs\.c obj/ bin/ debug/ + +pkg/zxtool.sh diff --git a/src/lib/parser_txdef.h b/app/src/coin.h similarity index 66% rename from src/lib/parser_txdef.h rename to app/src/coin.h index fed623c3..f33d058b 100644 --- a/src/lib/parser_txdef.h +++ b/app/src/coin.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,27 +22,21 @@ extern "C" { #include #include -typedef struct { - int16_t item_index; - int16_t chunk_index; - uint16_t item_index_current; - uint16_t item_index_root; - uint8_t max_level; - uint8_t max_depth; +#define HDPATH_0_DEFAULT (0x80000000 | 0x2c) +#define HDPATH_1_DEFAULT (0x80000000 | 0x76) +#define HDPATH_2_DEFAULT (0x80000000 | 0) +#define HDPATH_3_DEFAULT (0) +#define HDPATH_4_DEFAULT (0) - char *out_key; - int16_t out_key_len; +#define MENU_MAIN_APP_LINE1 "Cosmos" - char *out_val; - int16_t out_val_len; -} tx_query_t; +#ifdef TESTING_ENABLED +#define MENU_MAIN_APP_LINE2 "Cosmos TEST!" +#else +#define MENU_MAIN_APP_LINE2 "App" +#endif -typedef struct { - parsed_json_t json; - const char *tx; - uint8_t cache_valid; - tx_query_t query; -} parser_tx_t; +#define VIEW_ADDRESS_BUFFER_OFFSET (PK_LEN) #ifdef __cplusplus } diff --git a/src/actions.c b/app/src/common/actions.c similarity index 97% rename from src/actions.c rename to app/src/common/actions.c index f8f238d4..0a19eeba 100644 --- a/src/actions.c +++ b/app/src/common/actions.c @@ -1,6 +1,6 @@ /******************************************************************************* * (c) 2016 Ledger -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ ********************************************************************************/ #include "actions.h" -#include "lib/crypto.h" +#include "../crypto.h" #include "tx.h" #include "apdu_codes.h" #include diff --git a/src/actions.h b/app/src/common/actions.h similarity index 95% rename from src/actions.h rename to app/src/common/actions.h index 1ec06a38..70522459 100644 --- a/src/actions.h +++ b/app/src/common/actions.h @@ -1,6 +1,5 @@ /******************************************************************************* -* (c) 2016 Ledger -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/app_main.c b/app/src/common/app_main.c similarity index 93% rename from src/app_main.c rename to app/src/common/app_main.c index 5862c939..109be673 100644 --- a/src/app_main.c +++ b/app/src/common/app_main.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018, 2019 ZondaX GmbH +* (c) 2018, 2019 Zondax GmbH * (c) 2016 Ledger * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,7 @@ #include "view.h" #include "actions.h" #include "tx.h" -#include "lib/crypto.h" +#include "../crypto.h" #include "coin.h" #include "zxmacros.h" @@ -47,9 +47,9 @@ unsigned char io_event(unsigned char channel) { case SEPROXYHAL_TAG_TICKER_EVENT: { // UX_TICKER_EVENT(G_io_seproxyhal_spi_buffer, { - if (UX_ALLOWED) { - UX_REDISPLAY(); - } + if (UX_ALLOWED) { + UX_REDISPLAY(); + } }); break; } @@ -90,17 +90,17 @@ unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) { return 0; } -void extractBip44(uint32_t rx, uint32_t offset) { - if ((rx - offset) < sizeof(uint32_t) * BIP44_LEN_DEFAULT) { +void extractHDPath(uint32_t rx, uint32_t offset) { + if ((rx - offset) < sizeof(uint32_t) * HDPATH_LEN_DEFAULT) { THROW(APDU_CODE_WRONG_LENGTH); } - MEMCPY(bip44Path, G_io_apdu_buffer + offset, sizeof(uint32_t) * BIP44_LEN_DEFAULT); + MEMCPY(hdPath, G_io_apdu_buffer + offset, sizeof(uint32_t) * HDPATH_LEN_DEFAULT); // Check values - if (bip44Path[0] != BIP44_0_DEFAULT || - bip44Path[1] != BIP44_1_DEFAULT || - bip44Path[3] != BIP44_3_DEFAULT) { + if (hdPath[0] != HDPATH_0_DEFAULT || + hdPath[1] != HDPATH_1_DEFAULT || + hdPath[3] != HDPATH_3_DEFAULT) { THROW(APDU_CODE_DATA_INVALID); } } @@ -108,7 +108,7 @@ void extractBip44(uint32_t rx, uint32_t offset) { bool process_chunk(volatile uint32_t *tx, uint32_t rx) { const uint8_t payloadType = G_io_apdu_buffer[OFFSET_PAYLOAD_TYPE]; - if (G_io_apdu_buffer[OFFSET_P2] != 0){ + if (G_io_apdu_buffer[OFFSET_P2] != 0) { THROW(APDU_CODE_INVALIDP1P2); } @@ -117,11 +117,11 @@ bool process_chunk(volatile uint32_t *tx, uint32_t rx) { } uint32_t added; - switch(payloadType) { + switch (payloadType) { case 0: tx_initialize(); tx_reset(); - extractBip44(rx, OFFSET_DATA); + extractHDPath(rx, OFFSET_DATA); return false; case 1: added = tx_append(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA); @@ -179,7 +179,7 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { case INS_GET_ADDR_SECP256K1: { uint8_t len = extractHRP(rx, OFFSET_DATA); - extractBip44(rx, OFFSET_DATA + 1 + len); + extractHDPath(rx, OFFSET_DATA + 1 + len); uint8_t requireConfirmation = G_io_apdu_buffer[OFFSET_P1]; diff --git a/src/app_main.h b/app/src/common/app_main.h similarity index 98% rename from src/app_main.h rename to app/src/common/app_main.h index 724e97d1..c4facba9 100644 --- a/src/app_main.h +++ b/app/src/common/app_main.h @@ -1,6 +1,6 @@ /******************************************************************************* * (c) 2016 Ledger -* (c) 2018 ZondaX GmbH +* (c) 2018 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main.c b/app/src/common/main.c similarity index 97% rename from src/main.c rename to app/src/common/main.c index 834bfc7a..87150f07 100644 --- a/src/main.c +++ b/app/src/common/main.c @@ -1,6 +1,6 @@ /******************************************************************************* * (c) 2016 Ledger -* (c) 2018, 2019 ZondaX GmbH +* (c) 2018, 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/lib/parser.h b/app/src/common/parser.h similarity index 79% rename from src/lib/parser.h rename to app/src/common/parser.h index 33418362..8f5bbe04 100644 --- a/src/lib/parser.h +++ b/app/src/common/parser.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,23 +21,25 @@ extern "C" { #endif #include "parser_impl.h" +#include "hexutils.h" +#include "crypto.h" const char *parser_getErrorDescription(parser_error_t err); //// parses a tx buffer parser_error_t parser_parse(parser_context_t *ctx, const uint8_t *data, - uint16_t dataLen); + size_t dataLen); //// verifies tx fields -parser_error_t parser_validate(parser_context_t *ctx); +parser_error_t parser_validate(const parser_context_t *ctx); //// returns the number of items in the current parsing context -uint8_t parser_getNumItems(parser_context_t *ctx); +uint8_t parser_getNumItems(const parser_context_t *ctx); // retrieves a readable output for each field / page -parser_error_t parser_getItem(parser_context_t *ctx, - int8_t displayIdx, +parser_error_t parser_getItem(const parser_context_t *ctx, + uint16_t displayIdx, char *outKey, uint16_t outKeyLen, char *outValue, uint16_t outValueLen, uint8_t pageIdx, uint8_t *pageCount); diff --git a/src/lib/parser_common.h b/app/src/common/parser_common.h similarity index 80% rename from src/lib/parser_common.h rename to app/src/common/parser_common.h index 18722e64..958d480c 100644 --- a/src/lib/parser_common.h +++ b/app/src/common/parser_common.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,22 +22,34 @@ extern "C" { #include #include +#define CHECK_PARSER_ERR(__CALL) { \ + parser_error_t __err = __CALL; \ + if (__err!=parser_ok) return __err;} + typedef enum { + // Generic errors parser_ok = 0, parser_no_data, parser_init_context_empty, + parser_display_idx_out_of_range, + parser_display_page_out_of_range, + parser_unexepected_error, + // Coin generic + parser_unexpected_type, + parser_unexpected_method, parser_unexpected_buffer_end, + parser_unexpected_value, + parser_unexpected_number_items, parser_unexpected_version, parser_unexpected_characters, parser_unexpected_field, parser_duplicated_field, parser_value_out_of_range, + parser_invalid_address, parser_unexpected_chain, + parser_missing_field, parser_query_no_results, - ///// - parser_display_idx_out_of_range, - parser_display_page_out_of_range, - //// + // Coin Specific parser_json_zero_tokens, parser_json_too_many_tokens, // "NOMEM: JSON string contains too many tokens" parser_json_incomplete_json, // "JSON string is not complete"; @@ -49,6 +61,7 @@ typedef enum { parser_json_missing_msgs, parser_json_missing_account_number, parser_json_missing_memo, + parser_json_unexpected_error, } parser_error_t; typedef struct { diff --git a/src/tx.c b/app/src/common/tx.c similarity index 98% rename from src/tx.c rename to app/src/common/tx.c index fc8ea66d..9a49de8d 100644 --- a/src/tx.c +++ b/app/src/common/tx.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ #include "tx.h" #include "apdu_codes.h" #include "buffering.h" -#include "lib/parser.h" +#include "parser.h" #include #include "zxmacros.h" diff --git a/src/tx.h b/app/src/common/tx.h similarity index 98% rename from src/tx.h rename to app/src/common/tx.h index e59b247c..062842ba 100644 --- a/src/tx.h +++ b/app/src/common/tx.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/lib/crypto.c b/app/src/crypto.c similarity index 77% rename from src/lib/crypto.c rename to app/src/crypto.c index bedc13fd..9a08c165 100644 --- a/src/lib/crypto.c +++ b/app/src/crypto.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,40 +16,38 @@ #include "crypto.h" #include "coin.h" +#include "zxmacros.h" +#include "apdu_codes.h" #include -#include "apdu_codes.h" -#include "zxmacros.h" -uint32_t bip44Path[BIP44_LEN_DEFAULT]; +uint32_t hdPath[HDPATH_LEN_DEFAULT]; + uint8_t bech32_hrp_len; char bech32_hrp[MAX_BECH32_HRP_LEN + 1]; -#if defined(TARGET_NANOS) -#define SAFE_HEARTBEAT(X) io_seproxyhal_io_heartbeat(); X; io_seproxyhal_io_heartbeat(); -#endif - -#if defined(TARGET_NANOX) -#define SAFE_HEARTBEAT(X) X; -#endif - #if defined(TARGET_NANOS) || defined(TARGET_NANOX) #include "cx.h" -void crypto_extractPublicKey(uint32_t bip44Path[BIP44_LEN_DEFAULT], uint8_t *pubKey){ +void crypto_extractPublicKey(const uint32_t path[HDPATH_LEN_DEFAULT], uint8_t *pubKey, uint16_t pubKeyLen) { cx_ecfp_public_key_t cx_publicKey; cx_ecfp_private_key_t cx_privateKey; uint8_t privateKeyData[32]; + if (pubKeyLen < PK_LEN) { + return; + } + BEGIN_TRY { TRY { + // Generate keys SAFE_HEARTBEAT(os_perso_derive_node_bip32(CX_CURVE_256K1, - bip44Path, - BIP44_LEN_DEFAULT, - privateKeyData, NULL)); + path, + HDPATH_LEN_DEFAULT, + privateKeyData, + NULL)); - ////////////////////// SAFE_HEARTBEAT(cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &cx_privateKey)); SAFE_HEARTBEAT(cx_ecfp_init_public_key(CX_CURVE_256K1, NULL, 0, &cx_publicKey)); SAFE_HEARTBEAT(cx_ecfp_generate_pair(CX_CURVE_256K1, &cx_publicKey, &cx_privateKey, 1)); @@ -70,35 +68,41 @@ void crypto_extractPublicKey(uint32_t bip44Path[BIP44_LEN_DEFAULT], uint8_t *pub pubKey[31] |= 0x80; } ////////////////////// - memcpy(pubKey, cx_publicKey.W, PK_LEN); + MEMCPY(pubKey, cx_publicKey.W, PK_LEN); } -uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t *message, uint16_t messageLen) { - uint8_t message_digest[CX_SHA256_SIZE]; - SAFE_HEARTBEAT(cx_hash_sha256(message, messageLen, message_digest, CX_SHA256_SIZE)); +uint16_t crypto_sign(uint8_t *signature, + uint16_t signatureMaxlen, + const uint8_t *message, + uint16_t messageLen) { + uint8_t messageDigest[CX_SHA256_SIZE]; + + // Hash it + SAFE_HEARTBEAT(cx_hash_sha256(message, messageLen, messageDigest, CX_SHA256_SIZE)); cx_ecfp_private_key_t cx_privateKey; uint8_t privateKeyData[32]; int signatureLength; + unsigned int info = 0; + BEGIN_TRY { TRY { // Generate keys SAFE_HEARTBEAT(os_perso_derive_node_bip32(CX_CURVE_256K1, - bip44Path, - BIP44_LEN_DEFAULT, + hdPath, + HDPATH_LEN_DEFAULT, privateKeyData, NULL)); SAFE_HEARTBEAT(cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &cx_privateKey)); // Sign - unsigned int info = 0; SAFE_HEARTBEAT( signatureLength = cx_ecdsa_sign(&cx_privateKey, CX_RND_RFC6979 | CX_LAST, CX_SHA256, - message_digest, + messageDigest, CX_SHA256_SIZE, signature, signatureMaxlen, @@ -113,11 +117,16 @@ uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t return signatureLength; } + #else -void crypto_extractPublicKey(uint32_t path[BIP32_LEN_DEFAULT], uint8_t *pubKey) { +void crypto_extractPublicKey(const uint32_t path[HDPATH_LEN_DEFAULT], uint8_t *pubKey, uint16_t pubKeyLen) { + /////////////////////////////////////// + // THIS IS ONLY USED FOR TEST PURPOSES + /////////////////////////////////////// + // Empty version for non-Ledger devices - MEMZERO(pubKey, 32); + MEMZERO(pubKey, pubKeyLen); } uint16_t crypto_sign(uint8_t *signature, @@ -167,7 +176,7 @@ uint16_t crypto_fillAddress(uint8_t *buffer, uint16_t buffer_len) { } // extract pubkey - crypto_extractPublicKey(bip44Path, buffer); + crypto_extractPublicKey(hdPath, buffer, buffer_len); // Hash it uint8_t hashed1_pk[CX_SHA256_SIZE]; diff --git a/src/lib/crypto.h b/app/src/crypto.h similarity index 71% rename from src/lib/crypto.h rename to app/src/crypto.h index 4b31f9ec..acbf9666 100644 --- a/src/lib/crypto.h +++ b/app/src/crypto.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,20 +23,23 @@ extern "C" { #endif -#define BIP44_LEN_DEFAULT 5u +#define HDPATH_LEN_DEFAULT 5u #define MAX_BECH32_HRP_LEN 83u -#define PK_LEN 33u +#define PK_LEN 33u -extern uint32_t bip44Path[BIP44_LEN_DEFAULT]; +extern uint32_t hdPath[HDPATH_LEN_DEFAULT]; extern char *hrp; uint8_t extractHRP(uint32_t rx, uint32_t offset); void crypto_set_hrp(char *p); -uint16_t crypto_fillAddress(uint8_t *buffer, uint16_t buffer_len); +uint16_t crypto_fillAddress(uint8_t *buffer, uint16_t bufferLen); -uint16_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t *message, uint16_t messageLen); +uint16_t crypto_sign(uint8_t *signature, + uint16_t signatureMaxlen, + const uint8_t *message, + uint16_t messageLen); #ifdef __cplusplus } diff --git a/src/lib/json/json_parser.c b/app/src/json/json_parser.c similarity index 91% rename from src/lib/json/json_parser.c rename to app/src/json/json_parser.c index cd81202d..526d1521 100644 --- a/src/lib/json/json_parser.c +++ b/app/src/json/json_parser.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018, 2019 ZondaX GmbH +* (c) 2018, 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ #include #include -#include +#include #include "json_parser.h" #define EQUALS(_P, _Q, _LEN) (MEMCMP( PIC(_P), PIC(_Q), (_LEN))==0) @@ -29,20 +29,24 @@ parser_error_t json_parse(parsed_json_t *parsed_json, const char *buffer, uint16 parsed_json->buffer = buffer; parsed_json->bufferLen = bufferLen; - int num_tokens = jsmn_parse( - &parser, - parsed_json->buffer, - parsed_json->bufferLen, - parsed_json->tokens, - MAX_NUMBER_OF_TOKENS); - - switch (num_tokens) { - case JSMN_ERROR_NOMEM: - return parser_json_too_many_tokens; - case JSMN_ERROR_INVAL: - return parser_unexpected_characters; - case JSMN_ERROR_PART: - return parser_json_incomplete_json; + int32_t num_tokens = jsmn_parse( + &parser, + parsed_json->buffer, + parsed_json->bufferLen, + parsed_json->tokens, + MAX_NUMBER_OF_TOKENS); + + if (num_tokens < 0) { + switch (num_tokens) { + case JSMN_ERROR_NOMEM: + return parser_json_too_many_tokens; + case JSMN_ERROR_INVAL: + return parser_unexpected_characters; + case JSMN_ERROR_PART: + return parser_json_incomplete_json; + default: + return parser_json_unexpected_error; + } } parsed_json->numberOfTokens = 0; diff --git a/src/lib/json/json_parser.h b/app/src/json/json_parser.h similarity index 97% rename from src/lib/json/json_parser.h rename to app/src/json/json_parser.h index 3d5da6f2..19b4df7f 100644 --- a/src/lib/json/json_parser.h +++ b/app/src/json/json_parser.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018, 2019 ZondaX GmbH +* (c) 2018, 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ #include #include #include -#include "parser_common.h" +#include "common/parser_common.h" #ifdef __cplusplus extern "C" { @@ -47,8 +47,8 @@ extern "C" { // - parsed json tokens // - re-created SendMsg struct with indices pointing to tokens in parsed json typedef struct { - int8_t isValid; - uint16_t numberOfTokens; + uint8_t isValid; + int32_t numberOfTokens; jsmntok_t tokens[MAX_NUMBER_OF_TOKENS]; const char *buffer; uint16_t bufferLen; diff --git a/src/lib/parser.c b/app/src/parser.c similarity index 61% rename from src/lib/parser.c rename to app/src/parser.c index b2c98a85..39378310 100644 --- a/src/lib/parser.c +++ b/app/src/parser.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,29 +16,30 @@ #include #include -#include +#include #include -#include "json/tx_parser.h" -#include "json/tx_display.h" -#include "lib/parser_impl.h" -#include "view_internal.h" -#include "jsmn.h" -#include "parser.h" +#include "tx_parser.h" +#include "tx_display.h" +#include "parser_impl.h" +#include "common/parser.h" + +__Z_INLINE parser_error_t parser_getItem_raw(const parser_context_t *ctx, + int8_t displayIdx, + char *outKey, uint16_t outKeyLen, + char *outVal, uint16_t outValLen, + uint8_t pageIdx, uint8_t *pageCount); parser_error_t parser_parse(parser_context_t *ctx, const uint8_t *data, - uint16_t dataLen) { - parser_init(ctx, data, dataLen); - return _readTx(ctx, &parser_tx_obj); + size_t dataLen) { + CHECK_PARSER_ERR(tx_display_readTx(ctx, data, dataLen)) + return parser_ok; } -parser_error_t parser_validate(parser_context_t *ctx) { - parser_error_t err = tx_validate(&parser_tx_obj.json); - if (err != parser_ok) - return err; +parser_error_t parser_validate(const parser_context_t *ctx) { + CHECK_PARSER_ERR(tx_validate(&parser_tx_obj.json)) // Iterate through all items to check that all can be shown and are valid - uint8_t numItems = parser_getNumItems(ctx); char tmpKey[40]; @@ -46,65 +47,63 @@ parser_error_t parser_validate(parser_context_t *ctx) { for (uint8_t idx = 0; idx < numItems; idx++) { uint8_t pageCount; - err = parser_getItem(ctx, idx, tmpKey, sizeof(tmpKey), tmpVal, sizeof(tmpVal), 0, &pageCount); - if (err != parser_ok) { - return err; - } + CHECK_PARSER_ERR(parser_getItem(ctx, idx, tmpKey, sizeof(tmpKey), tmpVal, sizeof(tmpVal), 0, &pageCount)) } return parser_ok; } -uint8_t parser_getNumItems(parser_context_t *ctx) { +uint8_t parser_getNumItems(const parser_context_t *ctx) { return tx_display_numItems(); } __Z_INLINE bool_t parser_areEqual(uint16_t tokenidx, char *expected) { if (parser_tx_obj.json.tokens[tokenidx].type != JSMN_STRING) { - return false; + return bool_false; } - uint16_t len = parser_tx_obj.json.tokens[tokenidx].end - parser_tx_obj.json.tokens[tokenidx].start; + int16_t len = parser_tx_obj.json.tokens[tokenidx].end - parser_tx_obj.json.tokens[tokenidx].start; if (strlen(expected) != len) { - return false; + return bool_false; } const char *p = parser_tx_obj.tx + parser_tx_obj.json.tokens[tokenidx].start; for (uint16_t i = 0; i < len; i++) { if (expected[i] != *(p + i)) { - return false; + return bool_false; } } - return true; + return bool_true; } __Z_INLINE bool_t parser_isAmount(char *key) { if (strcmp(parser_tx_obj.query.out_key, "fee/amount") == 0) - return true; + return bool_true; if (strcmp(parser_tx_obj.query.out_key, "msgs/inputs/coins") == 0) - return true; + return bool_true; if (strcmp(parser_tx_obj.query.out_key, "msgs/outputs/coins") == 0) - return true; + return bool_true; if (strcmp(parser_tx_obj.query.out_key, "msgs/value/amount") == 0) - return true; + return bool_true; - return false; + return bool_false; } __Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, char *outVal, uint16_t outValLen, uint8_t pageIdx, uint8_t *pageCount) { - uint16_t numElements = array_get_element_count(amountToken, &parser_tx_obj.json); + *pageCount = 0; if (parser_tx_obj.json.tokens[amountToken].type == JSMN_ARRAY) { amountToken++; } - numElements = array_get_element_count(amountToken, &parser_tx_obj.json); + uint16_t numElements = array_get_element_count(amountToken, &parser_tx_obj.json); if (numElements == 0) { + *pageCount = 1; snprintf(outVal, outValLen, "Empty"); return parser_ok; } @@ -115,10 +114,10 @@ __Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, if (parser_tx_obj.json.tokens[amountToken].type != JSMN_OBJECT) return parser_unexpected_field; - if (!parser_areEqual(amountToken + 1, "amount")) + if (!parser_areEqual(amountToken + 1u, "amount")) return parser_unexpected_field; - if (!parser_areEqual(amountToken + 3, "denom")) + if (!parser_areEqual(amountToken + 3u, "denom")) return parser_unexpected_field; char bufferUI[160]; @@ -126,11 +125,11 @@ __Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, MEMZERO(bufferUI, sizeof(bufferUI)); const char *amountPtr = parser_tx_obj.tx + parser_tx_obj.json.tokens[amountToken + 2].start; - const uint16_t amountLen = parser_tx_obj.json.tokens[amountToken + 2].end - - parser_tx_obj.json.tokens[amountToken + 2].start; + const int16_t amountLen = parser_tx_obj.json.tokens[amountToken + 2].end - + parser_tx_obj.json.tokens[amountToken + 2].start; const char *denomPtr = parser_tx_obj.tx + parser_tx_obj.json.tokens[amountToken + 4].start; - const uint16_t denomLen = parser_tx_obj.json.tokens[amountToken + 4].end - - parser_tx_obj.json.tokens[amountToken + 4].start; + const int16_t denomLen = parser_tx_obj.json.tokens[amountToken + 4].end - + parser_tx_obj.json.tokens[amountToken + 4].start; if (sizeof(bufferUI) < amountLen + denomLen + 2) { return parser_unexpected_buffer_end; @@ -153,46 +152,44 @@ __Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, return parser_ok; } -parser_error_t parser_getItem(parser_context_t *ctx, - int8_t displayIdx, +parser_error_t parser_getItem(const parser_context_t *ctx, + uint16_t displayIdx, char *outKey, uint16_t outKeyLen, char *outVal, uint16_t outValLen, uint8_t pageIdx, uint8_t *pageCount) { + *pageCount = 0; - MEMZERO(outKey, outKeyLen); - MEMZERO(outVal, outValLen); - INIT_QUERY(outKey, outKeyLen, outVal, outValLen, pageIdx) - snprintf(outKey, outKeyLen, "?"); - snprintf(outVal, outValLen, " "); - - uint16_t displayStartToken; - parser_error_t err = tx_display_set_query(displayIdx, &displayStartToken); - if (err != parser_ok) - return err; + if (parser_getNumItems(ctx) == 0) { + return parser_unexpected_number_items; + } - STRNCPY_S(parser_tx_obj.query.out_key, - get_required_root_item(parser_tx_obj.query.item_index_root), - parser_tx_obj.query.out_key_len) + if (displayIdx < 0 || displayIdx >= parser_getNumItems(ctx)) { + return parser_display_idx_out_of_range; + } uint16_t ret_value_token_index; - err = tx_traverse_find(displayStartToken, &ret_value_token_index); - if (err != parser_ok) - return err; + tx_display_query(displayIdx, outKey, outKeyLen, &ret_value_token_index); - if (parser_isAmount(parser_tx_obj.query.out_key)) { - err = parser_formatAmount(ret_value_token_index, outVal, outValLen, pageIdx, pageCount); + if (parser_isAmount(outKey)) { + CHECK_PARSER_ERR(parser_formatAmount( + ret_value_token_index, + outVal, outValLen, + pageIdx, pageCount)) } else { - err = tx_getToken(ret_value_token_index, outVal, outValLen, parser_tx_obj.query.chunk_index, pageCount); + CHECK_PARSER_ERR(tx_getToken( + ret_value_token_index, + outVal, outValLen, + pageIdx, pageCount)) } tx_display_make_friendly(); if (*pageCount > 1) { - uint8_t keyLen = strlen(outKey); + size_t keyLen = strlen(outKey); if (keyLen < outKeyLen) { snprintf(outKey + keyLen, outKeyLen - keyLen, " [%d/%d]", pageIdx + 1, *pageCount); } } - return err; + return parser_ok; } diff --git a/src/lib/parser_impl.c b/app/src/parser_impl.c similarity index 90% rename from src/lib/parser_impl.c rename to app/src/parser_impl.c index 95e0ae82..7ac3d36b 100644 --- a/src/lib/parser_impl.c +++ b/app/src/parser_impl.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ ********************************************************************************/ #include "parser_impl.h" -#include "json/tx_display.h" parser_tx_t parser_tx_obj; @@ -37,7 +36,7 @@ parser_error_t parser_init_context(parser_context_t *ctx, return parser_ok; } -parser_error_t parser_init(parser_context_t *ctx, const uint8_t *buffer, uint16_t bufferSize) { +parser_error_t parser_init(parser_context_t *ctx, const uint8_t *buffer, size_t bufferSize) { parser_error_t err = parser_init_context(ctx, buffer, bufferSize); if (err != parser_ok) return err; @@ -69,6 +68,8 @@ const char *parser_getErrorDescription(parser_error_t err) { return "Unexpected chain"; case parser_query_no_results: return "item query returned no results"; + case parser_missing_field: + return "missing field"; ////// case parser_display_idx_out_of_range: return "display index out of range"; @@ -97,6 +98,8 @@ const char *parser_getErrorDescription(parser_error_t err) { return "JSON Missing account number"; case parser_json_missing_memo: return "JSON Missing memo"; + case parser_json_unexpected_error: + return "JSON Unexpected error"; default: return "Unrecognized error code"; @@ -105,13 +108,15 @@ const char *parser_getErrorDescription(parser_error_t err) { parser_error_t _readTx(parser_context_t *c, parser_tx_t *v) { parser_error_t err = json_parse(&parser_tx_obj.json, - (const char *) c->buffer, c->bufferLen); + (const char *) c->buffer, + c->bufferLen); if (err != parser_ok) { return err; } parser_tx_obj.tx = (const char *) c->buffer; - parser_tx_obj.cache_valid = 0; + parser_tx_obj.flags.cache_valid = 0; + parser_tx_obj.filter_msg_type_count = 0; return parser_ok; } diff --git a/src/lib/parser_impl.h b/app/src/parser_impl.h similarity index 85% rename from src/lib/parser_impl.h rename to app/src/parser_impl.h index 29cee8e5..43a9d675 100644 --- a/src/lib/parser_impl.h +++ b/app/src/parser_impl.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ ********************************************************************************/ #pragma once -#include "parser_common.h" +#include "common/parser_common.h" #include "json/json_parser.h" #include "parser_txdef.h" @@ -23,11 +23,16 @@ extern "C" { #endif +typedef struct { + char str1[50]; + char str2[50]; +} key_subst_t; + extern parser_tx_t parser_tx_obj; parser_error_t parser_init(parser_context_t *ctx, const uint8_t *buffer, - uint16_t bufferSize); + size_t bufferSize); parser_error_t _readTx(parser_context_t *c, parser_tx_t *v); diff --git a/app/src/parser_txdef.h b/app/src/parser_txdef.h new file mode 100644 index 00000000..150f9197 --- /dev/null +++ b/app/src/parser_txdef.h @@ -0,0 +1,73 @@ +/******************************************************************************* +* (c) 2019 Zondax GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include + +typedef struct { + // These are internal values used for tracking the state of the query/search + uint16_t _item_index_current; + + // maximum json tree level. Beyond this tree depth, key/values are flattened + uint8_t max_level; + + // maximum tree traversal depth. This limits possible stack overflow issues + uint8_t max_depth; + + // Index of the item to retrieve + int16_t item_index; + // Chunk of the item to retrieve (assuming partitioning based on out_val_len chunks) + int16_t page_index; + + // These fields (out_*) are where query results are placed + char *out_key; + uint16_t out_key_len; + char *out_val; + int16_t out_val_len; +} tx_query_t; + +typedef struct { + // Buffer to the original tx blob + const char *tx; + + // parsed data (tokens, etc.) + parsed_json_t json; + + // internal flags + struct { + unsigned int cache_valid:1; + unsigned int msg_type_grouping:1; + } flags; + + // indicates that N identical msg_type fields have been detected + + uint8_t filter_msg_type_count; + int32_t filter_msg_type_valid_idx; + + // current tx query + tx_query_t query; +} parser_tx_t; + +#ifdef __cplusplus +} +#endif diff --git a/app/src/tx_display.c b/app/src/tx_display.c new file mode 100644 index 00000000..91cad683 --- /dev/null +++ b/app/src/tx_display.c @@ -0,0 +1,297 @@ +/******************************************************************************* +* (c) 2018, 2019 Zondax GmbH +* +* 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 "tx_display.h" +#include "tx_parser.h" +#include "parser_impl.h" +#include + +#define NUM_REQUIRED_ROOT_PAGES 6 + +const char *get_required_root_item(root_item_e i) { + switch (i) { + case root_item_chain_id: + return "chain_id"; + case root_item_account_number: + return "account_number"; + case root_item_sequence: + return "sequence"; + case root_item_fee: + return "fee"; + case root_item_memo: + return "memo"; + case root_item_msgs: + return "msgs"; + default: + return "?"; + } +} + +static const uint8_t root_max_level[NUM_REQUIRED_ROOT_PAGES] = { + 2, // "chain_id", + 2, // "account_number", + 2, // "sequence", + 1, // "fee", + 2, // "memo" + 2, // "msgs" +}; + +typedef struct { + uint16_t total_item_count; + // token where the root_item starts (negative for non-existing) + int16_t root_item_start_token_idx[NUM_REQUIRED_ROOT_PAGES]; + // number of items the root_item contains + uint8_t root_item_number_subitems[NUM_REQUIRED_ROOT_PAGES]; +} display_cache_t; + +display_cache_t display_cache; + +parser_error_t tx_display_readTx(parser_context_t *ctx, const uint8_t *data, size_t dataLen) { + CHECK_PARSER_ERR(parser_init(ctx, data, dataLen)) + CHECK_PARSER_ERR(_readTx(ctx, &parser_tx_obj)) +} + +parser_error_t tx_indexRootFields() { + if (parser_tx_obj.flags.cache_valid) { + return parser_ok; + } + + // Clear cache + MEMZERO(&display_cache, sizeof(display_cache_t)); + char tmp_key[20]; + char tmp_val[40]; + char tmp_val_ref[40]; + parser_tx_obj.flags.msg_type_grouping = 1; + parser_tx_obj.filter_msg_type_count = 0; + + for (int8_t root_item_idx = 0; root_item_idx < NUM_REQUIRED_ROOT_PAGES; root_item_idx++) { + const char *req_root_item_key = get_required_root_item( (root_item_e) root_item_idx); + const int16_t req_root_item_key_token_idx = object_get_value(&parser_tx_obj.json, + ROOT_TOKEN_INDEX, + req_root_item_key); + + // Remember root item start token + display_cache.root_item_start_token_idx[root_item_idx] = req_root_item_key_token_idx; + + if (req_root_item_key_token_idx < 0) { + continue; + } + + // Now count how many items can be found in this root item + parser_error_t err = parser_ok; + int32_t current_item_idx = 0; + while (err == parser_ok) { + INIT_QUERY_CONTEXT(tmp_key, sizeof(tmp_key), + tmp_val, sizeof(tmp_val), + 0, root_max_level[root_item_idx]) + parser_tx_obj.query.item_index = current_item_idx; + strncpy_s(parser_tx_obj.query.out_key, req_root_item_key, parser_tx_obj.query.out_key_len); + + uint16_t ret_value_token_index; + + err = tx_traverse_find(display_cache.root_item_start_token_idx[root_item_idx], + &ret_value_token_index); + + if (err != parser_ok) { + continue; + } + + uint8_t pageCount; + CHECK_PARSER_ERR(tx_getToken(ret_value_token_index, + parser_tx_obj.query.out_val, + parser_tx_obj.query.out_key_len, + 0, &pageCount)) + + if (root_item_idx == root_item_memo) { + if (strlen(parser_tx_obj.query.out_val) == 0) { + err = parser_query_no_results; + continue; + } + } + + if (root_item_idx == root_item_msgs && parser_tx_obj.flags.msg_type_grouping == 1u) { + if (strcmp(tmp_key, "msgs/type") == 0) { + if (parser_tx_obj.filter_msg_type_count == 0) { + // First message, initialize expected type + strcpy(tmp_val_ref, tmp_val); + parser_tx_obj.filter_msg_type_valid_idx = current_item_idx; + } + + if (strcmp(tmp_val_ref, tmp_val) != 0) { + // different values, so disable grouping + parser_tx_obj.flags.msg_type_grouping = 0; + parser_tx_obj.filter_msg_type_count = 0; + } + + parser_tx_obj.filter_msg_type_count++; + } + } + + display_cache.root_item_number_subitems[root_item_idx]++; + current_item_idx++; + } + + if (err != parser_query_no_results) { + return err; + } + + display_cache.total_item_count += display_cache.root_item_number_subitems[root_item_idx]; + } + + parser_tx_obj.flags.cache_valid = 1; + + return parser_ok; +} + +uint16_t tx_display_numItems() { + parser_error_t err = tx_indexRootFields(); + if (err != parser_ok) { + return 0; + } + + uint16_t count = display_cache.total_item_count; + // Remove grouped items from list + if (parser_tx_obj.flags.msg_type_grouping == 1u && parser_tx_obj.filter_msg_type_count) { + count -= parser_tx_obj.filter_msg_type_count - 1; + } + + return count; +} + +// This function assumes that the tx_ctx has been set properly +parser_error_t tx_display_query(uint16_t displayIdx, + char *outKey, uint16_t outKeyLen, + uint16_t *ret_value_token_index) { + tx_indexRootFields(); + + if (displayIdx < 0 || displayIdx >= display_cache.total_item_count) { + return parser_display_idx_out_of_range; + } + + uint16_t current_item_index = 0; + uint16_t root_index = 0; + + // Find root index | display idx -> item_index + // consume indexed subpages until we get the item index in the subpage + for (uint16_t i = 0; i < displayIdx; i++) { + current_item_index++; + if (current_item_index >= display_cache.root_item_number_subitems[root_index]) { + current_item_index = 0; + + // Advance root index and skip empty items + root_index++; + while (display_cache.root_item_number_subitems[root_index] == 0) root_index++; + } + } + + // Prepare query + char tmp_val[2]; + INIT_QUERY_CONTEXT(outKey, outKeyLen, tmp_val, sizeof(tmp_val), + 0, root_max_level[root_index]) + parser_tx_obj.query.item_index = current_item_index; + parser_tx_obj.query._item_index_current = 0; + parser_tx_obj.query.max_level = root_max_level[root_index]; + + strncpy_s(outKey, get_required_root_item( (root_item_e) root_index), outKeyLen); + CHECK_PARSER_ERR(tx_traverse_find( + display_cache.root_item_start_token_idx[root_index], + ret_value_token_index)) + + return parser_ok; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// + +static const key_subst_t key_substitutions[] = { + {"chain_id", "Chain ID"}, + {"account_number", "Account"}, + {"sequence", "Sequence"}, + {"memo", "Memo"}, + {"fee/amount", "Fee"}, + {"fee/gas", "Gas"}, + {"msgs/type", "Type"}, + + // FIXME: Are these obsolete?? multisend? + {"msgs/inputs/address", "Source Address"}, + {"msgs/inputs/coins", "Source Coins"}, + {"msgs/outputs/address", "Dest Address"}, + {"msgs/outputs/coins", "Dest Coins"}, + + // MsgSend + {"msgs/value/from_address", "From"}, + {"msgs/value/to_address", "To"}, + {"msgs/value/amount", "Amount"}, + + // MsgDelegate + {"msgs/value/delegator_address", "Delegator"}, + {"msgs/value/validator_address", "Validator"}, + + // MsgUndelegate +// {"msgs/value/delegator_address", "Delegator"}, +// {"msgs/value/validator_address", "Validator"}, + + // MsgBeginRedelegate +// {"msgs/value/delegator_address", "Delegator"}, + {"msgs/value/validator_src_address", "Validator Source"}, + {"msgs/value/validator_dst_address", "Validator Dest"}, + + // MsgSubmitProposal + {"msgs/value/description", "Description"}, + {"msgs/value/initial_deposit/amount", "Deposit Amount"}, + {"msgs/value/initial_deposit/denom", "Deposit Denom"}, + {"msgs/value/proposal_type", "Proposal"}, + {"msgs/value/proposer", "Proposer"}, + {"msgs/value/title", "Title"}, + + // MsgDeposit + {"msgs/value/depositer", "Sender"}, + {"msgs/value/proposal_id", "Proposal ID"}, + {"msgs/value/amount", "Amount"}, + + // MsgVote + {"msgs/value/voter", "Description"}, +// {"msgs/value/proposal_id", "Proposal ID"}, + {"msgs/value/option", "Option"}, + + // MsgWithdrawDelegationReward +// {"msgs/value/delegator_address", "Delegator"}, // duplicated +// {"msgs/value/validator_address", "Validator"}, // duplicated +}; + +void tx_display_make_friendly() { + tx_indexRootFields(); + + // post process keys + for (size_t i = 0; i < array_length(key_substitutions); i++) { + if (!strcmp(parser_tx_obj.query.out_key, key_substitutions[i].str1)) { + strncpy_s(parser_tx_obj.query.out_key, key_substitutions[i].str2, parser_tx_obj.query.out_key_len); + break; + } + } +} + diff --git a/src/lib/json/tx_display.h b/app/src/tx_display.h similarity index 58% rename from src/lib/json/tx_display.h rename to app/src/tx_display.h index 3a9055ea..ed0b0999 100644 --- a/src/lib/json/tx_display.h +++ b/app/src/tx_display.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018, 2019 ZondaX GmbH +* (c) 2018, 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,27 +15,38 @@ ********************************************************************************/ #pragma once + #include -#include +#include +#include "parser_txdef.h" #ifdef __cplusplus extern "C" { #endif -#define STRNCPY_S(DST, SRC, DST_SIZE) \ - explicit_bzero(DST, DST_SIZE); \ - strncpy(DST, SRC, DST_SIZE - 1); +typedef enum { + root_item_chain_id = 0, + root_item_account_number = 1, + root_item_sequence = 2, + root_item_fee = 3, + root_item_memo = 4, + root_item_msgs = 5, +} root_item_e; + +const char *get_required_root_item(root_item_e i); -/// This is the main function called from ledger that updates key and value strings -/// that are going to be displayed in the UI. -/// This function assumes that the tx_ctx has been properly set -parser_error_t tx_display_set_query(uint16_t displayIdx, uint16_t *outStartToken); +parser_error_t tx_display_query(uint16_t displayIdx, + char *outKey, + uint16_t outKeyLen, + uint16_t *ret_value_token_index); + +parser_error_t tx_display_readTx(parser_context_t *c, + const uint8_t *data, + size_t dataLen); /// Return number of UI pages that we'll have for the current json transaction (only if the tx is valid) /// \return number of pages (msg pages + 5 required) -int16_t tx_display_numItems(); - -const char *get_required_root_item(uint8_t i); +uint16_t tx_display_numItems(); void tx_display_make_friendly(); diff --git a/src/lib/json/tx_parser.c b/app/src/tx_parser.c similarity index 69% rename from src/lib/json/tx_parser.c rename to app/src/tx_parser.c index 964a854c..8c075f7b 100644 --- a/src/lib/json/tx_parser.c +++ b/app/src/tx_parser.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018, 2019 ZondaX GmbH +* (c) 2018, 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,18 +17,17 @@ #include #include #include "tx_parser.h" -#include "json/json_parser.h" #include "zxmacros.h" #include "parser_impl.h" // strcat but source does not need to be terminated (a chunk from a bigger string is concatenated) // dst_max is measured in bytes including the space for NULL termination // src_size does not include NULL termination -__always_inline void strcat_chunk_s(char *dst, uint16_t dst_max, const char *src_chunk, uint16_t src_chunk_size) { +__always_inline void strcat_chunk_s(char *dst, uint16_t dst_max, const char *src_chunk, size_t src_chunk_size) { *(dst + dst_max - 1) = 0; // last character terminates with zero in case we go beyond bounds - const uint16_t prev_size = strlen(dst); + const size_t prev_size = strlen(dst); - uint16_t space_left = dst_max - prev_size - 1; // -1 because requires termination + size_t space_left = dst_max - prev_size - 1; // -1 because requires termination if (src_chunk_size > space_left) { src_chunk_size = space_left; @@ -50,28 +49,50 @@ __always_inline void strcat_chunk_s(char *dst, uint16_t dst_max, const char *src /////////////////////////// /////////////////////////// +static const key_subst_t value_substitutions[] = { + {"cosmos-sdk/MsgSend", "Send"}, + {"cosmos-sdk/MsgDelegate", "Delegate"}, + {"cosmos-sdk/MsgUndelegate", "Undelegate"}, + {"cosmos-sdk/MsgBeginRedelegate", "Redelegate"}, + {"cosmos-sdk/MsgSubmitProposal", "Propose"}, + {"cosmos-sdk/MsgDeposit", "Deposit"}, + {"cosmos-sdk/MsgVote", "Vote"}, + {"cosmos-sdk/MsgWithdrawDelegationReward", "Withdraw Reward"}, +}; + parser_error_t tx_getToken(uint16_t token_index, char *out_val, uint16_t out_val_len, uint8_t pageIdx, uint8_t *pageCount) { - *pageCount = 1; + *pageCount = 0; MEMZERO(out_val, out_val_len); const int16_t token_start = parser_tx_obj.json.tokens[token_index].start; const int16_t token_end = parser_tx_obj.json.tokens[token_index].end; - if (token_start > token_end) { + if (token_start >= token_end) { return parser_unexpected_buffer_end; } - if (token_start < token_end) { - pageStringExt(out_val, out_val_len, - parser_tx_obj.tx + token_start, - token_end - token_start, - pageIdx, pageCount); + const char *inValue = parser_tx_obj.tx + token_start; + uint16_t inLen = token_end - token_start; + + for (int8_t i = 0; i < array_length(value_substitutions); i++) { + const char *substStr = value_substitutions[i].str1; + const size_t substStrLen = strlen(substStr); + if (inLen == substStrLen && !MEMCMP(inValue, substStr, substStrLen)) { + inValue = value_substitutions[i].str2; + inLen = strlen(value_substitutions[i].str2); + break; + } } - if (pageIdx >= *pageCount) + pageStringExt(out_val, out_val_len, + inValue, inLen, + pageIdx, pageCount); + + if (pageIdx >= *pageCount) { return parser_display_page_out_of_range; + } return parser_ok; } @@ -80,7 +101,9 @@ __always_inline void append_key_item(int16_t token_index) { if (*parser_tx_obj.query.out_key > 0) { // There is already something there, add separator strcat_chunk_s(parser_tx_obj.query.out_key, - parser_tx_obj.query.out_key_len, "/", 1); + parser_tx_obj.query.out_key_len, + "/", + 1); } const int16_t token_start = parser_tx_obj.json.tokens[token_index].start; @@ -89,7 +112,9 @@ __always_inline void append_key_item(int16_t token_index) { const int16_t new_item_size = token_end - token_start; strcat_chunk_s(parser_tx_obj.query.out_key, - parser_tx_obj.query.out_key_len, address_ptr, new_item_size); + parser_tx_obj.query.out_key_len, + address_ptr, + new_item_size); } /////////////////////////// /////////////////////////// @@ -102,7 +127,7 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to parser_error_t err; const jsmntype_t token_type = parser_tx_obj.json.tokens[root_token_index].type; - if (parser_tx_obj.tx == NULL) { + if (parser_tx_obj.tx == NULL || root_token_index < 0) { return parser_no_data; } @@ -110,12 +135,21 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to token_type == JSMN_STRING || token_type == JSMN_PRIMITIVE) { + const bool groupedField = strcmp("msgs/type", parser_tx_obj.query.out_key) == 0; + const bool isMainIndex = parser_tx_obj.filter_msg_type_valid_idx != parser_tx_obj.query._item_index_current; + const bool skipItem = parser_tx_obj.flags.msg_type_grouping == 1u && groupedField && isMainIndex; + // Early bail out - if (parser_tx_obj.query.item_index_current == parser_tx_obj.query.item_index) { + if (!skipItem && parser_tx_obj.query._item_index_current == parser_tx_obj.query.item_index) { *ret_value_token_index = root_token_index; return parser_ok; } - parser_tx_obj.query.item_index_current++; + + if (skipItem) { + parser_tx_obj.query.item_index++; + } + + parser_tx_obj.query._item_index_current++; return parser_query_no_results; } @@ -123,7 +157,7 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to switch (token_type) { case JSMN_OBJECT: { - const int16_t key_len = strlen(parser_tx_obj.query.out_key); + const size_t key_len = strlen(parser_tx_obj.query.out_key); for (int16_t i = 0; i < el_count; ++i) { const int16_t key_index = object_get_nth_key(root_token_index, i, &parser_tx_obj.json); const int16_t value_index = object_get_nth_value(root_token_index, i, &parser_tx_obj.json); diff --git a/src/lib/json/tx_parser.h b/app/src/tx_parser.h similarity index 67% rename from src/lib/json/tx_parser.h rename to app/src/tx_parser.h index da91cd02..9b7c4409 100644 --- a/src/lib/json/tx_parser.h +++ b/app/src/tx_parser.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018, 2019 ZondaX GmbH +* (c) 2018, 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ #include "json/json_parser.h" #include -#include +#include #ifdef __cplusplus extern "C" { @@ -26,21 +26,20 @@ extern "C" { #define MAX_RECURSION_DEPTH 6 -#define INIT_QUERY_CONTEXT(_KEY, _KEY_LEN, _VAL, _VAL_LEN, _CHUNK_IDX, _MAX_LEVEL) \ - INIT_QUERY(_KEY, _KEY_LEN, _VAL, _VAL_LEN, _CHUNK_IDX) \ - parser_tx_obj.query.item_index_current = 0; \ +#define INIT_QUERY_CONTEXT(_KEY, _KEY_LEN, _VAL, _VAL_LEN, _PAGE_IDX, _MAX_LEVEL) \ + parser_tx_obj.query._item_index_current = 0; \ parser_tx_obj.query.max_depth = MAX_RECURSION_DEPTH; \ - parser_tx_obj.query.max_level = _MAX_LEVEL; - -#define INIT_QUERY(_KEY, _KEY_LEN, _VAL, _VAL_LEN, _CHUNK_IDX) \ - _KEY[0] = 0; \ - _VAL[0] = 0; \ - parser_tx_obj.query.out_key=_KEY; \ - parser_tx_obj.query.out_val=_VAL; \ - parser_tx_obj.query.out_key_len = _KEY_LEN; \ - parser_tx_obj.query.out_val_len = _VAL_LEN; \ - parser_tx_obj.query.item_index= 0; \ - parser_tx_obj.query.chunk_index = _CHUNK_IDX; + parser_tx_obj.query.max_level = _MAX_LEVEL; \ + \ + parser_tx_obj.query.item_index= 0; \ + parser_tx_obj.query.page_index = (_PAGE_IDX); \ + \ + MEMZERO(_KEY, (_KEY_LEN)); \ + MEMZERO(_VAL, (_VAL_LEN)); \ + parser_tx_obj.query.out_key= _KEY; \ + parser_tx_obj.query.out_val= _VAL; \ + parser_tx_obj.query.out_key_len = (_KEY_LEN); \ + parser_tx_obj.query.out_val_len = (_VAL_LEN); parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_token_index); @@ -48,7 +47,6 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to parser_error_t tx_traverse(int16_t root_token_index, uint8_t *numChunks); // Retrieves the value for the corresponding token index. If the value goes beyond val_len, the chunk_idx will be used - parser_error_t tx_getToken(uint16_t token_index, char *out_val, uint16_t out_val_len, uint8_t pageIdx, uint8_t *pageCount); diff --git a/src/lib/json/tx_validate.c b/app/src/tx_validate.c similarity index 86% rename from src/lib/json/tx_validate.c rename to app/src/tx_validate.c index 86c4e520..50aadcf7 100644 --- a/src/lib/json/tx_validate.c +++ b/app/src/tx_validate.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018, 2019 ZondaX GmbH +* (c) 2018, 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ ********************************************************************************/ #include -#include +#include #include "json/json_parser.h" const char whitespaces[] = { @@ -64,7 +64,7 @@ int8_t contains_whitespace(parsed_json_t *json) { } int8_t is_sorted(int16_t first_index, - int16_t second_index, + int16_t second_index, parsed_json_t *json) { #if DEBUG_SORTING char first[256]; @@ -78,8 +78,8 @@ int8_t is_sorted(int16_t first_index, second[size] = '\0'; #endif - if (strcmp((const char *) (json->buffer+json->tokens[first_index].start), - (const char *) (json->buffer+json->tokens[second_index].start)) <= 0) { + if (strcmp((json->buffer + json->tokens[first_index].start), + (json->buffer + json->tokens[second_index].start)) <= 0) { return 1; } return 0; @@ -115,44 +115,44 @@ parser_error_t tx_validate(parsed_json_t *json) { } if (object_get_value( - json, - 0, - "chain_id") == -1) { + json, + 0, + "chain_id") == -1) { return parser_json_missing_chain_id; } if (object_get_value( - json, - 0, - "sequence") == -1) { + json, + 0, + "sequence") == -1) { return parser_json_missing_sequence; } if (object_get_value( - json, - 0, - "fee") == -1) { + json, + 0, + "fee") == -1) { return parser_json_missing_fee; } if (object_get_value( - json, - 0, - "msgs") == -1) { + json, + 0, + "msgs") == -1) { return parser_json_missing_msgs; } if (object_get_value( - json, - 0, - "account_number") == -1) { + json, + 0, + "account_number") == -1) { return parser_json_missing_account_number; } if (object_get_value( - json, - 0, - "memo") == -1) { + json, + 0, + "memo") == -1) { return parser_json_missing_memo; } diff --git a/src/lib/json/tx_validate.h b/app/src/tx_validate.h similarity index 91% rename from src/lib/json/tx_validate.h rename to app/src/tx_validate.h index 92d8b246..acd72030 100644 --- a/src/lib/json/tx_validate.h +++ b/app/src/tx_validate.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018, 2019 ZondaX GmbH +* (c) 2018, 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,9 @@ #pragma once -#include "json_parser.h" +#include "json/json_parser.h" #include -#include +#include #ifdef __cplusplus extern "C" { diff --git a/cmake/conan/CMakeLists.txt b/cmake/conan/CMakeLists.txt new file mode 100644 index 00000000..4899e70e --- /dev/null +++ b/cmake/conan/CMakeLists.txt @@ -0,0 +1,12 @@ +# Download automatically, you can also just copy the conan.cmake file + +if (NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake") + message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan") + file(DOWNLOAD "https://raw.githubusercontent.com/conan-io/cmake-conan/v0.13/conan.cmake" + "${CMAKE_BINARY_DIR}/conan.cmake") +endif () +include(${CMAKE_BINARY_DIR}/conan.cmake) + +conan_check(REQUIRED) + +conan_cmake_run(CONANFILE conanfile.txt BASIC_SETUP CMAKE_TARGETS BUILD missing) diff --git a/cmake/gtest/CMakeLists.txt b/cmake/gtest/CMakeLists.txt new file mode 100644 index 00000000..eea7d079 --- /dev/null +++ b/cmake/gtest/CMakeLists.txt @@ -0,0 +1,31 @@ +############################## +# Google Test +# Based on instructions in https://github.com/google/googletest/tree/master/googletest#incorporating-into-an-existing-cmake-project +# Download and unpack googletest at configure time +configure_file(CMakeLists.txt.gtest.in ${CMAKE_BINARY_DIR}/googletest-download/CMakeLists.txt) + +execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download) +if (result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") +endif () + +execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download) +if (result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") +endif () + +# Prevent overriding the parent project's compiler/linker settings on Windows +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + +add_subdirectory( + ${CMAKE_BINARY_DIR}/googletest-src + ${CMAKE_BINARY_DIR}/googletest-build +) + +if (CMAKE_VERSION VERSION_LESS 2.8.11) + include_directories("${gtest_SOURCE_DIR}/include") +endif () diff --git a/cmake/gtest/CMakeLists.txt.gtest.in b/cmake/gtest/CMakeLists.txt.gtest.in new file mode 100644 index 00000000..202d3c3d --- /dev/null +++ b/cmake/gtest/CMakeLists.txt.gtest.in @@ -0,0 +1,16 @@ +# Based on https://github.com/google/googletest/tree/master/googletest#incorporating-into-an-existing-cmake-project +cmake_minimum_required(VERSION 2.8.2) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src" + BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" + ) diff --git a/conanfile.txt b/conanfile.txt new file mode 100644 index 00000000..501ad7d1 --- /dev/null +++ b/conanfile.txt @@ -0,0 +1,6 @@ +[requires] +jsoncpp/1.9.0@theirix/stable +fmt/6.0.0@bincrafters/stable + +[generators] +cmake diff --git a/deps/jsmn/src/jsmn.c b/deps/jsmn/src/jsmn.c index 8579f2e6..3d227c0a 100644 --- a/deps/jsmn/src/jsmn.c +++ b/deps/jsmn/src/jsmn.c @@ -4,18 +4,18 @@ * Allocates a fresh unused token from the token pull. */ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, - jsmntok_t *tokens, size_t num_tokens) { - jsmntok_t *tok; - if (parser->toknext >= num_tokens) { - return NULL; - } - tok = &tokens[parser->toknext++]; - tok->start = tok->end = -1; - tok->size = 0; + jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; #ifdef JSMN_PARENT_LINKS - tok->parent = -1; + tok->parent = -1; #endif - return tok; + return tok; } /** @@ -23,283 +23,299 @@ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, */ static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, short int start, short int end) { - token->type = type; - token->start = start; - token->end = end; - token->size = 0; + token->type = type; + token->start = start; + token->end = end; + token->size = 0; } /** * Fills next available token with JSON primitive. */ static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, - size_t len, jsmntok_t *tokens, size_t num_tokens) { - jsmntok_t *token; - short int start; + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + short int start; - start = parser->pos; + start = parser->pos; - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - switch (js[parser->pos]) { + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { #ifndef JSMN_STRICT - /* In strict mode primitive must be followed by "," or "}" or "]" */ - case ':': + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': #endif - case '\t' : case '\r' : case '\n' : case ' ' : - case ',' : case ']' : case '}' : - goto found; - } - if (js[parser->pos] < 32 || js[parser->pos] >= 127) { - parser->pos = start; - return JSMN_ERROR_INVAL; - } - } + case '\t' : + case '\r' : + case '\n' : + case ' ' : + case ',' : + case ']' : + case '}' : + goto found; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } #ifdef JSMN_STRICT - /* In strict mode primitive must be followed by a comma/object/array */ - parser->pos = start; - return JSMN_ERROR_PART; + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; #endif -found: - if (tokens == NULL) { - parser->pos--; - return 0; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - parser->pos = start; - return JSMN_ERROR_NOMEM; - } - jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); + found: + if (tokens == NULL) { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); #ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; + token->parent = parser->toksuper; #endif - parser->pos--; - return 0; + parser->pos--; + return 0; } /** * Fills next token with JSON string. */ static int jsmn_parse_string(jsmn_parser *parser, const char *js, - size_t len, jsmntok_t *tokens, size_t num_tokens) { - jsmntok_t *token; + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; - short int start = parser->pos; + short int start = parser->pos; - parser->pos++; + parser->pos++; - /* Skip starting quote */ - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - char c = js[parser->pos]; + /* Skip starting quote */ + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; - /* Quote: end of string */ - if (c == '\"') { - if (tokens == NULL) { - return 0; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - parser->pos = start; - return JSMN_ERROR_NOMEM; - } - jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); + /* Quote: end of string */ + if (c == '\"') { + if (tokens == NULL) { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); #ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; + token->parent = parser->toksuper; #endif - return 0; - } + return 0; + } - /* Backslash: Quoted symbol expected */ - if (c == '\\' && parser->pos + 1 < len) { - short int i; - parser->pos++; - switch (js[parser->pos]) { - /* Allowed escaped symbols */ - case '\"': case '/' : case '\\' : case 'b' : - case 'f' : case 'r' : case 'n' : case 't' : - break; - /* Allows escaped symbol \uXXXX */ - case 'u': - parser->pos++; - for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { - /* If it isn't a hex character we have an error */ - if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ - (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ - (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ - parser->pos = start; - return JSMN_ERROR_INVAL; - } - parser->pos++; - } - parser->pos--; - break; - /* Unexpected symbol */ - default: - parser->pos = start; - return JSMN_ERROR_INVAL; - } - } - } - parser->pos = start; - return JSMN_ERROR_PART; + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) { + short int i; + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': + case '/' : + case '\\' : + case 'b' : + case 'f' : + case 'r' : + case 'n' : + case 't' : + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { + /* If it isn't a hex character we have an error */ + if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; } /** * Parse JSON string and fill tokens. */ int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, - jsmntok_t *tokens, unsigned int num_tokens) { + jsmntok_t *tokens, unsigned int num_tokens) { short int r; - short int i; - jsmntok_t *token; - short int count = parser->toknext; + short int i; + jsmntok_t *token; + short int count = parser->toknext; - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - char c; - jsmntype_t type; + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; - c = js[parser->pos]; - switch (c) { - case '{': case '[': - count++; - if (tokens == NULL) { - break; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) - return JSMN_ERROR_NOMEM; - if (parser->toksuper != -1) { - tokens[parser->toksuper].size++; + c = js[parser->pos]; + switch (c) { + case '{': + case '[': + count++; + if (tokens == NULL) { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + return JSMN_ERROR_NOMEM; + if (parser->toksuper != -1) { + tokens[parser->toksuper].size++; #ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; + token->parent = parser->toksuper; #endif - } - token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); - token->start = parser->pos; - parser->toksuper = parser->toknext - 1; - break; - case '}': case ']': - if (tokens == NULL) - break; - type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': + case ']': + if (tokens == NULL) + break; + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); #ifdef JSMN_PARENT_LINKS - if (parser->toknext < 1) { - return JSMN_ERROR_INVAL; - } - token = &tokens[parser->toknext - 1]; - for (;;) { - if (token->start != -1 && token->end == -1) { - if (token->type != type) { - return JSMN_ERROR_INVAL; - } - token->end = parser->pos + 1; - parser->toksuper = token->parent; - break; - } - if (token->parent == -1) { - if(token->type != type || parser->toksuper == -1) { - return JSMN_ERROR_INVAL; - } - break; - } - token = &tokens[token->parent]; - } + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + if(token->type != type || parser->toksuper == -1) { + return JSMN_ERROR_INVAL; + } + break; + } + token = &tokens[token->parent]; + } #else - for (i = parser->toknext - 1; i >= 0; i--) { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) { - if (token->type != type) { - return JSMN_ERROR_INVAL; - } - parser->toksuper = -1; - token->end = parser->pos + 1; - break; - } - } - /* Error if unmatched closing bracket */ - if (i == -1) return JSMN_ERROR_INVAL; - for (; i >= 0; i--) { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) { - parser->toksuper = i; - break; - } - } + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) return JSMN_ERROR_INVAL; + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } #endif - break; - case '\"': - r = jsmn_parse_string(parser, js, len, tokens, num_tokens); - if (r < 0) return r; - count++; - if (parser->toksuper != -1 && tokens != NULL) - tokens[parser->toksuper].size++; - break; - case '\t' : case '\r' : case '\n' : case ' ': - break; - case ':': - parser->toksuper = parser->toknext - 1; - break; - case ',': - if (tokens != NULL && parser->toksuper != -1 && - tokens[parser->toksuper].type != JSMN_ARRAY && - tokens[parser->toksuper].type != JSMN_OBJECT) { + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + case '\t' : + case '\r' : + case '\n' : + case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && parser->toksuper != -1 && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) { #ifdef JSMN_PARENT_LINKS - parser->toksuper = tokens[parser->toksuper].parent; + parser->toksuper = tokens[parser->toksuper].parent; #else - for (i = parser->toknext - 1; i >= 0; i--) { - if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { - if (tokens[i].start != -1 && tokens[i].end == -1) { - parser->toksuper = i; - break; - } - } - } + for (i = parser->toknext - 1; i >= 0; i--) { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { + if (tokens[i].start != -1 && tokens[i].end == -1) { + parser->toksuper = i; + break; + } + } + } #endif - } - break; + } + break; #ifdef JSMN_STRICT - /* In strict mode primitives are: numbers and booleans */ - case '-': case '0': case '1' : case '2': case '3' : case '4': - case '5': case '6': case '7' : case '8': case '9': - case 't': case 'f': case 'n' : - /* And they must not be keys of the object */ - if (tokens != NULL && parser->toksuper != -1) { - jsmntok_t *t = &tokens[parser->toksuper]; - if (t->type == JSMN_OBJECT || - (t->type == JSMN_STRING && t->size != 0)) { - return JSMN_ERROR_INVAL; - } - } + /* In strict mode primitives are: numbers and booleans */ + case '-': case '0': case '1' : case '2': case '3' : case '4': + case '5': case '6': case '7' : case '8': case '9': + case 't': case 'f': case 'n' : + /* And they must not be keys of the object */ + if (tokens != NULL && parser->toksuper != -1) { + jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) { + return JSMN_ERROR_INVAL; + } + } #else - /* In non-strict mode every unquoted value is a primitive */ - default: + /* In non-strict mode every unquoted value is a primitive */ + default: #endif - r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); - if (r < 0) return r; - count++; - if (parser->toksuper != -1 && tokens != NULL) - tokens[parser->toksuper].size++; - break; + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; #ifdef JSMN_STRICT - /* Unexpected char in strict mode */ - default: - return JSMN_ERROR_INVAL; + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; #endif - } - } + } + } - if (tokens != NULL) { - for (i = parser->toknext - 1; i >= 0; i--) { - /* Unmatched opened object or array */ - if (tokens[i].start != -1 && tokens[i].end == -1) { - return JSMN_ERROR_PART; - } - } - } + if (tokens != NULL) { + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + } - return count; + return count; } /** @@ -307,8 +323,8 @@ int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, * available. */ void jsmn_init(jsmn_parser *parser) { - parser->pos = 0; - parser->toknext = 0; - parser->toksuper = -1; + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; } diff --git a/deps/ledger-zxlib/CMakeLists.txt b/deps/ledger-zxlib/CMakeLists.txt index 2e140f7d..43db7a07 100644 --- a/deps/ledger-zxlib/CMakeLists.txt +++ b/deps/ledger-zxlib/CMakeLists.txt @@ -1,5 +1,5 @@ #******************************************************************************* -#* (c) 2018 ZondaX GmbH +#* (c) 2018 Zondax GmbH #* #* Licensed under the Apache License, Version 2.0 (the "License"); #* you may not use this file except in compliance with the License. diff --git a/deps/ledger-zxlib/LICENSE b/deps/ledger-zxlib/LICENSE index c8e8e95a..0fa613e8 100644 --- a/deps/ledger-zxlib/LICENSE +++ b/deps/ledger-zxlib/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2018 ZondaX GmbH + Copyright 2018-2020 Zondax GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/deps/ledger-zxlib/README.md b/deps/ledger-zxlib/README.md index 6ab06178..9dabbf58 100644 --- a/deps/ledger-zxlib/README.md +++ b/deps/ledger-zxlib/README.md @@ -1 +1,5 @@ -ledger-zxlib +# ledger-zxlib + +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![CircleCI](https://circleci.com/gh/Zondax/ledger-zxlib/tree/master.svg?style=shield)](https://circleci.com/gh/Zondax/ledger-zxlib/tree/master) +[![CodeFactor](https://www.codefactor.io/repository/github/zondax/ledger-zxlib/badge)](https://www.codefactor.io/repository/github/zondax/ledger-zxlib) diff --git a/src/view.c b/deps/ledger-zxlib/app/common/view.c similarity index 87% rename from src/view.c rename to deps/ledger-zxlib/app/common/view.c index 19f01941..bf012980 100644 --- a/src/view.c +++ b/deps/ledger-zxlib/app/common/view.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018, 2019 ZondaX GmbH +* (c) 2018, 2019 Zondax GmbH * (c) 2016 Ledger * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,6 +16,7 @@ ********************************************************************************/ #include "view.h" +#include "coin.h" #include "view_internal.h" #include "crypto.h" @@ -54,8 +55,13 @@ void h_sign_accept(unsigned int _) { view_idle_show(0); UX_WAIT(); - set_code(G_io_apdu_buffer, replyLen, APDU_CODE_OK); - io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, replyLen + 2); + if (replyLen > 0) { + set_code(G_io_apdu_buffer, replyLen, APDU_CODE_OK); + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, replyLen + 2); + } else { + set_code(G_io_apdu_buffer, 0, APDU_CODE_SIGN_VERIFY_ERROR); + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); + } } void h_sign_reject(unsigned int _) { @@ -119,10 +125,10 @@ view_error_t h_addr_update_item(uint8_t idx) { MEMZERO(viewdata.addr, MAX_CHARS_ADDR); switch (idx) { case 0: - snprintf(viewdata.addr, MAX_CHARS_ADDR, "%s", (char *) (G_io_apdu_buffer + PK_LEN)); + snprintf(viewdata.addr, MAX_CHARS_ADDR, "%s", (char *) (G_io_apdu_buffer + VIEW_ADDRESS_BUFFER_OFFSET)); break; case 1: - bip44_to_str(viewdata.addr, MAX_CHARS_ADDR, bip44Path); + bip32_to_str(viewdata.addr, MAX_CHARS_ADDR, hdPath, HDPATH_LEN_DEFAULT); break; } return view_no_error; diff --git a/src/view.h b/deps/ledger-zxlib/app/common/view.h similarity index 97% rename from src/view.h rename to deps/ledger-zxlib/app/common/view.h index 4853a8fe..1908dcdf 100644 --- a/src/view.h +++ b/deps/ledger-zxlib/app/common/view.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018,2019 ZondaX GmbH +* (c) 2018-2020 Zondax GmbH * (c) 2016 Ledger * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/view_internal.h b/deps/ledger-zxlib/app/common/view_internal.h similarity index 94% rename from src/view_internal.h rename to deps/ledger-zxlib/app/common/view_internal.h index a89887fd..1775eafc 100644 --- a/src/view_internal.h +++ b/deps/ledger-zxlib/app/common/view_internal.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * (c) 2016 Ledger * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,14 +17,7 @@ #pragma once #include - -#define MENU_MAIN_APP_LINE1 "Cosmos" - -#ifdef TESTING_ENABLED -#define MENU_MAIN_APP_LINE2 "Cosmos TEST!" -#else -#define MENU_MAIN_APP_LINE2 "App" -#endif +#include "coin.h" #define CUR_FLOW G_ux.flow_stack[G_ux.stack_count-1] diff --git a/src/view_s.c b/deps/ledger-zxlib/app/common/view_s.c similarity index 99% rename from src/view_s.c rename to deps/ledger-zxlib/app/common/view_s.c index d16f95b4..58b91d7f 100644 --- a/src/view_s.c +++ b/deps/ledger-zxlib/app/common/view_s.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018, 2019 ZondaX GmbH +* (c) 2018, 2019 Zondax GmbH * (c) 2016 Ledger * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/view_x.c b/deps/ledger-zxlib/app/common/view_x.c similarity index 99% rename from src/view_x.c rename to deps/ledger-zxlib/app/common/view_x.c index 99c3e482..c3519e03 100644 --- a/src/view_x.c +++ b/deps/ledger-zxlib/app/common/view_x.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018, 2019 ZondaX GmbH +* (c) 2018, 2019 Zondax GmbH * (c) 2016 Ledger * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/deps/ledger-zxlib/cmake/dockerized_build.mk b/deps/ledger-zxlib/cmake/dockerized_build.mk new file mode 100644 index 00000000..91ca574a --- /dev/null +++ b/deps/ledger-zxlib/cmake/dockerized_build.mk @@ -0,0 +1,131 @@ +#******************************************************************************* +#* (c) 2019 Zondax GmbH +#* +#* 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. +#******************************************************************************** + +.PHONY: all deps build clean load delete check_python show_info_recovery_mode + +LEDGER_SRC=$(CURDIR)/app +DOCKER_APP_SRC=/project +DOCKER_APP_BIN=$(DOCKER_APP_SRC)/app/bin/app.elf + +DOCKER_IMAGE=zondax/ledger-docker-bolos:latest +DOCKER_BOLOS_SDK=/project/deps/nanos-secure-sdk +DOCKER_BOLOS_SDKX=/project/deps/nano2-sdk + +SCP_PUBKEY=049bc79d139c70c83a4b19e8922e5ee3e0080bb14a2e8b0752aa42cda90a1463f689b0fa68c1c0246845c2074787b649d0d8a6c0b97d4607065eee3057bdf16b83 +SCP_PRIVKEY=ff701d781f43ce106f72dc26a46b6a83e053b5d07bb3d4ceab79c91ca822a66b + +INTERACTIVE:=$(shell [ -t 0 ] && echo 1) + +ifdef INTERACTIVE +INTERACTIVE_SETTING:="-i" +TTY_SETTING:="-t" +else +INTERACTIVE_SETTING:= +TTY_SETTING:= +endif + +define run_docker + docker run $(TTY_SETTING) $(INTERACTIVE_SETTING) --rm \ + --privileged \ + -e SCP_PRIVKEY=$(SCP_PRIVKEY) \ + -e BOLOS_SDK=$(1) \ + -e BOLOS_ENV=/opt/bolos \ + -p 1234:1234 \ + -p 8001:8001 \ + -p 9998-9999:9998-9999 \ + -u $(shell id -u) \ + -v $(shell pwd):/project \ + -e DISPLAY=$(shell echo ${DISPLAY}) \ + -v /tmp/.X11-unix:/tmp/.X11-unix:ro \ + $(DOCKER_IMAGE) \ + "$(2)" +endef + +all: build + +check_python: + @python -c 'import sys; sys.exit(3-sys.version_info.major)' || (echo "The python command does not point to Python 3"; exit 1) + +deps: check_python + @echo "Install dependencies" + $(CURDIR)/deps/ledger-zxlib/install_deps.sh + +pull: + docker pull $(DOCKER_IMAGE) + +build_rust: + $(call run_docker,$(DOCKER_BOLOS_SDK),make -C $(DOCKER_APP_SRC) rust) + +build: build_rust + $(info Replacing app icon) + @cp $(LEDGER_SRC)/nanos_icon.gif $(LEDGER_SRC)/glyphs/icon_app.gif + $(info calling make inside docker) + $(call run_docker,$(DOCKER_BOLOS_SDK),make -C $(DOCKER_APP_SRC)) + +buildX: build_rust + @cp $(LEDGER_SRC)/nanos_icon.gif $(LEDGER_SRC)/glyphs/icon_app.gif + @convert $(LEDGER_SRC)/nanos_icon.gif -crop 14x14+1+1 +repage -negate $(LEDGER_SRC)/nanox_icon.gif + $(call run_docker,$(DOCKER_BOLOS_SDKX),make -C $(DOCKER_APP_SRC)) + +clean: + $(call run_docker,$(DOCKER_BOLOS_SDK),make -C $(DOCKER_APP_SRC) clean) + +shell: + $(call run_docker,$(DOCKER_BOLOS_SDK) -t,bash) + +debug: build + $(call run_docker,$(DOCKER_BOLOS_SDK),/home/zondax/speculos/speculos.py --debug --display headless -t $(DOCKER_APP_BIN)) + +emu: build + $(call run_docker,$(DOCKER_BOLOS_SDK),/home/zondax/speculos/speculos.py --ontop --zoom 3 --vnc-port 8001 $(DOCKER_APP_BIN)) + + +load: + ${LEDGER_SRC}/pkg/zxtool.sh load + +delete: + ${LEDGER_SRC}/pkg/zxtool.sh delete + +show_info_recovery_mode: + @echo "This command requires a Ledger Nano S in recovery mode. To go into recovery mode, follow:" + @echo " 1. Settings -> Device -> Reset all and confirm" + @echo " 2. Unplug device, press and hold the right button, plug-in again" + @echo " 3. Navigate to the main menu" + @echo "If everything was correct, no PIN needs to be entered." + +# This target will initialize the device with the integration testing mnemonic +dev_init: show_info_recovery_mode + @echo "Initializing device with test mnemonic! WARNING TAKES 2 MINUTES AND REQUIRES RECOVERY MODE" + @python -m ledgerblue.hostOnboard --apdu --id 0 --prefix "" --passphrase "" --pin 5555 --words "equip will roof matter pink blind book anxiety banner elbow sun young" + +# This target will initialize the device with the secondary integration testing mnemonic (Bob) +dev_init_secondary: check_python show_info_recovery_mode + @echo "Initializing device with secondary test mnemonic! WARNING TAKES 2 MINUTES AND REQUIRES RECOVERY MODE" + @python -m ledgerblue.hostOnboard --apdu --id 0 --prefix "" --passphrase "" --pin 5555 --words "elite vote proof agree february step sibling sand grocery axis false cup" + +# This target will setup a custom developer certificate +dev_ca: check_python + @python -m ledgerblue.setupCustomCA --targetId 0x31100004 --public $(SCP_PUBKEY) --name zondax + +dev_ca_delete: check_python + @python -m ledgerblue.resetCustomCA --targetId 0x31100004 + +# This target will setup a custom developer certificate +dev_ca2: check_python + @python -m ledgerblue.setupCustomCA --targetId 0x33000004 --public $(SCP_PUBKEY) --name zondax + +dev_ca_delete2: check_python + @python -m ledgerblue.resetCustomCA --targetId 0x33000004 diff --git a/deps/ledger-zxlib/include/apdu_codes.h b/deps/ledger-zxlib/include/apdu_codes.h index 2e545069..e1c22f5a 100644 --- a/deps/ledger-zxlib/include/apdu_codes.h +++ b/deps/ledger-zxlib/include/apdu_codes.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018 ZondaX GmbH +* (c) 2018 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,9 @@ ********************************************************************************/ #pragma once + #include "inttypes.h" +#include "zxmacros.h" // Based on ISO7816 @@ -41,8 +43,7 @@ #define APDU_CODE_SIGN_VERIFY_ERROR 0x6F01 -inline void set_code(uint8_t *buffer, uint8_t offset, uint16_t value) -{ - *(buffer + offset) = (uint8_t)(value >> 8); - *(buffer + offset + 1) = (uint8_t)(value & 0xFF); +__Z_INLINE void set_code(uint8_t *buffer, uint8_t offset, uint16_t value) { + *(buffer + offset) = (uint8_t) (value >> 8); + *(buffer + offset + 1) = (uint8_t) (value & 0xFF); } diff --git a/deps/ledger-zxlib/include/bech32.h b/deps/ledger-zxlib/include/bech32.h index ead5a2f6..df8c2ad0 100644 --- a/deps/ledger-zxlib/include/bech32.h +++ b/deps/ledger-zxlib/include/bech32.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/deps/ledger-zxlib/include/bignum.h b/deps/ledger-zxlib/include/bignum.h index 08bdcb5e..9bf31f8f 100644 --- a/deps/ledger-zxlib/include/bignum.h +++ b/deps/ledger-zxlib/include/bignum.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/deps/ledger-zxlib/include/bittools.h b/deps/ledger-zxlib/include/bittools.h index d2cfa448..1903878d 100644 --- a/deps/ledger-zxlib/include/bittools.h +++ b/deps/ledger-zxlib/include/bittools.h @@ -27,10 +27,10 @@ extern "C" { int convert_bits(uint8_t *out, size_t *outlen, - int outbits, + int outBits, const uint8_t *in, - size_t inlen, - int inbits, int pad); + size_t inLen, + int inBits, int pad); #ifdef __cplusplus } diff --git a/deps/ledger-zxlib/include/buffering.h b/deps/ledger-zxlib/include/buffering.h index 63eef3a2..ec6e80cf 100644 --- a/deps/ledger-zxlib/include/buffering.h +++ b/deps/ledger-zxlib/include/buffering.h @@ -1,6 +1,6 @@ /******************************************************************************* * (c) 2016 Ledger -* (c) 2018 ZondaX GmbH +* (c) 2018 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/deps/ledger-zxlib/include/hexutils.h b/deps/ledger-zxlib/include/hexutils.h index e8010842..3cbaad8e 100644 --- a/deps/ledger-zxlib/include/hexutils.h +++ b/deps/ledger-zxlib/include/hexutils.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018 ZondaX GmbH +* (c) 2018 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ extern "C" { #endif -size_t parseHexString(const char *s, uint8_t *out); +size_t parseHexString(uint8_t *out, uint16_t outLen, const char *input); #ifdef __cplusplus } diff --git a/deps/ledger-zxlib/include/sigutils.h b/deps/ledger-zxlib/include/sigutils.h new file mode 100644 index 00000000..862f809a --- /dev/null +++ b/deps/ledger-zxlib/include/sigutils.h @@ -0,0 +1,41 @@ +/******************************************************************************* +* (c) 2020 Zondax GmbH +* +* 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 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + no_error = 0, + invalid_derPrefix, + invalid_payloadLen, + invalid_rmaker, + invalid_rLen, + invalid_smarker, + invalid_sLen, +} err_convert_e; + +err_convert_e convertDERtoRSV(const uint8_t *inSignatureDER, + unsigned int inInfo, + uint8_t *outR, + uint8_t *outS, + uint8_t *outV); + +#ifdef __cplusplus +} +#endif diff --git a/deps/ledger-zxlib/include/utf8.h b/deps/ledger-zxlib/include/utf8.h index db48edbb..4a0ca694 100644 --- a/deps/ledger-zxlib/include/utf8.h +++ b/deps/ledger-zxlib/include/utf8.h @@ -1166,7 +1166,7 @@ utf8_int32_t utf8lwrcodepoint(utf8_int32_t cp) { case 0x03f9: cp = 0x03f2; break; case 0x03f7: cp = 0x03f8; break; case 0x03fa: cp = 0x03fb; break; - }; + } } return cp; @@ -1242,7 +1242,7 @@ utf8_int32_t utf8uprcodepoint(utf8_int32_t cp) { case 0x03f2: cp = 0x03f9; break; case 0x03f8: cp = 0x03f7; break; case 0x03fb: cp = 0x03fa; break; - }; + } } return cp; @@ -1259,4 +1259,4 @@ utf8_int32_t utf8uprcodepoint(utf8_int32_t cp) { #pragma clang diagnostic pop #endif -#endif // SHEREDOM_UTF8_H_INCLUDED \ No newline at end of file +#endif // SHEREDOM_UTF8_H_INCLUDED diff --git a/deps/ledger-zxlib/include/view_templates.h b/deps/ledger-zxlib/include/view_templates.h index 97efd03b..0b5c6bcd 100644 --- a/deps/ledger-zxlib/include/view_templates.h +++ b/deps/ledger-zxlib/include/view_templates.h @@ -1,6 +1,6 @@ /******************************************************************************* * (c) 2016 Ledger -* (c) 2018 ZondaX GmbH +* (c) 2018 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/deps/ledger-zxlib/include/zxformat.h b/deps/ledger-zxlib/include/zxformat.h new file mode 100644 index 00000000..6ed3cb52 --- /dev/null +++ b/deps/ledger-zxlib/include/zxformat.h @@ -0,0 +1,249 @@ +/******************************************************************************* +* (c) 2018 Zondax GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define NUM_TO_STR(TYPE) __Z_INLINE const char * TYPE##_to_str(char *data, int dataLen, TYPE##_t number) { \ + if (dataLen < 2) return "Buffer too small"; \ + MEMZERO(data, dataLen); \ + char *p = data; \ + if (number < 0) { *(p++) = '-'; data++; } \ + else if (number == 0) { *(p++) = '0'; } \ + TYPE##_t tmp; \ + while (number != 0) { \ + if (p - data >= (dataLen - 1)) { return "Buffer too small"; } \ + tmp = number % 10; \ + tmp = tmp < 0 ? -tmp : tmp; \ + *(p++) = (char) ('0' + tmp); \ + number /= 10u; \ + } \ + while (p > data) { \ + p--; \ + char z = *data; *data = *p; *p = z; \ + data++; \ + } \ + return NULL; \ +} + +NUM_TO_STR(int64) + +NUM_TO_STR(uint64) + +__Z_INLINE void bip32_to_str(char *s, uint32_t max, const uint32_t *path, uint8_t pathLen) { + if (pathLen == 0) { + snprintf(s, max, "EMPTY PATH"); + return; + } + + if (pathLen > 5) { + snprintf(s, max, "ERROR"); + return; + } + + uint32_t offset = 0; + for (int i = 0; i < pathLen; i++) { + uint32_t written = snprintf(s + offset, max - offset, "%d%s%s", + path[i] & 0x7FFFFFFFu, + (path[i] & 0x80000000u) != 0 ? "'" : "", + i == pathLen - 1 ? "" : "/"); + if (written >= max - offset) { + snprintf(s, max, "ERROR"); + return; + } + offset += written; + } +} + +__Z_INLINE void bip44_to_str(char *s, uint32_t max, const uint32_t path[5]) { + bip32_to_str(s, max, path, 5); +} + +__Z_INLINE int8_t str_to_int8(const char *start, const char *end, char *error) { + int sign = 1; + if (*start == '-') { + sign = -1; + start++; + } + + int64_t value = 0; + int multiplier = 1; + for (const char *s = end - 1; s >= start; s--) { + int delta = (*s - '0'); + if (delta >= 0 && delta <= 9) { + value += (delta * multiplier); + multiplier *= 10; + } else { + if (error != NULL) { + *error = 1; + return 0; + } + } + } + + value *= sign; + if (value >= INT8_MIN && value <= INT8_MAX) { + return (int8_t) value; + } + if (error != NULL) { + *error = 1; + } + return 0; +} + +__Z_INLINE int64_t str_to_int64(const char *start, const char *end, char *error) { + int sign = 1; + if (*start == '-') { + sign = -1; + start++; + } + + int64_t value = 0; + uint64_t multiplier = 1; + for (const char *s = end - 1; s >= start; s--) { + int delta = (*s - '0'); + if (delta >= 0 && delta <= 9) { + value += (delta * multiplier); + multiplier *= 10; + } else { + if (error != NULL) { + *error = 1; + return 0; + } + } + } + + return value * sign; +} + +__Z_INLINE uint8_t fpstr_to_str(char *out, uint16_t outLen, const char *number, uint8_t decimals) { + MEMZERO(out, outLen); + size_t digits = strlen(number); + + if (decimals == 0) { + if (digits == 0) { + snprintf(out, outLen, "0"); + return 0; + } else if (outLen < digits) { + snprintf(out, outLen, "ERR"); + return 1; + } + strcpy(out, number); + return 0; + } + + if ((outLen < decimals + 2) || + (outLen < digits + 1)) { + snprintf(out, outLen, "ERR"); + return 1; + } + + if (digits <= decimals) { + // First part + strcpy(out, "0."); + out += 2; + MEMSET(out, '0', decimals - digits); + out += decimals - digits; + } else { + const size_t shift = digits - decimals; + strcpy(out, number); + number += shift; + out += shift; + *out++ = '.'; + } + + strcpy(out, number); + return 0; +} + +__Z_INLINE void fpuint64_to_str(char *out, uint16_t outLen, const uint64_t value, uint8_t decimals) { + char buffer[30]; + MEMZERO(buffer, sizeof(buffer)); + int64_to_str(buffer, sizeof(buffer), value); + fpstr_to_str(out, outLen, buffer, decimals); +} + +__Z_INLINE uint64_t uint64_from_BEarray(const uint8_t data[8]) { + uint64_t result = 0; + for (uint8_t i = 0; i < 8u; i++) { + result <<= 8u; + result += data[i]; + } + return result; +} + +__Z_INLINE uint32_t array_to_hexstr(char *dst, uint16_t dstLen, const uint8_t *src, uint8_t count) { + + if (dstLen < (count * 2 + 1)) { + return 0; + } + + const char hexchars[] = "0123456789ABCDEF"; + for (uint8_t i = 0; i < count; i++, src++) { + *dst++ = hexchars[*src >> 4u]; + *dst++ = hexchars[*src & 0x0Fu]; + } + *dst = 0; // terminate string + + return count * 2; +} + +__Z_INLINE void pageStringExt(char *outValue, uint16_t outValueLen, + const char *inValue, uint16_t inValueLen, + uint8_t pageIdx, uint8_t *pageCount) { + MEMZERO(outValue, outValueLen); + *pageCount = 0; + + outValueLen--; // leave space for NULL termination + if (outValueLen == 0) { + return; + } + + if (inValueLen == 0) { + return; + } + + *pageCount = (inValueLen / outValueLen); + const uint16_t lastChunkLen = (inValueLen % outValueLen); + + if (lastChunkLen > 0) { + (*pageCount)++; + } + + if (pageIdx < *pageCount) { + if (lastChunkLen > 0 && pageIdx == *pageCount - 1) { + MEMCPY(outValue, inValue + (pageIdx * outValueLen), lastChunkLen); + } else { + MEMCPY(outValue, inValue + (pageIdx * outValueLen), outValueLen); + } + } +} + +__Z_INLINE void pageString(char *outValue, uint16_t outValueLen, + const char *inValue, + uint8_t pageIdx, uint8_t *pageCount) { + pageStringExt(outValue, outValueLen, inValue, strlen(inValue), pageIdx, pageCount); +} + +size_t asciify(char *utf8_in); + +size_t asciify_ext(const char *utf8_in, char *ascii_only_out); + +#ifdef __cplusplus +} +#endif diff --git a/deps/ledger-zxlib/include/zxmacros.h b/deps/ledger-zxlib/include/zxmacros.h index e875aaef..133ccaf4 100644 --- a/deps/ledger-zxlib/include/zxmacros.h +++ b/deps/ledger-zxlib/include/zxmacros.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018 ZondaX GmbH +* (c) 2018 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,16 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. ********************************************************************************/ - #pragma once +#pragma clang diagnostic push +#pragma ide diagnostic ignored "modernize-use-nullptr" +#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection" +#pragma ide diagnostic ignored "OCUnusedMacroInspection" +#pragma ide diagnostic ignored "modernize-deprecated-headers" + #ifdef __cplusplus extern "C" { #endif #include "string.h" #ifndef __APPLE__ -extern void explicit_bzero (void *__s, size_t __n) __THROW __nonnull ((1)); +extern void explicit_bzero(void *__s, size_t __n) __THROW __nonnull ((1)); #endif #define __Z_INLINE inline __attribute__((always_inline)) static @@ -33,9 +38,12 @@ extern void explicit_bzero (void *__s, size_t __n) __THROW __nonnull ((1)); #if defined(TARGET_NANOX) #define NV_CONST const #define NV_VOL volatile +#define SAFE_HEARTBEAT(X) X; #else #define NV_CONST #define NV_VOL +// Disabling heartbeat (this was a Nano S workaround for U2F) +#define SAFE_HEARTBEAT(X) X; /*io_seproxyhal_io_heartbeat(); X; io_seproxyhal_io_heartbeat();*/ #endif #ifndef PIC @@ -94,6 +102,9 @@ void __logstack(); #define MEMCMP memcmp #define MEMCPY_NV memcpy +#define CX_ECCINFO_PARITY_ODD 1u +#define CX_ECCINFO_xGTn 2u + #ifndef __APPLE__ #define MEMZERO explicit_bzero #else @@ -123,191 +134,18 @@ __Z_INLINE void __memzero(void *buffer, size_t s) { memset(buffer, 0, s); } #define NtoHL(x) (x) #endif -#define NUM_TO_STR(TYPE) __Z_INLINE const char * TYPE##_to_str(char *data, int dataLen, TYPE##_t number) { \ - if (dataLen < 2) return "Buffer too small"; \ - MEMZERO(data, dataLen); \ - char *p = data; \ - if (number < 0) { *(p++) = '-'; data++; } \ - else if (number == 0) { *(p++) = '0'; } \ - TYPE##_t tmp; \ - while (number != 0) { \ - if (p - data >= (dataLen - 1)) { return "Buffer too small"; } \ - tmp = number % 10; \ - tmp = tmp < 0 ? -tmp : tmp; \ - *(p++) = (char) ('0' + tmp); \ - number /= 10u; \ - } \ - while (p > data) { \ - p--; \ - char z = *data; *data = *p; *p = z; \ - data++; \ - } \ - return NULL; \ -} - -NUM_TO_STR(int64) - -NUM_TO_STR(uint64) - -__Z_INLINE void bip44_to_str(char *s, uint32_t max, const uint32_t path[5]) { - snprintf(s, max, "%d%s%d%s%d%s%d%s%d%s", - path[0] & 0x7FFFFFFFu, (path[0] & 0x80000000u) != 0 ? "'/" : "/", - path[1] & 0x7FFFFFFFu, (path[1] & 0x80000000u) != 0 ? "'/" : "/", - path[2] & 0x7FFFFFFFu, (path[2] & 0x80000000u) != 0 ? "'/" : "/", - path[3] & 0x7FFFFFFFu, (path[3] & 0x80000000u) != 0 ? "'/" : "/", - path[4] & 0x7FFFFFFFu, (path[4] & 0x80000000u) != 0 ? "'" : ""); -} - -__Z_INLINE int8_t str_to_int8(const char *start, const char *end, char *error) { - - int sign = 1; - if (*start == '-') { - sign = -1; - start++; - } - - int64_t value = 0; - int multiplier = 1; - for (const char *s = end - 1; s >= start; s--) { - int delta = (*s - '0'); - if (delta >= 0 && delta <= 9) { - value += (delta * multiplier); - multiplier *= 10; - } else { - if (error != NULL) { - *error = 1; - return 0; - } - } - } - - value *= sign; - if (value >= INT8_MIN && value <= INT8_MAX) { - return (int8_t) value; - } - if (error != NULL) { - *error = 1; - } - return 0; -} - -__Z_INLINE int64_t str_to_int64(const char *start, const char *end, char *error) { - - int sign = 1; - if (*start == '-') { - sign = -1; - start++; - } - - int64_t value = 0; - uint64_t multiplier = 1; - for (const char *s = end - 1; s >= start; s--) { - int delta = (*s - '0'); - if (delta >= 0 && delta <= 9) { - value += (delta * multiplier); - multiplier *= 10; - } else { - if (error != NULL) { - *error = 1; - return 0; - } - } - } - - return value * sign; -} - -__Z_INLINE void fpstr_to_str(char *dst, const char *number, uint8_t decimals) { - size_t digits = strlen(number); - - if (digits <= decimals) { - *dst++ = '0'; - *dst++ = '.'; - for (uint16_t i = 0; i < decimals - digits; i++, dst++) - *dst = '0'; - strcpy(dst, number); - } else { - strcpy(dst, number); - const size_t shift = digits - decimals; - dst = dst + shift; - *dst++ = '.'; +#define sizeof_field(type, member) sizeof(((type *)0)->member) +#define array_length(array) (sizeof(array) / sizeof(array[0])) - const char *p = number + shift; - strcpy(dst, p); - } +__Z_INLINE void strncpy_s(char *dst, const char *src, size_t dstSize) { + MEMZERO(dst, dstSize); + strncpy(dst, src, dstSize - 1); } -__Z_INLINE void fpuint64_to_str(char *dst, const uint64_t value, uint8_t decimals) { - char buffer[30]; - MEMZERO(buffer, sizeof(buffer)); - int64_to_str(buffer, 30, value); - fpstr_to_str(dst, buffer, decimals); -} - -__Z_INLINE uint64_t uint64_from_BEarray(const uint8_t data[8]) { - uint64_t result = 0; - for (uint8_t i = 0; i < 8u; i++) { - result <<= 8u; - result += data[i]; - } - return result; -} - -__Z_INLINE void array_to_hexstr(char *dst, const uint8_t *src, uint8_t count) { - const char hexchars[] = "0123456789ABCDEF"; - for (uint8_t i = 0; i < count; i++, src++) { - *dst++ = hexchars[*src >> 4u]; - *dst++ = hexchars[*src & 0x0Fu]; - } - *dst = 0; // terminate string -} - -__Z_INLINE void pageStringExt(char *outValue, uint16_t outValueLen, - const char *inValue, uint16_t inValueLen, - uint8_t pageIdx, uint8_t *pageCount) { - MEMZERO(outValue, outValueLen); - - outValueLen--; // leave space for NULL termination - if (outValueLen == 0) { - return; - } - - if (inValueLen == 0) { - *pageCount = 0; - return; - } - - *pageCount = (inValueLen / outValueLen); - const uint16_t lastChunkLen = (inValueLen % outValueLen); - - if (lastChunkLen > 0) { - (*pageCount)++; - } - - if (pageIdx < *pageCount) { - if (lastChunkLen > 0 && pageIdx == *pageCount - 1) { - MEMCPY(outValue, inValue + (pageIdx * outValueLen), lastChunkLen); - } else { - MEMCPY(outValue, inValue + (pageIdx * outValueLen), outValueLen); - } - } -} - -__Z_INLINE void pageString(char *outValue, uint16_t outValueLen, - const char *inValue, - uint8_t pageIdx, uint8_t *pageCount) { - pageStringExt(outValue, outValueLen, inValue, strlen(inValue), pageIdx, pageCount); -} - -/////////////////////// -/////////////////////// -/////////////////////// -/////////////////////// - -size_t asciify(char *utf8_in); - -size_t asciify_ext(const char *utf8_in, char *ascii_only_out); - #ifdef __cplusplus } #endif + +#pragma clang diagnostic pop + +#include "zxformat.h" diff --git a/deps/ledger-zxlib/include/zxtypes.h b/deps/ledger-zxlib/include/zxtypes.h index 1712c46f..9aae6770 100644 --- a/deps/ledger-zxlib/include/zxtypes.h +++ b/deps/ledger-zxlib/include/zxtypes.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018 ZondaX GmbH +* (c) 2018 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/deps/ledger-zxlib/scripts/install_deps.sh b/deps/ledger-zxlib/scripts/install_deps.sh new file mode 100755 index 00000000..83c55b22 --- /dev/null +++ b/deps/ledger-zxlib/scripts/install_deps.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +#******************************************************************************* +#* (c) 2018 Zondax GmbH +#* +#* 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. +#******************************************************************************** + +os_string="$(uname -s)" +case "${os_string}" in + Linux*) + sudo apt-get install libusb-1.0.0 libudev-dev + pip install -U setuptools + pip install -U --no-cache ledgerblue ecpy + ;; + Darwin*) + brew install libusb + pip install -U ledgerblue ecpy + ;; + *) + echo "OS not recognized" + ;; +esac diff --git a/scripts/template.sh b/deps/ledger-zxlib/scripts/template.sh similarity index 100% rename from scripts/template.sh rename to deps/ledger-zxlib/scripts/template.sh diff --git a/deps/ledger-zxlib/src/bech32.c b/deps/ledger-zxlib/src/bech32.c index c450c4f2..ec1e85ee 100644 --- a/deps/ledger-zxlib/src/bech32.c +++ b/deps/ledger-zxlib/src/bech32.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/deps/ledger-zxlib/src/bignum.c b/deps/ledger-zxlib/src/bignum.c index 56a33160..b4c3e483 100644 --- a/deps/ledger-zxlib/src/bignum.c +++ b/deps/ledger-zxlib/src/bignum.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/deps/ledger-zxlib/src/buffering.c b/deps/ledger-zxlib/src/buffering.c index 985accf3..97d56fcd 100644 --- a/deps/ledger-zxlib/src/buffering.c +++ b/deps/ledger-zxlib/src/buffering.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018 ZondaX GmbH +* (c) 2018 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/deps/ledger-zxlib/src/hexutils.c b/deps/ledger-zxlib/src/hexutils.c index 6ea676fd..bc426072 100644 --- a/deps/ledger-zxlib/src/hexutils.c +++ b/deps/ledger-zxlib/src/hexutils.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018 ZondaX GmbH +* (c) 2018 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,36 +19,39 @@ #include "hexutils.h" uint8_t hex2dec(char c, char *out) { - c = (char) tolower((int)c); + c = (char) tolower((int) c); - if (!isxdigit((int)c)) { + if (!isxdigit((int) c)) { return -1; } - if (isdigit((int)c)) { - *out = c - '0'; + if (isdigit((int) c)) { + *out = (char) (c - '0'); return 0; } - *out = c - 'a' + 10; + *out = (char) (c - 'a' + 10); return 0; } -size_t parseHexString(const char *s, uint8_t *out) { - size_t len = strlen(s); +size_t parseHexString(uint8_t *out, uint16_t outLen, const char *input) { + size_t len = strnlen(input, outLen * 2 + 1); + if ( (len / 2) > outLen) { + return 0; + } if (len % 2 == 1) { return 0; } for (size_t i = 0; i < len; i += 2) { char tmp1, tmp2; - if (hex2dec(s[i], &tmp1)) + if (hex2dec(input[i], &tmp1)) return 0; - if (hex2dec(s[i + 1], &tmp2)) + if (hex2dec(input[i + 1], &tmp2)) return 0; out[i >> 1u] = (tmp1 << 4u) + tmp2; } - return len >> 1u; -}; + return (len / 2); +} diff --git a/deps/ledger-zxlib/src/segwit_addr.c b/deps/ledger-zxlib/src/segwit_addr.c index 406c4dc8..d3e99ab4 100644 --- a/deps/ledger-zxlib/src/segwit_addr.c +++ b/deps/ledger-zxlib/src/segwit_addr.c @@ -140,23 +140,23 @@ int bech32_decode(char* hrp, uint8_t *data, size_t *data_len, const char *input) return chk == 1; } -int convert_bits(uint8_t* out, size_t* outlen, int outbits, const uint8_t* in, size_t inlen, int inbits, int pad) { +int convert_bits(uint8_t* out, size_t* outlen, int outBits, const uint8_t* in, size_t inLen, int inBits, int pad) { uint32_t val = 0; int bits = 0; - uint32_t maxv = (((uint32_t)1) << outbits) - 1; - while (inlen--) { - val = (val << inbits) | *(in++); - bits += inbits; - while (bits >= outbits) { - bits -= outbits; + uint32_t maxv = (((uint32_t)1) << outBits) - 1; + while (inLen--) { + val = (val << inBits) | *(in++); + bits += inBits; + while (bits >= outBits) { + bits -= outBits; out[(*outlen)++] = (val >> bits) & maxv; } } if (pad) { if (bits) { - out[(*outlen)++] = (val << (outbits - bits)) & maxv; + out[(*outlen)++] = (val << (outBits - bits)) & maxv; } - } else if (((val << (outbits - bits)) & maxv) || bits >= inbits) { + } else if (((val << (outBits - bits)) & maxv) || bits >= inBits) { return 0; } return 1; diff --git a/deps/ledger-zxlib/src/sigutils.c b/deps/ledger-zxlib/src/sigutils.c new file mode 100644 index 00000000..222a40dd --- /dev/null +++ b/deps/ledger-zxlib/src/sigutils.c @@ -0,0 +1,98 @@ +/******************************************************************************* +* (c) 2020 Zondax GmbH +* +* 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 +#include + +err_convert_e convertDERtoRSV(const uint8_t *inSignatureDER, + unsigned int inInfo, + uint8_t *outR, + uint8_t *outS, + uint8_t *outV) { + + // https://github.com/libbitcoin/libbitcoin-system/wiki/ECDSA-and-DER-Signatures#serialised-der-signature-sequence + // 0 [1 byte] - DER Prefix + // 1 [1 byte] - Payload len + // 2 [1 byte] - R Marker. Always 02 + // 3 [1 byte] - R Len RLEN + // ROFFSET ... [.?. byte] - R ROFFSET + // ROFFSET+RLEN [1 byte] - S Marker. Always 02 + // ROFFSET+RLEN+1 [1 byte] - S Length SLEN + // ROFFSET+RLEN+2 [.?. byte] - S SOFFSET + // Prepare response + // R [32] + // S [32] + // V [1] + + const uint8_t derPrefix = *(inSignatureDER); + if (derPrefix != 0x30) { + return invalid_derPrefix; + } + + const uint8_t payloadLen = *(inSignatureDER + 1); + const uint8_t minPayloadLen = 2 + 32 + 2 + 32; + const uint8_t maxPayloadLen = 2 + 33 + 2 + 33; + if (payloadLen < minPayloadLen || payloadLen > maxPayloadLen) { + return invalid_payloadLen; + } + + const uint8_t rMarker = *(inSignatureDER + 2); + if (rMarker != 0x02) { + return invalid_rmaker; + } + + const uint8_t rLen = *(inSignatureDER + 3); + if (rLen > 33 || rLen < 32) { + return invalid_rLen; + } + + const uint8_t sMarker = *(inSignatureDER + 4 + rLen); + if (sMarker != 0x02) { + return invalid_smarker; + } + + const uint8_t sLen = *(inSignatureDER + 4 + rLen + 1); + if (sLen > 33 || sLen < 32) { + return invalid_sLen; + } + + // Get data fields + const uint8_t *rPtr = inSignatureDER + 4; + // Correct field pointers + if (rLen == 33) { + rPtr++; // get only 32 bytes + } + + const uint8_t *sPtr = inSignatureDER + 4 + rLen + 2; + if (sLen == 33) { + sPtr++; // get only 32 bytes + } + + // Prepare V + *outV = 0; + if (inInfo & CX_ECCINFO_PARITY_ODD) { + *outV += 1; + } + if (inInfo & CX_ECCINFO_xGTn) { + *outV += 2; + } + + // Copy things + MEMCPY(outR, rPtr, 32); + MEMCPY(outS, sPtr, 32); + + return no_error; +} diff --git a/deps/ledger-zxlib/src/zxmacros.c b/deps/ledger-zxlib/src/zxmacros.c index 65fb22c4..fc98f341 100644 --- a/deps/ledger-zxlib/src/zxmacros.c +++ b/deps/ledger-zxlib/src/zxmacros.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018 ZondaX GmbH +* (c) 2018 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/deps/ledger-zxlib/templates/Makefile.root b/deps/ledger-zxlib/templates/Makefile.root new file mode 100644 index 00000000..a3fcae5f --- /dev/null +++ b/deps/ledger-zxlib/templates/Makefile.root @@ -0,0 +1,29 @@ +#******************************************************************************* +#* (c) 2019 Zondax GmbH +#* +#* 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. +#******************************************************************************** + +# We use BOLOS_SDK to determine the develoment environment that is being used +# BOLOS_SDK IS DEFINED We use the plain Makefile for Ledger +# BOLOS_SDK NOT DEFINED We use a containerized build approach + +ifeq ($(BOLOS_SDK),) +include $(CURDIR)/deps/ledger-zxlib/cmake/dockerized_build.mk +else +default: + $(MAKE) -C app +%: + $(info "Calling app Makefile for target $@") + $(MAKE) -C app $@ +endif diff --git a/deps/ledger-zxlib/tests/asciify.cpp b/deps/ledger-zxlib/tests/asciify.cpp index 832d5ceb..eed67925 100644 --- a/deps/ledger-zxlib/tests/asciify.cpp +++ b/deps/ledger-zxlib/tests/asciify.cpp @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018 ZondaX GmbH +* (c) 2018 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/deps/ledger-zxlib/tests/bech32.cpp b/deps/ledger-zxlib/tests/bech32.cpp index 9fc4e5b7..5a682dde 100644 --- a/deps/ledger-zxlib/tests/bech32.cpp +++ b/deps/ledger-zxlib/tests/bech32.cpp @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/deps/ledger-zxlib/tests/bip44path.cpp b/deps/ledger-zxlib/tests/bip44path.cpp index 18da83f0..c4cb7a45 100644 --- a/deps/ledger-zxlib/tests/bip44path.cpp +++ b/deps/ledger-zxlib/tests/bip44path.cpp @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018 ZondaX GmbH +* (c) 2018 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,48 @@ #include namespace { + TEST(MACROS, bip32empty) { + char buffer[100]; + uint32_t path[] = {44, 60, 0, 0, 1}; + bip32_to_str(buffer, sizeof(buffer), path, 0); + EXPECT_EQ("EMPTY PATH", std::string(buffer)); + } + + TEST(MACROS, bip32tooManyChildren) { + char buffer[100]; + uint32_t path[] = {44, 60, 0, 0, 1}; + bip32_to_str(buffer, sizeof(buffer), path, 200); + EXPECT_EQ("ERROR", std::string(buffer)); + } + + TEST(MACROS, bip32notEnoughSpaceInBuffer1) { + char buffer[6]; + uint32_t path[] = {1234, 60, 0, 0, 1}; + bip32_to_str(buffer, sizeof(buffer), path, 5); + EXPECT_EQ("ERROR", std::string(buffer)); + } + + TEST(MACROS, bip32notEnoughSpaceInBuffer2) { + char buffer[9]; + uint32_t path[] = {1, 1, 1, 1, 1}; + bip32_to_str(buffer, sizeof(buffer), path, 5); + EXPECT_EQ("ERROR", std::string(buffer)); + } + + TEST(MACROS, bip32notEnoughSpaceInBuffer3) { + char buffer[10]; + uint32_t path[] = {1, 1, 1, 1, 0x80000001 }; + bip32_to_str(buffer, sizeof(buffer), path, 5); + EXPECT_EQ("ERROR", std::string(buffer)); + } + + TEST(MACROS, bip32justEnoughSpaceInBuffer3) { + char buffer[10]; + uint32_t path[] = {1, 1, 1, 1, 1}; + bip32_to_str(buffer, sizeof(buffer), path, 5); + EXPECT_EQ("1/1/1/1/1", std::string(buffer)); + } + TEST(MACROS, bip44path1) { uint32_t path[] = {44, 60, 0, 0, 1}; diff --git a/deps/ledger-zxlib/tests/buffering_tests.cpp b/deps/ledger-zxlib/tests/buffering_tests.cpp index d3948196..4af9fd00 100644 --- a/deps/ledger-zxlib/tests/buffering_tests.cpp +++ b/deps/ledger-zxlib/tests/buffering_tests.cpp @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018 ZondaX GmbH +* (c) 2018 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ namespace { TEST(Buffering, SmallBuffer) { - uint8_t ram_buffer[100]; uint8_t flash_buffer[1000]; @@ -43,7 +42,6 @@ namespace { } TEST(Buffering, BigBuffer) { - uint8_t ram_buffer[100]; uint8_t flash_buffer[1000]; @@ -66,7 +64,6 @@ namespace { } TEST(Buffering, SmallBufferMultipleTimesWithinRam) { - uint8_t ram_buffer[100]; uint8_t flash_buffer[1000]; @@ -93,7 +90,6 @@ namespace { } TEST(Buffering, SmallBufferMultipleTimesToFlash) { - uint8_t ram_buffer[100]; uint8_t flash_buffer[1000]; @@ -120,7 +116,6 @@ namespace { } TEST(Buffering, SmallBufferMultipleTimes_CheckData) { - uint8_t ram_buffer[100]; uint8_t flash_buffer[1000]; @@ -154,7 +149,6 @@ namespace { } TEST(Buffering, Reset) { - uint8_t ram_buffer[100]; uint8_t flash_buffer[1000]; @@ -174,11 +168,9 @@ namespace { EXPECT_TRUE(buffering_get_ram_buffer()->in_use) << "After reset RAM should be enabled by default"; EXPECT_FALSE(buffering_get_flash_buffer()->in_use) << "After reset RAM should be enabled by default"; - } TEST(Buffering, NotEnoughRoomInFlash) { - uint8_t ram_buffer[100]; uint8_t flash_buffer[1000]; @@ -193,7 +185,6 @@ namespace { } TEST(Buffering, NoFlashOnlyRAM) { - uint8_t ram_buffer[100]; buffering_init(ram_buffer, diff --git a/deps/ledger-zxlib/tests/doubledabble.cpp b/deps/ledger-zxlib/tests/doubledabble.cpp index dfad45b2..10133a8f 100644 --- a/deps/ledger-zxlib/tests/doubledabble.cpp +++ b/deps/ledger-zxlib/tests/doubledabble.cpp @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,25 +36,25 @@ class BignumBigEndianTests : public ::testing::TestWithParam INSTANTIATE_TEST_CASE_P ( - BignumTestCases, BignumLittleEndianTests, testing::Values( - bignum_testcase_t{"00", "0"}, - bignum_testcase_t{"01", "1"}, - bignum_testcase_t{"0001", "256"}, - bignum_testcase_t{"03E8", "59395"}, - bignum_testcase_t{"E803", "1000"}, - bignum_testcase_t{"10", "16"}, - bignum_testcase_t{"FF01", "511"}, - bignum_testcase_t{"0102", "513"}, - bignum_testcase_t{"FFFF01", "131071"}, - bignum_testcase_t{"a08601", "100000"}, - bignum_testcase_t{"40420f", "1000000"}, - bignum_testcase_t{"809698", "10000000"}, - bignum_testcase_t{"002d3101", "20000000"}, - bignum_testcase_t{"00e1f505", "100000000"}, - bignum_testcase_t{"00407a10f35a", "100000000000000"}, - bignum_testcase_t{"d2029649", "1234567890"}, - bignum_testcase_t{"d20a3fce96f1cf8c9cb4378c37a4873f17621ebce404f5aa13", - "123456789012345678901234567890123456789012345678901234567890"} + BignumTestCases, BignumLittleEndianTests, testing::Values( + bignum_testcase_t{"00", "0"}, + bignum_testcase_t{"01", "1"}, + bignum_testcase_t{"0001", "256"}, + bignum_testcase_t{"03E8", "59395"}, + bignum_testcase_t{"E803", "1000"}, + bignum_testcase_t{"10", "16"}, + bignum_testcase_t{"FF01", "511"}, + bignum_testcase_t{"0102", "513"}, + bignum_testcase_t{"FFFF01", "131071"}, + bignum_testcase_t{"a08601", "100000"}, + bignum_testcase_t{"40420f", "1000000"}, + bignum_testcase_t{"809698", "10000000"}, + bignum_testcase_t{"002d3101", "20000000"}, + bignum_testcase_t{"00e1f505", "100000000"}, + bignum_testcase_t{"00407a10f35a", "100000000000000"}, + bignum_testcase_t{"d2029649", "1234567890"}, + bignum_testcase_t{"d20a3fce96f1cf8c9cb4378c37a4873f17621ebce404f5aa13", + "123456789012345678901234567890123456789012345678901234567890"} )); // Check that bignums are printed properly (parametric tests) @@ -62,7 +62,7 @@ TEST_P(BignumLittleEndianTests, print) { auto testcase = GetParam(); uint8_t inBuffer[100]; - auto inBufferLen = parseHexString(testcase.hex.c_str(), inBuffer); + auto inBufferLen = parseHexString(inBuffer, sizeof(inBuffer), testcase.hex.c_str()); uint8_t bcdOut[100]; uint16_t bcdOutLen = sizeof(bcdOut); @@ -85,7 +85,7 @@ TEST(BignumLittleEndianTests, range) { s << std::setfill('0') << std::setw(2) << std::hex << tmp % 256; tmp /= 256; } - auto inBufferLen = parseHexString(s.str().c_str(), inBuffer); + auto inBufferLen = parseHexString(inBuffer, sizeof(inBuffer), s.str().c_str()); uint8_t bcdOut[100]; uint16_t bcdOutLen = sizeof(bcdOut); @@ -102,27 +102,27 @@ TEST(BignumLittleEndianTests, range) { INSTANTIATE_TEST_CASE_P ( - BignumTestCases, BignumBigEndianTests, testing::Values( - bignum_testcase_t{"00", "0"}, - bignum_testcase_t{"01", "1"}, - bignum_testcase_t{"0001", "1"}, - bignum_testcase_t{"000001", "1"}, - bignum_testcase_t{"03E8", "1000"}, - bignum_testcase_t{"E803", "59395"}, - bignum_testcase_t{"10", "16"}, - bignum_testcase_t{"FF01", "65281"}, - bignum_testcase_t{"01FF", "511"}, - bignum_testcase_t{"0102", "258"}, - bignum_testcase_t{"FFFF01", "16776961"}, - bignum_testcase_t{"a08601", "10520065"}, - bignum_testcase_t{"40420f", "4211215"}, - bignum_testcase_t{"809698", "8427160"}, - bignum_testcase_t{"002d3101", "2961665"}, - bignum_testcase_t{"00e1f505", "14808325"}, - bignum_testcase_t{"00407a10f35a", "276925838170"}, - bignum_testcase_t{"d2029649", "3523384905"}, - bignum_testcase_t{"d20a3fce96f1cf8c9cb4378c37a4873f17621ebce404f5aa13", - "1318442675213289749221432902819395197389189473307425559128595"} + BignumTestCases, BignumBigEndianTests, testing::Values( + bignum_testcase_t{"00", "0"}, + bignum_testcase_t{"01", "1"}, + bignum_testcase_t{"0001", "1"}, + bignum_testcase_t{"000001", "1"}, + bignum_testcase_t{"03E8", "1000"}, + bignum_testcase_t{"E803", "59395"}, + bignum_testcase_t{"10", "16"}, + bignum_testcase_t{"FF01", "65281"}, + bignum_testcase_t{"01FF", "511"}, + bignum_testcase_t{"0102", "258"}, + bignum_testcase_t{"FFFF01", "16776961"}, + bignum_testcase_t{"a08601", "10520065"}, + bignum_testcase_t{"40420f", "4211215"}, + bignum_testcase_t{"809698", "8427160"}, + bignum_testcase_t{"002d3101", "2961665"}, + bignum_testcase_t{"00e1f505", "14808325"}, + bignum_testcase_t{"00407a10f35a", "276925838170"}, + bignum_testcase_t{"d2029649", "3523384905"}, + bignum_testcase_t{"d20a3fce96f1cf8c9cb4378c37a4873f17621ebce404f5aa13", + "1318442675213289749221432902819395197389189473307425559128595"} )); // Check that bignums are printed properly (parametric tests) @@ -130,7 +130,7 @@ TEST_P(BignumBigEndianTests, print) { auto testcase = GetParam(); uint8_t inBuffer[100]; - auto inBufferLen = parseHexString(testcase.hex.c_str(), inBuffer); + auto inBufferLen = parseHexString(inBuffer, sizeof(inBuffer), testcase.hex.c_str()); uint8_t bcdOut[100]; uint16_t bcdOutLen = sizeof(bcdOut); @@ -148,8 +148,8 @@ TEST(BignumBigEndianTests, range) { for (uint64_t i = 0; i < 2500; i += 7) { std::stringstream s; s << std::setfill('0') << std::setw(10) << std::hex << i; - std::cout << s.str() << std::endl; - auto inBufferLen = parseHexString(s.str().c_str(), inBuffer); + std::cout << s.str() << std::endl; + auto inBufferLen = parseHexString(inBuffer, sizeof(inBuffer), s.str().c_str()); uint8_t bcdOut[100]; uint16_t bcdOutLen = sizeof(bcdOut); diff --git a/deps/ledger-zxlib/tests/hexutils.cpp b/deps/ledger-zxlib/tests/hexutils.cpp index 58e5ca1a..e9d3ae4c 100644 --- a/deps/ledger-zxlib/tests/hexutils.cpp +++ b/deps/ledger-zxlib/tests/hexutils.cpp @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018 ZondaX GmbH +* (c) 2018 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,7 @@ #include "gmock/gmock.h" -#include -#include -#include -#include #include -#include -#include #include "hexutils.h" @@ -30,7 +24,7 @@ TEST(HEXUTILS, parseHexString) { char s[] = "1234567890"; uint8_t data[100]; - auto length = parseHexString(s, data); + auto length = parseHexString(data, sizeof(data), s); ASSERT_THAT(length, testing::Eq(5)); @@ -45,7 +39,7 @@ TEST(HEXUTILS, parseHexString2) { char s[] = "be333be7ee"; uint8_t data[100]; - auto length = parseHexString(s, data); + auto length = parseHexString(data, sizeof(data), s); ASSERT_THAT(length, testing::Eq(5)); diff --git a/deps/ledger-zxlib/tests/macros.cpp b/deps/ledger-zxlib/tests/macros.cpp index 86c9ab70..75b3042e 100644 --- a/deps/ledger-zxlib/tests/macros.cpp +++ b/deps/ledger-zxlib/tests/macros.cpp @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018 ZondaX GmbH +* (c) 2018 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,263 +17,361 @@ #include namespace { -TEST(MACROS, array_to_hexstr) { - uint8_t array1[] = {1, 3, 5}; + TEST(FORMAT, array_to_hexstr) { + uint8_t array1[] = {1, 3, 5}; - char output[20]; - memset(output, 1, 20); + char output[20]; + memset(output, 1, 20); - array_to_hexstr(output, array1, sizeof(array1)); - EXPECT_EQ(memcmp(output, "010305", 2*sizeof(array1)), 0); - EXPECT_EQ(output[2*sizeof(array1)], 0); -} -} + array_to_hexstr(output, sizeof(output), array1, sizeof(array1)); + EXPECT_EQ(memcmp(output, "010305", 2 * sizeof(array1)), 0); + EXPECT_EQ(output[2 * sizeof(array1)], 0); + } -namespace { -TEST(MACROS, fpuint64_to_str) { - char output[100]; - printf("\n"); + TEST(FORMAT, fpuint64_to_str) { + char output[100]; + printf("\n"); - fpuint64_to_str(output, 123, 5); - printf("%10s\n", output); - EXPECT_EQ(std::string(output), "0.00123"); + fpuint64_to_str(output, sizeof(output), 123, 5); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "0.00123"); - fpuint64_to_str(output, 1234, 5); - printf("%10s\n", output); - EXPECT_EQ(std::string(output), "0.01234"); + fpuint64_to_str(output, sizeof(output), 1234, 5); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "0.01234"); - fpuint64_to_str(output, 12345, 5); - printf("%10s\n", output); - EXPECT_EQ(std::string(output), "0.12345"); + fpuint64_to_str(output, sizeof(output), 12345, 5); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "0.12345"); - fpuint64_to_str(output, 123456, 5); - printf("%10s\n", output); - EXPECT_EQ(std::string(output), "1.23456"); + fpuint64_to_str(output, sizeof(output), 123456, 5); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "1.23456"); - fpuint64_to_str(output, 1234567, 5); - printf("%10s\n", output); - EXPECT_EQ(std::string(output), "12.34567"); -} + fpuint64_to_str(output, sizeof(output), 1234567, 5); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "12.34567"); + } - TEST(MACROS, fpuint64_to_str_zeros) { + TEST(FORMAT, fpstr_to_str) { char output[100]; printf("\n"); - fpuint64_to_str(output, 0, 9); - printf("%11s\n", output); - EXPECT_EQ(std::string(output), "0.000000000"); + fpstr_to_str(output, sizeof(output), "", 0); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "0"); - fpuint64_to_str(output, 0, 1); - printf("%11s\n", output); - EXPECT_EQ(std::string(output), "0.0"); + fpstr_to_str(output, sizeof(output), "1", 0); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "1"); - fpuint64_to_str(output, 1, 1); - printf("%11s\n", output); - EXPECT_EQ(std::string(output), "0.1"); + fpstr_to_str(output, sizeof(output), "123", 0); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "123"); - fpuint64_to_str(output, 10, 1); - printf("%11s\n", output); - EXPECT_EQ(std::string(output), "1.0"); + fpstr_to_str(output, sizeof(output), "", 5); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "0.00000"); + + fpstr_to_str(output, sizeof(output), "0", 5); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "0.00000"); + + fpstr_to_str(output, sizeof(output), "123", 5); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "0.00123"); + + fpstr_to_str(output, sizeof(output), "1234", 5); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "0.01234"); + + fpstr_to_str(output, sizeof(output), "12345", 5); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "0.12345"); + + fpstr_to_str(output, sizeof(output), "123456", 5); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "1.23456"); + + fpstr_to_str(output, sizeof(output), "1234567", 5); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "12.34567"); } -TEST(INT64_TO_STR, Zero) { + TEST(FORMAT, fpstr_to_str_BAD_zeros) { + char output[8]; + printf("\n"); - char temp[10]; - const char* error = int64_to_str(temp, sizeof(temp), int64_t(0)); - EXPECT_STREQ(temp, "0"); - EXPECT_TRUE(error == nullptr); -} + fpstr_to_str(output, sizeof(output), "", 5); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "0.00000"); -TEST(INT64_TO_STR, Positive_1234) { + fpstr_to_str(output, sizeof(output), "", 6); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "0.000000"); - char temp[10]; - const char* error = int64_to_str(temp, sizeof(temp), int64_t(1234)); - EXPECT_STREQ(temp, "1234"); - EXPECT_TRUE(error == nullptr); -} + fpstr_to_str(output, sizeof(output), "", 7); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "ERR"); -TEST(INT64_TO_STR, Negative_1234) { + fpstr_to_str(output, sizeof(output), "", 8); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "ERR"); + } - char temp[10]; - const char* error = int64_to_str(temp, sizeof(temp), int64_t(-1234)); - EXPECT_STREQ(temp, "-1234"); - EXPECT_TRUE(error == nullptr); -} + TEST(FORMAT, fpstr_to_str_BAD_short) { + char output[8]; + printf("\n"); -TEST(INT64_TO_STR, TooSmall_0) { + fpstr_to_str(output, sizeof(output), "123", 5); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "0.00123"); - char temp[1]; - const char* error = int64_to_str(temp, sizeof(temp), int64_t(0)); - EXPECT_STREQ("Buffer too small", error); -} + fpstr_to_str(output, sizeof(output), "123", 6); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "0.000123"); -TEST(INT64_TO_STR, FitsJust) { + fpstr_to_str(output, sizeof(output), "123", 7); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "ERR"); - char temp[4]; - const char *error = int64_to_str(temp, sizeof(temp), int64_t(999)); - EXPECT_STREQ(temp, "999"); - EXPECT_TRUE(error == nullptr); -} + fpstr_to_str(output, sizeof(output), "123", 8); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "ERR"); + } -TEST(INT64_TO_STR, TooSmall_10) { + TEST(FORMAT, fpstr_to_str_BAD_long) { + char output[8]; + printf("\n"); - char temp[2]; - const char* error = int64_to_str(temp, sizeof(temp), int64_t(10)); - EXPECT_STREQ("Buffer too small", error); -} + fpstr_to_str(output, sizeof(output), "123456", 5); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "1.23456"); -TEST(INT64_TO_STR, Max) { + fpstr_to_str(output, sizeof(output), "123456", 6); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "0.123456"); - char temp[20]; - const char* error = int64_to_str(temp, sizeof(temp), std::numeric_limits::max()); - EXPECT_STREQ(temp, "9223372036854775807"); - EXPECT_TRUE(error == nullptr); -} + fpstr_to_str(output, sizeof(output), "123456", 7); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "ERR"); -TEST(INT64_TO_STR, Min) { + fpstr_to_str(output, sizeof(output), "12345", 7); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "ERR"); - char temp[21]; - const char* error = int64_to_str(temp, sizeof(temp), std::numeric_limits::min()); - EXPECT_STREQ(temp, "-9223372036854775808"); - EXPECT_TRUE(error == nullptr); -} + fpstr_to_str(output, sizeof(output), "12345", 2); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "123.45"); -TEST(STR_TO_INT8, Min) { + fpstr_to_str(output, sizeof(output), "123456", 2); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "1234.56"); - char numberStr[] = "-128"; - char error = 0; - int8_t number = str_to_int8(numberStr, numberStr + strlen(numberStr), &error); - EXPECT_EQ(-128, number); - EXPECT_EQ(0, error); -} + fpstr_to_str(output, sizeof(output), "1234567", 2); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "12345.67"); -TEST(STR_TO_INT8, Max) { + fpstr_to_str(output, sizeof(output), "12345678", 2); + printf("%10s\n", output); + EXPECT_EQ(std::string(output), "ERR"); + } - char numberStr[] = "127"; - char error = 0; - int8_t number = str_to_int8(numberStr, numberStr + strlen(numberStr), &error); - EXPECT_EQ(127, number); - EXPECT_EQ(0, error); -} + TEST(FORMAT, fpuint64_to_str_zeros) { + char output[100]; + printf("\n"); -TEST(STR_TO_INT8, Zero) { + fpuint64_to_str(output, sizeof(output), 0, 9); + printf("%11s\n", output); + EXPECT_EQ(std::string(output), "0.000000000"); - char numberStr[] = "0"; - char error = 0; - int8_t number = str_to_int8(numberStr, numberStr + strlen(numberStr), &error); - EXPECT_EQ(0, number); - EXPECT_EQ(0, error); -} + fpuint64_to_str(output, sizeof(output), 0, 1); + printf("%11s\n", output); + EXPECT_EQ(std::string(output), "0.0"); -TEST(STR_TO_INT8, Hundred) { + fpuint64_to_str(output, sizeof(output), 1, 1); + printf("%11s\n", output); + EXPECT_EQ(std::string(output), "0.1"); - char numberStr[] = "100"; - char error = 0; - int8_t number = str_to_int8(numberStr, numberStr + strlen(numberStr), &error); - EXPECT_EQ(100, number); - EXPECT_EQ(0, error); -} + fpuint64_to_str(output, sizeof(output), 10, 1); + printf("%11s\n", output); + EXPECT_EQ(std::string(output), "1.0"); + } -TEST(STR_TO_INT8, NegHundred) { + TEST(INT64_TO_STR, Zero) { + char temp[10]; + const char *error = int64_to_str(temp, sizeof(temp), int64_t(0)); + EXPECT_STREQ(temp, "0"); + EXPECT_TRUE(error == nullptr); + } - char numberStr[] = "-100"; - char error = 0; - int8_t number = str_to_int8(numberStr, numberStr + strlen(numberStr), &error); - EXPECT_EQ(-100, number); - EXPECT_EQ(0, error); -} + TEST(INT64_TO_STR, Positive_1234) { + char temp[10]; + const char *error = int64_to_str(temp, sizeof(temp), int64_t(1234)); + EXPECT_STREQ(temp, "1234"); + EXPECT_TRUE(error == nullptr); + } -TEST(STR_TO_INT8, OutsideBoundsPositive) { + TEST(INT64_TO_STR, Negative_1234) { + char temp[10]; + const char *error = int64_to_str(temp, sizeof(temp), int64_t(-1234)); + EXPECT_STREQ(temp, "-1234"); + EXPECT_TRUE(error == nullptr); + } - char numberStr[] = "128"; - char error = 0; - int8_t number = str_to_int8(numberStr, numberStr + strlen(numberStr), &error); - EXPECT_EQ(1, error); -} + TEST(INT64_TO_STR, TooSmall_0) { + char temp[1]; + const char *error = int64_to_str(temp, sizeof(temp), int64_t(0)); + EXPECT_STREQ("Buffer too small", error); + } -TEST(STR_TO_INT8, OutsideBoundsNegative) { + TEST(INT64_TO_STR, FitsJust) { + char temp[4]; + const char *error = int64_to_str(temp, sizeof(temp), int64_t(999)); + EXPECT_STREQ(temp, "999"); + EXPECT_TRUE(error == nullptr); + } - char numberStr[] = "-129"; - char error = 0; - int8_t number = str_to_int8(numberStr, numberStr + strlen(numberStr), &error); - EXPECT_EQ(1, error); -} + TEST(INT64_TO_STR, TooSmall_10) { + char temp[2]; + const char *error = int64_to_str(temp, sizeof(temp), int64_t(10)); + EXPECT_STREQ("Buffer too small", error); + } + TEST(INT64_TO_STR, Max) { + char temp[20]; + const char *error = int64_to_str(temp, sizeof(temp), std::numeric_limits::max()); + EXPECT_STREQ(temp, "9223372036854775807"); + EXPECT_TRUE(error == nullptr); + } -TEST(STR_TO_INT8, DummyData_Positive) { + TEST(INT64_TO_STR, Min) { + char temp[21]; + const char *error = int64_to_str(temp, sizeof(temp), std::numeric_limits::min()); + EXPECT_STREQ(temp, "-9223372036854775808"); + EXPECT_TRUE(error == nullptr); + } - char numberStr[] = "100b0"; - char error = 0; - int8_t number = str_to_int8(numberStr, numberStr + strlen(numberStr), &error); - EXPECT_EQ(1, error); -} + TEST(STR_TO_INT8, Min) { + char numberStr[] = "-128"; + char error = 0; + int8_t number = str_to_int8(numberStr, numberStr + strlen(numberStr), &error); + EXPECT_EQ(-128, number); + EXPECT_EQ(0, error); + } -TEST(STR_TO_INT8, DummyData_Negative) { + TEST(STR_TO_INT8, Max) { + char numberStr[] = "127"; + char error = 0; + int8_t number = str_to_int8(numberStr, numberStr + strlen(numberStr), &error); + EXPECT_EQ(127, number); + EXPECT_EQ(0, error); + } - char numberStr[] = "-1002xx"; - char error = 0; - int8_t number = str_to_int8(numberStr, numberStr + strlen(numberStr), &error); - EXPECT_EQ(1, error); -} + TEST(STR_TO_INT8, Zero) { + char numberStr[] = "0"; + char error = 0; + int8_t number = str_to_int8(numberStr, numberStr + strlen(numberStr), &error); + EXPECT_EQ(0, number); + EXPECT_EQ(0, error); + } -TEST(STR_TO_INT64, Min) { + TEST(STR_TO_INT8, Hundred) { + char numberStr[] = "100"; + char error = 0; + int8_t number = str_to_int8(numberStr, numberStr + strlen(numberStr), &error); + EXPECT_EQ(100, number); + EXPECT_EQ(0, error); + } - char numberStr[] = "-9223372036854775807"; - char error = 0; - int64_t number = str_to_int64(numberStr, numberStr + strlen(numberStr), &error); - EXPECT_EQ(-9223372036854775807, number); - EXPECT_EQ(0, error); -} + TEST(STR_TO_INT8, NegHundred) { + char numberStr[] = "-100"; + char error = 0; + int8_t number = str_to_int8(numberStr, numberStr + strlen(numberStr), &error); + EXPECT_EQ(-100, number); + EXPECT_EQ(0, error); + } -TEST(STR_TO_INT64, Max) { + TEST(STR_TO_INT8, OutsideBoundsPositive) { + char numberStr[] = "128"; + char error = 0; + int8_t number = str_to_int8(numberStr, numberStr + strlen(numberStr), &error); + EXPECT_EQ(1, error); + } - char numberStr[] = "9223372036854775807"; - char error = 0; - int64_t number = str_to_int64(numberStr, numberStr + strlen(numberStr), &error); - EXPECT_EQ(9223372036854775807, number); - EXPECT_EQ(0, error); -} + TEST(STR_TO_INT8, OutsideBoundsNegative) { + char numberStr[] = "-129"; + char error = 0; + int8_t number = str_to_int8(numberStr, numberStr + strlen(numberStr), &error); + EXPECT_EQ(1, error); + } -TEST(STR_TO_INT64, Zero) { - char numberStr[] = "0"; - char error = 0; - int64_t number = str_to_int64(numberStr, numberStr + strlen(numberStr), &error); - EXPECT_EQ(0, number); - EXPECT_EQ(0, error); -} + TEST(STR_TO_INT8, DummyData_Positive) { + char numberStr[] = "100b0"; + char error = 0; + int8_t number = str_to_int8(numberStr, numberStr + strlen(numberStr), &error); + EXPECT_EQ(1, error); + } -TEST(STR_TO_INT64, Hundred) { + TEST(STR_TO_INT8, DummyData_Negative) { + char numberStr[] = "-1002xx"; + char error = 0; + int8_t number = str_to_int8(numberStr, numberStr + strlen(numberStr), &error); + EXPECT_EQ(1, error); + } - char numberStr[] = "100"; - char error = 0; - int64_t number = str_to_int64(numberStr, numberStr + strlen(numberStr), &error); - EXPECT_EQ(100, number); - EXPECT_EQ(0, error); -} + TEST(STR_TO_INT64, Min) { + char numberStr[] = "-9223372036854775807"; + char error = 0; + int64_t number = str_to_int64(numberStr, numberStr + strlen(numberStr), &error); + EXPECT_EQ(-9223372036854775807, number); + EXPECT_EQ(0, error); + } -TEST(STR_TO_INT64, NegHundred) { + TEST(STR_TO_INT64, Max) { + char numberStr[] = "9223372036854775807"; + char error = 0; + int64_t number = str_to_int64(numberStr, numberStr + strlen(numberStr), &error); + EXPECT_EQ(9223372036854775807, number); + EXPECT_EQ(0, error); + } - char numberStr[] = "-100"; - char error = 0; - int64_t number = str_to_int64(numberStr, numberStr + strlen(numberStr), &error); - EXPECT_EQ(-100, number); - EXPECT_EQ(0, error); -} + TEST(STR_TO_INT64, Zero) { + char numberStr[] = "0"; + char error = 0; + int64_t number = str_to_int64(numberStr, numberStr + strlen(numberStr), &error); + EXPECT_EQ(0, number); + EXPECT_EQ(0, error); + } -TEST(STR_TO_INT64, DummyData_Positive) { + TEST(STR_TO_INT64, Hundred) { + char numberStr[] = "100"; + char error = 0; + int64_t number = str_to_int64(numberStr, numberStr + strlen(numberStr), &error); + EXPECT_EQ(100, number); + EXPECT_EQ(0, error); + } - char numberStr[] = "100b0"; - char error = 0; - int64_t number = str_to_int64(numberStr, numberStr + strlen(numberStr), &error); - EXPECT_EQ(1, error); -} + TEST(STR_TO_INT64, NegHundred) { + char numberStr[] = "-100"; + char error = 0; + int64_t number = str_to_int64(numberStr, numberStr + strlen(numberStr), &error); + EXPECT_EQ(-100, number); + EXPECT_EQ(0, error); + } -TEST(STR_TO_INT64, DummyData_Negative) { + TEST(STR_TO_INT64, DummyData_Positive) { + char numberStr[] = "100b0"; + char error = 0; + int64_t number = str_to_int64(numberStr, numberStr + strlen(numberStr), &error); + EXPECT_EQ(1, error); + } - char numberStr[] = "-1002xx"; - char error = 0; - int64_t number = str_to_int64(numberStr, numberStr + strlen(numberStr), &error); - EXPECT_EQ(1, error); -} + TEST(STR_TO_INT64, DummyData_Negative) { + char numberStr[] = "-1002xx"; + char error = 0; + int64_t number = str_to_int64(numberStr, numberStr + strlen(numberStr), &error); + EXPECT_EQ(1, error); + } } diff --git a/deps/ledger-zxlib/tests/sigutils.cpp b/deps/ledger-zxlib/tests/sigutils.cpp new file mode 100644 index 00000000..1734112b --- /dev/null +++ b/deps/ledger-zxlib/tests/sigutils.cpp @@ -0,0 +1,46 @@ +/******************************************************************************* +* (c) 2020 Zondax GmbH +* +* 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 "sigutils.h" + +TEST(SIGUTILS, convertBasic) { + char inSignatureDERStr[] = "304402206878b5690514437a2342405029426cc2b25b4a03fc396fef845d656cf62bad2c022018610a8d37e3384245176ab49ddbdbe8da4133f661bf5ea7ad4e3d2b912d856f01"; + auto inSignatureDER = std::vector(71); + + auto length = parseHexString(inSignatureDER.data(), inSignatureDER.size(), inSignatureDERStr); + EXPECT_EQ(length, sizeof(inSignatureDERStr) / 2); + + uint8_t R[32]; + uint8_t S[32]; + uint8_t V; + + auto ret = convertDERtoRSV(inSignatureDER.data(), 0, R, S, &V); + EXPECT_EQ(ret, 0); + + char inSignatureDERStr_R[] = "6878b5690514437a2342405029426cc2b25b4a03fc396fef845d656cf62bad2c"; + char inSignatureDERStr_S[] = "18610a8d37e3384245176ab49ddbdbe8da4133f661bf5ea7ad4e3d2b912d856f"; + auto inSignatureDER_R = std::vector(32); + auto inSignatureDER_S = std::vector(32); + parseHexString(inSignatureDER_R.data(), inSignatureDER_R.size(), inSignatureDERStr_R); + parseHexString(inSignatureDER_S.data(), inSignatureDER_S.size(), inSignatureDERStr_S); + + EXPECT_THAT(R, ::testing::ElementsAreArray(inSignatureDER_R)); + EXPECT_THAT(S, ::testing::ElementsAreArray(inSignatureDER_S)); +} diff --git a/deps/nanos-secure-sdk b/deps/nanos-secure-sdk new file mode 160000 index 00000000..1f270694 --- /dev/null +++ b/deps/nanos-secure-sdk @@ -0,0 +1 @@ +Subproject commit 1f2706941b68d897622f75407a868b60eb2be8d7 diff --git a/docs/BUILD.md b/docs/BUILD.md deleted file mode 100644 index c9720494..00000000 --- a/docs/BUILD.md +++ /dev/null @@ -1,5 +0,0 @@ -# Building Cosmos App - Ledger Nano S - -Please refer to the [Ledger-Cosmos](https://github.com/cosmos/ledger-cosmos) for the complete source code, build instructions, etc (unit tests, integration tests, documentation, etc.) - -Up to date instructions are kept [here](https://github.com/cosmos/ledger-cosmos/blob/master/docs/BUILD.md) diff --git a/docs/img/cosmos_app1.png b/docs/img/cosmos_app1.png new file mode 100644 index 0000000000000000000000000000000000000000..d971cef2378c29a47cebdb00726192de552a1af8 GIT binary patch literal 15105 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

%u>jH7fM>JM| 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 literal 0 HcmV?d00001 diff --git a/docs/img/cosmos_app3.png b/docs/img/cosmos_app3.png new file mode 100644 index 0000000000000000000000000000000000000000..4011eeacf6688d28dc7e4dcc020fedc315e067b4 GIT binary patch literal 13197 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 LC8?sPhJODGX_>4h literal 0 HcmV?d00001 diff --git a/docs/img/tendermint_app.png b/docs/img/tendermint_app.png new file mode 100644 index 0000000000000000000000000000000000000000..a21a37b0625ad497d07f502d06e8fef4a52a3a03 GIT binary patch literal 11365 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 literal 0 HcmV?d00001 diff --git a/fuzzing/Makefile b/fuzzing/Makefile new file mode 100644 index 00000000..d4289adb --- /dev/null +++ b/fuzzing/Makefile @@ -0,0 +1,34 @@ +#******************************************************************************* +#* (c) 2019 Zondax GmbH +#* +#* 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. +#******************************************************************************** + +.PHONY: all deps build clean load delete + +MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) + +build: + ${MAKEFILE_DIR}/scripts/build.sh + +run: + ${MAKEFILE_DIR}/scripts/run.sh + +run_slaves: + ${MAKEFILE_DIR}/scripts/run_slaves.sh + +plot: + ${MAKEFILE_DIR}/scripts/plot.sh + +login: + ${MAKEFILE_DIR}/scripts/login.sh diff --git a/fuzzing/fuzzing.md b/fuzzing/fuzzing.md new file mode 100644 index 00000000..7a3782b5 --- /dev/null +++ b/fuzzing/fuzzing.md @@ -0,0 +1,24 @@ +# Fuzzing notes + +# Dependencies + +**Docker CE** + +Please install docker CE. The instructions can be found here: https://docs.docker.com/install/ + +#Run Fuzzers + +There are two options, you install `afl` in your host or you use a docker container. + +If you want to use the docker container. In the `fuzzing` directory: + + - Use `make login` to get a bash session in a preinstalled container. + - Run `make build` to build an instrumented stub + - Run `make run` to start fuzzing + +If you want to run multiple fuzzers: + + - Get another session in the same container: `make login` + - run `make run_slaves` to start 4 more parallel fuzzers + +You may want to configure docker to use more CPUs/cores diff --git a/fuzzing/fuzzingMain.cpp b/fuzzing/fuzzingMain.cpp new file mode 100644 index 00000000..fb0eba60 --- /dev/null +++ b/fuzzing/fuzzingMain.cpp @@ -0,0 +1,60 @@ +/******************************************************************************* +* (c) 2019 Zondax GmbH +* +* 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 +#include +#include +#include +#include "../tests/util/common.h" + +#ifndef __AFL_LOOP +#define __AFL_LOOP(x) (0) +#endif + +/// +/// This file is just a fuzzing stub used by afl +/// + +void parse(std::istream &istream) { + parser_context_t ctx; + parser_error_t err; + + std::string input; + istream >> input; + + err = parser_parse(&ctx, (const uint8_t *) input.c_str(), input.length()); + if (err != parser_ok) + return; + + auto output = dumpUI(&ctx, 40, 40); + + for (const auto &line : output) { + std::cout << line << std::endl; + } +} + +int main(int argc, char **argv) { + if (argc > 1) { + std::ifstream fin; + fin.open(argv[1]); + parse(fin); + } else { + while (__AFL_LOOP(1000)) { + parse(std::cin); + } + } + + return 0; +} diff --git a/fuzzing/inputs/input1.txt b/fuzzing/inputs/input1.txt new file mode 100644 index 00000000..3c8a8da4 --- /dev/null +++ b/fuzzing/inputs/input1.txt @@ -0,0 +1 @@ +{"account_number":"0","chain_id":"test-chain-1","fee":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]},{"inputs":[{"address":"test2","coins":[{"amount":"20","denom":"bitcoin"}]}],"outputs":[{"address":"test3","coins":[{"amount":"50","denom":"ripple"}]}]}],"sequence":"1"} diff --git a/fuzzing/inputs/input2.txt b/fuzzing/inputs/input2.txt new file mode 100644 index 00000000..3a79d59e --- /dev/null +++ b/fuzzing/inputs/input2.txt @@ -0,0 +1 @@ +{"account_number":"6571","chain_id":"cosmoshub-2","fee":{"amount":[{"amount":"5000","denom":"uatom"}],"gas":"200000"},"memo":"Delegated with Ledger from union.market","msgs":[{"type":"cosmos-sdk/MsgDelegate","value":{"amount":{"amount":"1000000","denom":"uatom"},"delegator_address":"cosmos102hty0jv2s29lyc4u0tv97z9v298e24t3vwtpl","validator_address":"cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt9m5s03xfytvz7"}}],"sequence":"1"} diff --git a/fuzzing/inputs/input3.txt b/fuzzing/inputs/input3.txt new file mode 100644 index 00000000..c2292a15 --- /dev/null +++ b/fuzzing/inputs/input3.txt @@ -0,0 +1 @@ +{"account_number":"108","chain_id":"cosmoshub-2","fee":{"amount":[{"amount":"600","denom":"uatom"}],"gas":"200000"},"memo":"","msgs":[{"type":"cosmos-sdk/MsgWithdrawDelegationReward","value":{"delegator_address":"cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh","validator_address":"cosmosvaloper1qwl879nx9t6kef4supyazayf7vjhennyh568ys"}},{"type":"cosmos-sdk/MsgWithdrawDelegationReward","value":{"delegator_address":"cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh","validator_address":"cosmosvaloper1x88j7vp2xnw3zec8ur3g4waxycyz7m0mahdv3p"}},{"type":"cosmos-sdk/MsgWithdrawDelegationReward","value":{"delegator_address":"cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh","validator_address":"cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt9m5s03xfytvz7"}},{"type":"cosmos-sdk/MsgWithdrawDelegationReward","value":{"delegator_address":"cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh","validator_address":"cosmosvaloper1ttfytaf43nkytzp8hkfjfgjc693ky4t3y2n2ku"}},{"type":"cosmos-sdk/MsgWithdrawDelegationReward","value":{"delegator_address":"cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh","validator_address":"cosmosvaloper1wdrypwex63geqswmcy5qynv4w3z3dyef2qmyna"}},{"type":"cosmos-sdk/MsgWithdrawDelegationReward","value":{"delegator_address":"cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh","validator_address":"cosmosvaloper102ruvpv2srmunfffxavttxnhezln6fnc54at8c"}},{"type":"cosmos-sdk/MsgWithdrawDelegationReward","value":{"delegator_address":"cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv"}},{"type":"cosmos-sdk/MsgWithdrawDelegationReward","value":{"delegator_address":"cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh","validator_address":"cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08"}},{"type":"cosmos-sdk/MsgWithdrawDelegationReward","value":{"delegator_address":"cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh","validator_address":"cosmosvaloper1ssm0d433seakyak8kcf93yefhknjleeds4y3em"}},{"type":"cosmos-sdk/MsgWithdrawDelegationReward","value":{"delegator_address":"cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh","validator_address":"cosmosvaloper13sduv92y3xdhy3rpmhakrc3v7t37e7ps9l0kpv"}},{"type":"cosmos-sdk/MsgWithdrawDelegationReward","value":{"delegator_address":"cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh","validator_address":"cosmosvaloper15urq2dtp9qce4fyc85m6upwm9xul3049e02707"}},{"type":"cosmos-sdk/MsgWithdrawDelegationReward","value":{"delegator_address":"cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh","validator_address":"cosmosvaloper14kn0kk33szpwus9nh8n87fjel8djx0y070ymmj"}},{"type":"cosmos-sdk/MsgWithdrawDelegationReward","value":{"delegator_address":"cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh","validator_address":"cosmosvaloper14lultfckehtszvzw4ehu0apvsr77afvyju5zzy"}},{"type":"cosmos-sdk/MsgWithdrawDelegationReward","value":{"delegator_address":"cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh","validator_address":"cosmosvaloper1k9a0cs97vul8w2vwknlfmpez6prv8klv03lv3d"}},{"type":"cosmos-sdk/MsgWithdrawDelegationReward","value":{"delegator_address":"cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh","validator_address":"cosmosvaloper1kj0h4kn4z5xvedu2nd9c4a9a559wvpuvu0h6qn"}},{"type":"cosmos-sdk/MsgWithdrawDelegationReward","value":{"delegator_address":"cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh","validator_address":"cosmosvaloper1hjct6q7npsspsg3dgvzk3sdf89spmlpfdn6m9d"}}],"sequence":"106"} diff --git a/fuzzing/inputs/input4.txt b/fuzzing/inputs/input4.txt new file mode 100644 index 00000000..02b836d4 --- /dev/null +++ b/fuzzing/inputs/input4.txt @@ -0,0 +1 @@ +{"1":[[[[[[[[[[[[{"2":"4"}]]]]]]]]]]]]} diff --git a/fuzzing/inputs/input5.txt b/fuzzing/inputs/input5.txt new file mode 100644 index 00000000..20b331d9 --- /dev/null +++ b/fuzzing/inputs/input5.txt @@ -0,0 +1 @@ +{"account_number":"6571","chain_id":"cosmoshub-2","fee":{"amount":[{"amount":"5000","denom":"uatom"}],"gas":"200000"},"memo":"Delegated with Ledger from union.market","msgs":[{"type":"cosmos-sdk/MsgDelegate","value":{"amount":{"amount":"1000000","denom":"uatom"},"delegator_address":"cosmos102hty0jv2s29lyc4u0tv97z9v298e24t3vwtpl","validator_address":"cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt9m5s03xfytvz7"}}],"sequence":"0"} diff --git a/fuzzing/inputs/input6.txt b/fuzzing/inputs/input6.txt new file mode 100644 index 00000000..300caa88 --- /dev/null +++ b/fuzzing/inputs/input6.txt @@ -0,0 +1 @@ +{"account_number":"2","chain_id":"local-testnet","fee":{"amount":[],"gas":"500000"},"memo":"abc","msgs":[{"description":"test","initial_deposit":[{"amount":"1","denom":"stake"}],"proposal_type":"Text","proposer":"cosmos101234567890abcdefghijklmnopqrstuvwxyz0","title":"test"}],"sequence":"0"} diff --git a/fuzzing/inputs/input7.txt b/fuzzing/inputs/input7.txt new file mode 100644 index 00000000..3a79d59e --- /dev/null +++ b/fuzzing/inputs/input7.txt @@ -0,0 +1 @@ +{"account_number":"6571","chain_id":"cosmoshub-2","fee":{"amount":[{"amount":"5000","denom":"uatom"}],"gas":"200000"},"memo":"Delegated with Ledger from union.market","msgs":[{"type":"cosmos-sdk/MsgDelegate","value":{"amount":{"amount":"1000000","denom":"uatom"},"delegator_address":"cosmos102hty0jv2s29lyc4u0tv97z9v298e24t3vwtpl","validator_address":"cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt9m5s03xfytvz7"}}],"sequence":"1"} diff --git a/fuzzing/inputs/input8.txt b/fuzzing/inputs/input8.txt new file mode 100644 index 00000000..3c8a8da4 --- /dev/null +++ b/fuzzing/inputs/input8.txt @@ -0,0 +1 @@ +{"account_number":"0","chain_id":"test-chain-1","fee":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]},{"inputs":[{"address":"test2","coins":[{"amount":"20","denom":"bitcoin"}]}],"outputs":[{"address":"test3","coins":[{"amount":"50","denom":"ripple"}]}]}],"sequence":"1"} diff --git a/fuzzing/scripts/build.sh b/fuzzing/scripts/build.sh new file mode 100755 index 00000000..20616fff --- /dev/null +++ b/fuzzing/scripts/build.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +#******************************************************************************* +#* (c) 2019 Zondax GmbH +#* +#* 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. +#********************************************************************************/ + +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +BUILDDIR=$SCRIPTDIR/../../cmake-build-fuzz + +# Compile fuzzing stub +rm -rf "$BUILDDIR" +mkdir -p "$BUILDDIR/syncdir" +cd "$BUILDDIR" || exit + +cmake -DCMAKE_CXX_COMPILER=afl-clang-fast++ -DCMAKE_C_COMPILER=afl-clang-fast .. +make clean +make fuzzing_stub diff --git a/fuzzing/scripts/login.sh b/fuzzing/scripts/login.sh new file mode 100755 index 00000000..82760fa4 --- /dev/null +++ b/fuzzing/scripts/login.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +#******************************************************************************* +#* (c) 2019 Zondax GmbH +#* +#* 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. +#********************************************************************************/ +CONTAINER_NAME='zx-fuzzer-container' +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +DOCKER_IMAGE=zondax/docker-fuzzing:latest +CID=$(docker ps -q -f status=running -f name=^/${CONTAINER_NAME}$) + +echo "$SCRIPTDIR" + +if [ ! "${CID}" ]; then + docker run -it --rm --name ${CONTAINER_NAME} \ + -u "$(id -u)" -v ${SCRIPTDIR}/../../:/project \ + ${DOCKER_IMAGE} +else + docker exec -it ${CONTAINER_NAME} /bin/bash +fi diff --git a/fuzzing/scripts/plot.sh b/fuzzing/scripts/plot.sh new file mode 100755 index 00000000..f107b9fc --- /dev/null +++ b/fuzzing/scripts/plot.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +#******************************************************************************* +#* (c) 2019 Zondax GmbH +#* +#* 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. +#********************************************************************************/ + +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +BUILDDIR=$SCRIPTDIR/../../cmake-build-fuzz + +afl-plot "$BUILDDIR/syncdir/master" "$BUILDDIR/plots" diff --git a/fuzzing/scripts/run.sh b/fuzzing/scripts/run.sh new file mode 100755 index 00000000..3fabbf52 --- /dev/null +++ b/fuzzing/scripts/run.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +BUILDDIR=$SCRIPTDIR/../../cmake-build-fuzz +INPUTS=$SCRIPTDIR/../inputs + +echo "$BUILDDIR" +AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 afl-fuzz -t 50 -i "$INPUTS" -o "$BUILDDIR/syncdir" -M main -- "$BUILDDIR/fuzzing_stub" + diff --git a/fuzzing/scripts/run_slaves.sh b/fuzzing/scripts/run_slaves.sh new file mode 100755 index 00000000..ab8a2e7f --- /dev/null +++ b/fuzzing/scripts/run_slaves.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +BUILDDIR=$SCRIPTDIR/../../cmake-build-fuzz +INPUTS=$SCRIPTDIR/../inputs + +AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 afl-fuzz -t 50 -i "$INPUTS" -o "$BUILDDIR/syncdir" -S fuzzer2 -- "$BUILDDIR/fuzzing_stub" & +AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 afl-fuzz -t 50 -i "$INPUTS" -o "$BUILDDIR/syncdir" -S fuzzer3 -- "$BUILDDIR/fuzzing_stub" & +AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 afl-fuzz -t 50 -i "$INPUTS" -o "$BUILDDIR/syncdir" -S fuzzer1 -- "$BUILDDIR/fuzzing_stub" & +AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 afl-fuzz -t 50 -i "$INPUTS" -o "$BUILDDIR/syncdir" -S fuzzer4 -- "$BUILDDIR/fuzzing_stu"b & diff --git a/src/lib/json/tx_display.c b/src/lib/json/tx_display.c deleted file mode 100644 index b7f6da33..00000000 --- a/src/lib/json/tx_display.c +++ /dev/null @@ -1,262 +0,0 @@ -/******************************************************************************* -* (c) 2018, 2019 ZondaX GmbH -* -* 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 "tx_display.h" -#include "tx_parser.h" -#include "json/json_parser.h" -#include "lib/parser_impl.h" -#include - -#define NUM_REQUIRED_ROOT_PAGES 6 - -// Required pages -const char *get_required_root_item(uint8_t i) { - switch (i) { - case 0: - return "chain_id"; - case 1: - return "account_number"; - case 2: - return "sequence"; - case 3: - return "fee"; - case 4: - return "memo"; - case 5: - return "msgs"; - default: - return "?"; - } -} - -static const uint16_t root_max_level[NUM_REQUIRED_ROOT_PAGES] = { - 2, // "chain_id", - 2, // "account_number", - 2, // "sequence", - 1, // "fee", - 2, // "memo" - 2, // "msgs" -}; - -typedef struct { - int16_t numItems; - int16_t subroot_start_token[NUM_REQUIRED_ROOT_PAGES]; - uint8_t num_subpages[NUM_REQUIRED_ROOT_PAGES]; -} display_cache_t; - -display_cache_t display_cache; - -void _indexRootFields() { - if (parser_tx_obj.cache_valid) { - return; - } - - // Clear cache - MEMZERO(&display_cache, sizeof(display_cache_t)); - - // Calculate pages - for (int8_t idx = 0; idx < NUM_REQUIRED_ROOT_PAGES; idx++) { - const int16_t subroot_token_idx = object_get_value(&parser_tx_obj.json, - ROOT_TOKEN_INDEX, - get_required_root_item(idx)); - - if (subroot_token_idx < 0) { - break; - } - - display_cache.num_subpages[idx] = 0; - display_cache.subroot_start_token[idx] = subroot_token_idx; - - char tmp_key[2]; - char tmp_val[2]; - INIT_QUERY_CONTEXT(tmp_key, sizeof(tmp_key), - tmp_val, sizeof(tmp_val), - 0, root_max_level[idx]) - - STRNCPY_S(parser_tx_obj.query.out_key, - get_required_root_item(idx), - parser_tx_obj.query.out_key_len) - - parser_tx_obj.query.max_depth = MAX_RECURSION_DEPTH; - parser_tx_obj.query.item_index = 0; - - parser_error_t err = parser_ok; - while (err == parser_ok) { - parser_tx_obj.query.item_index_current = 0; - uint16_t dummy; - err = tx_traverse_find(subroot_token_idx, &dummy); - if (err == parser_ok) { - display_cache.num_subpages[idx]++; - parser_tx_obj.query.item_index++; - } - } - - display_cache.numItems += display_cache.num_subpages[idx]; - - if (display_cache.num_subpages[idx] == 0) { - break; - } - } - - parser_tx_obj.cache_valid = 1; -} - -int16_t tx_display_numItems() { - _indexRootFields(); - return display_cache.numItems; -} - -// This function assumes that the tx_ctx has been set properly -parser_error_t tx_display_set_query(uint16_t displayIdx, uint16_t *outStartToken) { - _indexRootFields(); - - if (displayIdx < 0 || displayIdx >= display_cache.numItems) { - return parser_display_idx_out_of_range; - } - - parser_tx_obj.query.item_index = 0; - uint16_t root_index = 0; - - for (uint16_t i = 0; i < displayIdx; i++) { - parser_tx_obj.query.item_index++; - if (parser_tx_obj.query.item_index >= display_cache.num_subpages[root_index]) { - parser_tx_obj.query.item_index = 0; - root_index++; - } - } - - parser_tx_obj.query.item_index_root = root_index; - parser_tx_obj.query.item_index_current = 0; - parser_tx_obj.query.max_level = root_max_level[root_index]; - parser_tx_obj.query.max_depth = MAX_RECURSION_DEPTH; - - *outStartToken = display_cache.subroot_start_token[parser_tx_obj.query.item_index_root]; - - return parser_ok; -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// - -#define NUM_KEY_SUBSTITUTIONS 29 -#define NUM_VALUE_SUBSTITUTIONS 8 - -typedef struct { - char str1[50]; - char str2[50]; -} key_subst_t; - -static const key_subst_t key_substitutions[NUM_KEY_SUBSTITUTIONS] = { - {"chain_id", "Chain ID"}, - {"account_number", "Account"}, - {"sequence", "Sequence"}, - {"memo", "Memo"}, - {"fee/amount", "Fee"}, - {"fee/gas", "Gas"}, - {"msgs/type", "Type"}, - - // FIXME: Are these obsolete?? multisend? - {"msgs/inputs/address", "Source Address"}, - {"msgs/inputs/coins", "Source Coins"}, - {"msgs/outputs/address", "Dest Address"}, - {"msgs/outputs/coins", "Dest Coins"}, - - // MsgSend - {"msgs/value/from_address", "From"}, - {"msgs/value/to_address", "To"}, - {"msgs/value/amount", "Amount"}, - - // MsgDelegate - {"msgs/value/delegator_address", "Delegator"}, - {"msgs/value/validator_address", "Validator"}, - - // MsgUndelegate -// {"msgs/value/delegator_address", "Delegator"}, -// {"msgs/value/validator_address", "Validator"}, - - // MsgBeginRedelegate -// {"msgs/value/delegator_address", "Delegator"}, - {"msgs/value/validator_src_address", "Validator Source"}, - {"msgs/value/validator_dst_address", "Validator Dest"}, - - // MsgSubmitProposal - {"msgs/value/description", "Description"}, - {"msgs/value/initial_deposit/amount", "Deposit Amount"}, - {"msgs/value/initial_deposit/denom", "Deposit Denom"}, - {"msgs/value/proposal_type", "Proposal"}, - {"msgs/value/proposer", "Proposer"}, - {"msgs/value/title", "Title"}, - - // MsgDeposit - {"msgs/value/depositer", "Sender"}, - {"msgs/value/proposal_id", "Proposal ID"}, - {"msgs/value/amount", "Amount"}, - - // MsgVote - {"msgs/value/voter", "Description"}, -// {"msgs/value/proposal_id", "Proposal ID"}, - {"msgs/value/option", "Option"}, - - // MsgWithdrawDelegationReward -// {"msgs/value/delegator_address", "Delegator"}, // duplicated -// {"msgs/value/validator_address", "Validator"}, // duplicated -}; - -static const key_subst_t value_substitutions[NUM_VALUE_SUBSTITUTIONS] = { - {"cosmos-sdk/MsgSend", "Send"}, - {"cosmos-sdk/MsgDelegate", "Delegate"}, - {"cosmos-sdk/MsgUndelegate", "Undelegate"}, - {"cosmos-sdk/MsgBeginRedelegate", "Redelegate"}, - {"cosmos-sdk/MsgSubmitProposal", "Propose"}, - {"cosmos-sdk/MsgDeposit", "Deposit"}, - {"cosmos-sdk/MsgVote", "Vote"}, - {"cosmos-sdk/MsgWithdrawDelegationReward", "Withdraw Reward"}, -}; - -void tx_display_make_friendly() { - _indexRootFields(); - - // post process keys - for (int8_t i = 0; i < NUM_KEY_SUBSTITUTIONS; i++) { - if (!strcmp(parser_tx_obj.query.out_key, key_substitutions[i].str1)) { - STRNCPY_S(parser_tx_obj.query.out_key, - key_substitutions[i].str2, - parser_tx_obj.query.out_key_len) - break; - } - } - - for (int8_t i = 0; i < NUM_VALUE_SUBSTITUTIONS; i++) { - if (!strcmp(parser_tx_obj.query.out_val, value_substitutions[i].str1)) { - STRNCPY_S(parser_tx_obj.query.out_val, - value_substitutions[i].str2, - parser_tx_obj.query.out_val_len) - break; - } - } -} - diff --git a/tests/json_parser.cpp b/tests/json_parser.cpp new file mode 100644 index 00000000..e983c50a --- /dev/null +++ b/tests/json_parser.cpp @@ -0,0 +1,355 @@ +/******************************************************************************* +* (c) 2018 Zondax GmbH +* +* 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 "gtest/gtest.h" +#include "util/common.h" +#include +#include + +namespace { + TEST(JsonParserTest, Empty) { + parsed_json_t parserData = {false}; + JSON_PARSE(&parserData, ""); + + EXPECT_FALSE(parserData.isValid); + EXPECT_EQ(0, parserData.numberOfTokens); + } + + TEST(JsonParserTest, SinglePrimitive) { + parsed_json_t parserData = {false}; + JSON_PARSE(&parserData, "EMPTY"); + + EXPECT_TRUE(parserData.isValid); + EXPECT_EQ(1, parserData.numberOfTokens); + EXPECT_TRUE(parserData.tokens[0].type == jsmntype_t::JSMN_PRIMITIVE); + } + + TEST(JsonParserTest, KeyValuePrimitives) { + parsed_json_t parserData = {false}; + JSON_PARSE(&parserData, "KEY : VALUE"); + + EXPECT_TRUE(parserData.isValid); + EXPECT_EQ(2, parserData.numberOfTokens); + EXPECT_TRUE(parserData.tokens[0].type == jsmntype_t::JSMN_PRIMITIVE); + EXPECT_TRUE(parserData.tokens[1].type == jsmntype_t::JSMN_PRIMITIVE); + } + + TEST(JsonParserTest, SingleString) { + parsed_json_t parserData = {false}; + JSON_PARSE(&parserData, "\"EMPTY\""); + + EXPECT_TRUE(parserData.isValid); + EXPECT_EQ(1, parserData.numberOfTokens); + EXPECT_TRUE(parserData.tokens[0].type == jsmntype_t::JSMN_STRING); + } + + TEST(JsonParserTest, KeyValueStrings) { + parsed_json_t parserData = {false}; + JSON_PARSE(&parserData, R"("KEY" : "VALUE")"); + + EXPECT_TRUE(parserData.isValid); + EXPECT_EQ(2, parserData.numberOfTokens); + EXPECT_TRUE(parserData.tokens[0].type == jsmntype_t::JSMN_STRING); + EXPECT_TRUE(parserData.tokens[1].type == jsmntype_t::JSMN_STRING); + } + + TEST(JsonParserTest, SimpleArray) { + parsed_json_t parserData = {false}; + JSON_PARSE(&parserData, "LIST : [1, 2, 3, 4]"); + + EXPECT_TRUE(parserData.isValid); + EXPECT_EQ(6, parserData.numberOfTokens); + EXPECT_TRUE(parserData.tokens[0].type == jsmntype_t::JSMN_PRIMITIVE); + EXPECT_TRUE(parserData.tokens[1].type == jsmntype_t::JSMN_ARRAY); + EXPECT_TRUE(parserData.tokens[2].type == jsmntype_t::JSMN_PRIMITIVE); + EXPECT_TRUE(parserData.tokens[3].type == jsmntype_t::JSMN_PRIMITIVE); + EXPECT_TRUE(parserData.tokens[4].type == jsmntype_t::JSMN_PRIMITIVE); + EXPECT_TRUE(parserData.tokens[5].type == jsmntype_t::JSMN_PRIMITIVE); + } + + TEST(JsonParserTest, MixedArray) { + parsed_json_t parserData = {false}; + JSON_PARSE(&parserData, R"(LIST : [1, "Text", 3, "Another text"])"); + + EXPECT_TRUE(parserData.isValid); + EXPECT_EQ(6, parserData.numberOfTokens); + EXPECT_TRUE(parserData.tokens[0].type == jsmntype_t::JSMN_PRIMITIVE); + EXPECT_TRUE(parserData.tokens[1].type == jsmntype_t::JSMN_ARRAY); + EXPECT_TRUE(parserData.tokens[2].type == jsmntype_t::JSMN_PRIMITIVE); + EXPECT_TRUE(parserData.tokens[3].type == jsmntype_t::JSMN_STRING); + EXPECT_TRUE(parserData.tokens[4].type == jsmntype_t::JSMN_PRIMITIVE); + EXPECT_TRUE(parserData.tokens[5].type == jsmntype_t::JSMN_STRING); + } + + TEST(JsonParserTest, SimpleObject) { + parsed_json_t parserData = {false}; + JSON_PARSE(&parserData, "vote : " + "{ " + "\"key\" : \"value\", " + "\"another key\" : { " + "\"inner key\" : \"inner value\", " + "\"total\":123 }" + "}"); + + EXPECT_TRUE(parserData.isValid); + EXPECT_EQ(10, parserData.numberOfTokens); + EXPECT_TRUE(parserData.tokens[0].type == jsmntype_t::JSMN_PRIMITIVE); + EXPECT_TRUE(parserData.tokens[1].type == jsmntype_t::JSMN_OBJECT); + EXPECT_TRUE(parserData.tokens[2].type == jsmntype_t::JSMN_STRING); + EXPECT_TRUE(parserData.tokens[3].type == jsmntype_t::JSMN_STRING); + EXPECT_TRUE(parserData.tokens[4].type == jsmntype_t::JSMN_STRING); + EXPECT_TRUE(parserData.tokens[5].type == jsmntype_t::JSMN_OBJECT); + EXPECT_TRUE(parserData.tokens[6].type == jsmntype_t::JSMN_STRING); + EXPECT_TRUE(parserData.tokens[7].type == jsmntype_t::JSMN_STRING); + EXPECT_TRUE(parserData.tokens[8].type == jsmntype_t::JSMN_STRING); + EXPECT_TRUE(parserData.tokens[9].type == jsmntype_t::JSMN_PRIMITIVE); + } + + TEST(JsonParserTest, ArrayElementCount_objects) { + auto transaction = + R"({"array":[{"amount":5,"denom":"photon"}, {"amount":5,"denom":"photon"}, {"amount":5,"denom":"photon"}]})"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + EXPECT_EQ(array_get_element_count(2, &parsed_json), 3) << "Wrong number of array elements"; + } + + TEST(JsonParserTest, ArrayElementCount_primitives) { + auto transaction = R"({"array":[1, 2, 3, 4, 5, 6, 7]})"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + EXPECT_EQ(array_get_element_count(2, &parsed_json), 7) << "Wrong number of array elements"; + } + + TEST(JsonParserTest, ArrayElementCount_strings) { + auto transaction = R"({"array":["hello", "there"]})"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + EXPECT_EQ(array_get_element_count(2, &parsed_json), 2) << "Wrong number of array elements"; + } + + TEST(JsonParserTest, ArrayElementCount_empty) { + auto transaction = R"({"array":[])"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + EXPECT_EQ(array_get_element_count(2, &parsed_json), 0) << "Wrong number of array elements"; + } + + TEST(JsonParserTest, ArrayElementGet_objects) { + auto transaction = + R"({"array":[{"amount":5,"denom":"photon"}, {"amount":5,"denom":"photon"}, {"amount":5,"denom":"photon"}]})"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + int token_index = array_get_nth_element(2, 1, &parsed_json); + EXPECT_EQ(token_index, 8) << "Wrong token index returned"; + EXPECT_EQ(parsed_json.tokens[token_index].type, JSMN_OBJECT) << "Wrong token type returned"; + } + + TEST(JsonParserTest, ArrayElementGet_primitives) { + auto transaction = R"({"array":[1, 2, 3, 4, 5, 6, 7]})"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + int token_index = array_get_nth_element(2, 5, &parsed_json); + EXPECT_EQ(token_index, 8) << "Wrong token index returned"; + EXPECT_EQ(parsed_json.tokens[token_index].type, JSMN_PRIMITIVE) << "Wrong token type returned"; + } + + TEST(TxValidationTest, ArrayElementGet_strings) { + auto transaction = R"({"array":["hello", "there"]})"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + int token_index = array_get_nth_element(2, 0, &parsed_json); + EXPECT_EQ(token_index, 3) << "Wrong token index returned"; + EXPECT_EQ(parsed_json.tokens[token_index].type, JSMN_STRING) << "Wrong token type returned"; + } + + TEST(TxValidationTest, ArrayElementGet_empty) { + auto transaction = R"({"array":[])"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + int token_index = array_get_nth_element(2, 0, &parsed_json); + EXPECT_EQ(token_index, -1) << "Token index should be invalid (not found)."; + } + + TEST(TxValidationTest, ArrayElementGet_out_of_bounds_negative) { + auto transaction = R"({"array":["hello", "there"])"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + int token_index = array_get_nth_element(2, -1, &parsed_json); + EXPECT_EQ(token_index, -1) << "Token index should be invalid (not found)."; + } + + TEST(TxValidationTest, ArrayElementGet_out_of_bounds) { + auto transaction = R"({"array":["hello", "there"])"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + int token_index = array_get_nth_element(2, 3, &parsed_json); + EXPECT_EQ(token_index, -1) << "Token index should be invalid (not found)."; + } + + TEST(TxValidationTest, ObjectElementCount_primitives) { + auto transaction = R"({"age":36, "height":185, "year":1981})"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + EXPECT_EQ(object_get_element_count(0, &parsed_json), 3) << "Wrong number of object elements"; + } + + TEST(TxValidationTest, ObjectElementCount_string) { + auto transaction = R"({"age":"36", "height":"185", "year":"1981", "month":"july"})"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + EXPECT_EQ(object_get_element_count(0, &parsed_json), 4) << "Wrong number of object elements"; + } + + TEST(TxValidationTest, ObjectElementCount_array) { + auto transaction = R"({ "ages":[36, 31, 10, 2], + "heights":[185, 164, 154, 132], + "years":[1981, 1985, 2008, 2016], + "months":["july", "august", "february", "july"]})"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + EXPECT_EQ(object_get_element_count(0, &parsed_json), 4) << "Wrong number of object elements"; + } + + TEST(TxValidationTest, ObjectElementCount_object) { + auto transaction = R"({"person1":{"age":36, "height":185, "year":1981}, + "person2":{"age":36, "height":185, "year":1981}, + "person3":{"age":36, "height":185, "year":1981}})"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + EXPECT_EQ(object_get_element_count(0, &parsed_json), 3) << "Wrong number of object elements"; + } + + TEST(TxValidationTest, ObjectElementCount_deep) { + auto transaction = R"({"person1":{"age":{"age":36, "height":185, "year":1981}, "height":{"age":36, "height":185, "year":1981}, "year":1981}, + "person2":{"age":{"age":36, "height":185, "year":1981}, "height":{"age":36, "height":185, "year":1981}, "year":1981}, + "person3":{"age":{"age":36, "height":185, "year":1981}, "height":{"age":36, "height":185, "year":1981}, "year":1981}})"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + EXPECT_EQ(object_get_element_count(0, &parsed_json), 3) << "Wrong number of object elements"; + } + + TEST(TxValidationTest, ObjectElementGet_primitives) { + auto transaction = R"({"age":36, "height":185, "year":1981})"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + int token_index = object_get_nth_key(0, 0, &parsed_json); + EXPECT_EQ(token_index, 1) << "Wrong token index"; + EXPECT_EQ(parsed_json.tokens[token_index].type, JSMN_STRING) << "Wrong token type returned"; + EXPECT_EQ(memcmp(transaction + parsed_json.tokens[token_index].start, "age", strlen("age")), 0) + << "Wrong key returned"; + } + + TEST(TxValidationTest, ObjectElementGet_string) { + auto transaction = R"({"age":"36", "height":"185", "year":"1981", "month":"july"})"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + int token_index = object_get_nth_value(0, 3, &parsed_json); + EXPECT_EQ(token_index, 8) << "Wrong token index"; + EXPECT_EQ(parsed_json.tokens[token_index].type, JSMN_STRING) << "Wrong token type returned"; + EXPECT_EQ(memcmp(transaction + parsed_json.tokens[token_index].start, "july", strlen("july")), 0) + << "Wrong key returned"; + } + + TEST(TxValidationTest, ObjectElementGet_out_of_bounds_negative) { + auto transaction = R"({"age":36, "height":185, "year":1981})"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + int token_index = object_get_nth_key(0, -1, &parsed_json); + EXPECT_EQ(token_index, -1) << "Wrong token index, should be invalid"; + } + + TEST(TxValidationTest, ObjectElementGet_out_of_bounds) { + auto transaction = R"({"age":36, "height":185, "year":1981})"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + int token_index = object_get_nth_key(0, 5, &parsed_json); + EXPECT_EQ(token_index, -1) << "Wrong token index, should be invalid"; + } + + TEST(TxValidationTest, ObjectElementGet_array) { + auto transaction = R"({ "ages":[36, 31, 10, 2], + "heights":[185, 164, 154, 132], + "years":[1981, 1985, 2008, 2016, 2022], + "months":["july", "august", "february", "july"]})"; + + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + int token_index = object_get_value(&parsed_json, 0, "years"); + + EXPECT_EQ(token_index, 14) << "Wrong token index"; + EXPECT_EQ(parsed_json.tokens[token_index].type, JSMN_ARRAY) << "Wrong token type returned"; + EXPECT_EQ(array_get_element_count(token_index, &parsed_json), 5) << "Wrong number of array elements"; + } + + TEST(TxValidationTest, ObjectGetValueCorrectFormat) { + auto transaction = + R"({"account_number":"0","chain_id":"test-chain-1","fee":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}],"sequence":"1"})"; + parsed_json_t parsed_json; + JSON_PARSE(&parsed_json, transaction); + + int token_index = object_get_value(&parsed_json, 0, "alt_bytes"); + EXPECT_EQ(token_index, -1) << "Wrong token index"; // alt_bytes should not be found + token_index = object_get_value(&parsed_json, 0, "account_number"); + EXPECT_EQ(token_index, 2) << "Wrong token index"; // alt_bytes should not be found + token_index = object_get_value(&parsed_json, 0, "chain_id"); + EXPECT_EQ(token_index, 4) << "Wrong token index"; + token_index = object_get_value(&parsed_json, 0, "fee"); + EXPECT_EQ(token_index, 6) << "Wrong token index"; + token_index = object_get_value(&parsed_json, 0, "msgs"); + EXPECT_EQ(token_index, 19) << "Wrong token index"; + token_index = object_get_value(&parsed_json, 0, "sequence"); + EXPECT_EQ(token_index, 46) << "Wrong token index"; + } +} diff --git a/tests/testcases/manual.json b/tests/testcases/manual.json new file mode 100644 index 00000000..8efe5d90 --- /dev/null +++ b/tests/testcases/manual.json @@ -0,0 +1,859 @@ +[ + { + "name": "empty", + "tx": "", + "parsingErr": "No error", + "validationErr": "JSON Missing chain_id", + "expected": [] + }, + { + "name": "minimal", + "tx": { + "account_number": "V1", + "chain_id": "V2", + "fee": { + "amount": [ + { + "amount": "b" + }, + { + "c": "d" + }, + { + "x": "y" + } + ], + "gas": "V3" + }, + "memo": "V4", + "msgs": [], + "sequence": "V5" + }, + "parsingErr": "No error", + "validationErr": "Unexpected field", + "expected": [ + "0 | Chain ID : V2", + "1 | Account : V1", + "2 | Sequence : V5", + "3 | fee/amount : Unexpected field", + "4 | Gas : V3", + "5 | Memo : V4" + ] + }, + { + "name": "multipleMessages", + "tx": { + "account_number": "V1", + "chain_id": "V2", + "fee": { + "amount": [ + { + "amount": "b", + "denom": "d" + } + ], + "gas": "V3" + }, + "memo": "V4", + "msgs": [ + { + "m1": "z1" + }, + { + "m2": "z2" + }, + { + "m3": "z3" + } + ], + "sequence": "V5" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Chain ID : V2", + "1 | Account : V1", + "2 | Sequence : V5", + "3 | Fee : b d", + "4 | Gas : V3", + "5 | Memo : V4", + "6 | msgs/m1 : z1", + "7 | msgs/m2 : z2", + "8 | msgs/m3 : z3" + ] + }, + { + "name": "completeTransfer", + "tx": { + "account_number": "0", + "chain_id": "test-chain-1", + "fee": { + "amount": [ + { + "amount": "5", + "denom": "photon" + } + ], + "gas": "10000" + }, + "memo": "testmemo", + "msgs": [ + { + "inputs": [ + { + "address": "cosmosaccaddr1d9h8qat5e4ehc5", + "coins": [ + { + "amount": "10", + "denom": "atom" + } + ] + } + ], + "outputs": [ + { + "address": "cosmosaccaddr1da6hgur4wse3jx32", + "coins": [ + { + "amount": "10", + "denom": "atom" + } + ] + } + ] + } + ], + "sequence": "1" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Chain ID : test-chain-1", + "1 | Account : 0", + "2 | Sequence : 1", + "3 | Fee : 5 photon", + "4 | Gas : 10000", + "5 | Memo : testmemo", + "6 | Source Address : cosmosaccaddr1d9h8qat5e4ehc5", + "7 | Source Coins : 10 atom", + "8 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", + "9 | Dest Coins : 10 atom" + ] + }, + { + "name": "completeTransferNoMemo", + "tx": { + "account_number": "0", + "chain_id": "test-chain-1", + "fee": { + "amount": [ + { + "amount": "5", + "denom": "photon" + } + ], + "gas": "10000" + }, + "msgs": [ + { + "inputs": [ + { + "address": "cosmosaccaddr1d9h8qat5e4ehc5", + "coins": [ + { + "amount": "10", + "denom": "atom" + } + ] + } + ], + "outputs": [ + { + "address": "cosmosaccaddr1da6hgur4wse3jx32", + "coins": [ + { + "amount": "10", + "denom": "atom" + } + ] + } + ] + } + ], + "sequence": "1" + }, + "parsingErr": "No error", + "validationErr": "JSON Missing memo", + "expected": [ + "0 | Chain ID : test-chain-1", + "1 | Account : 0", + "2 | Sequence : 1", + "3 | Fee : 5 photon", + "4 | Gas : 10000", + "5 | Source Address : cosmosaccaddr1d9h8qat5e4ehc5", + "6 | Source Coins : 10 atom", + "7 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", + "8 | Dest Coins : 10 atom" + ] + }, + { + "name": "completeTransferEmptyMemo", + "tx": { + "account_number": "0", + "chain_id": "test-chain-1", + "fee": { + "amount": [ + { + "amount": "5", + "denom": "photon" + } + ], + "gas": "10000" + }, + "memo": "", + "msgs": [ + { + "inputs": [ + { + "address": "cosmosaccaddr1d9h8qat5e4ehc5", + "coins": [ + { + "amount": "10", + "denom": "atom" + } + ] + } + ], + "outputs": [ + { + "address": "cosmosaccaddr1da6hgur4wse3jx32", + "coins": [ + { + "amount": "10", + "denom": "atom" + } + ] + } + ] + } + ], + "sequence": "1" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Chain ID : test-chain-1", + "1 | Account : 0", + "2 | Sequence : 1", + "3 | Fee : 5 photon", + "4 | Gas : 10000", + "5 | Source Address : cosmosaccaddr1d9h8qat5e4ehc5", + "6 | Source Coins : 10 atom", + "7 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", + "8 | Dest Coins : 10 atom" + ] + }, + { + "name": "multipleTransfers", + "tx": { + "account_number": "0", + "chain_id": "test-chain-1", + "fee": { + "amount": [ + { + "amount": "5", + "denom": "photon" + } + ], + "gas": "10000" + }, + "memo": "testmemo", + "msgs": [ + { + "inputs": [ + { + "address": "cosmosaccaddr1d9h8qat5e4ehc5", + "coins": [ + { + "amount": "10", + "denom": "atom" + } + ] + } + ], + "outputs": [ + { + "address": "cosmosaccaddr1da6hgur4wse3jx32", + "coins": [ + { + "amount": "10", + "denom": "atom" + } + ] + } + ] + }, + { + "inputs": [ + { + "address": "test2", + "coins": [ + { + "amount": "20", + "denom": "bitcoin" + } + ] + } + ], + "outputs": [ + { + "address": "test3", + "coins": [ + { + "amount": "50", + "denom": "ripple" + } + ] + } + ] + } + ], + "sequence": "1" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Chain ID : test-chain-1", + "1 | Account : 0", + "2 | Sequence : 1", + "3 | Fee : 5 photon", + "4 | Gas : 10000", + "5 | Memo : testmemo", + "6 | Source Address : cosmosaccaddr1d9h8qat5e4ehc5", + "7 | Source Coins : 10 atom", + "8 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", + "9 | Dest Coins : 10 atom", + "10 | Source Address : test2", + "11 | Source Coins : 20 bitcoin", + "12 | Dest Address : test3", + "13 | Dest Coins : 50 ripple" + ] + }, + { + "name": "delegation", + "tx": { + "account_number": "6571", + "chain_id": "cosmoshub-2", + "fee": { + "amount": [ + { + "amount": "5000", + "denom": "uatom" + } + ], + "gas": "200000" + }, + "memo": "Delegated with Ledger from union.market", + "msgs": [ + { + "type": "cosmos-sdk/MsgDelegate", + "value": { + "amount": { + "amount": "1000000", + "denom": "uatom" + }, + "delegator_address": "cosmos102hty0jv2s29lyc4u0tv97z9v298e24t3vwtpl", + "validator_address": "cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt9m5s03xfytvz7" + } + } + ], + "sequence": "1" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Chain ID : cosmoshub-2", + "1 | Account : 6571", + "2 | Sequence : 1", + "3 | Fee : 5000 uatom", + "4 | Gas : 200000", + "5 | Memo : Delegated with Ledger from union.market", + "6 | Type : Delegate", + "7 | Amount : 1000000 uatom", + "8 | Delegator [1/2] : cosmos102hty0jv2s29lyc4u0tv97z9v298e24t", + "8 | Delegator [2/2] : 3vwtpl", + "9 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", + "9 | Validator [2/2] : 9m5s03xfytvz7" + ] + }, + { + "name": "proposal", + "tx": { + "account_number": "2", + "chain_id": "local-testnet", + "fee": { + "amount": [], + "gas": "500000" + }, + "memo": "abc", + "msgs": [ + { + "description": "test", + "initial_deposit": [ + { + "amount": "1", + "denom": "stake" + } + ], + "proposal_type": "Text", + "proposer": "cosmos101234567890abcdefghijklmnopqrstuvwxyz0", + "title": "test" + } + ], + "sequence": "0" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Chain ID : local-testnet", + "1 | Account : 2", + "2 | Sequence : 0", + "3 | Fee : Empty", + "4 | Gas : 500000", + "5 | Memo : abc", + "6 | msgs/description : test", + "7 | msgs/initial_deposit/amount : 1", + "8 | msgs/initial_deposit/denom : stake", + "9 | msgs/proposal_type : Text", + "10 | msgs/proposer [1/2] : cosmos101234567890abcdefghijklmnopqrstu", + "10 | msgs/proposer [2/2] : vwxyz0", + "11 | msgs/title : test" + ] + }, + { + "name": "delegation2", + "tx": { + "account_number": "6571", + "chain_id": "cosmoshub-2", + "fee": { + "amount": [ + { + "amount": "5000", + "denom": "uatom" + } + ], + "gas": "200000" + }, + "memo": "Delegated with Ledger from union.market", + "msgs": [ + { + "type": "cosmos-sdk/MsgDelegate", + "value": { + "amount": { + "amount": "1000000", + "denom": "uatom" + }, + "delegator_address": "cosmos102hty0jv2s29lyc4u0tv97z9v298e24t3vwtpl", + "validator_address": "cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt9m5s03xfytvz7" + } + } + ], + "sequence": "0" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Chain ID : cosmoshub-2", + "1 | Account : 6571", + "2 | Sequence : 0", + "3 | Fee : 5000 uatom", + "4 | Gas : 200000", + "5 | Memo : Delegated with Ledger from union.market", + "6 | Type : Delegate", + "7 | Amount : 1000000 uatom", + "8 | Delegator [1/2] : cosmos102hty0jv2s29lyc4u0tv97z9v298e24t", + "8 | Delegator [2/2] : 3vwtpl", + "9 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", + "9 | Validator [2/2] : 9m5s03xfytvz7" + ] + }, + { + "name": "badcase", + "tx": { + "chain_id": "1234567890AB", + "fee": "1234" + }, + "parsingErr": "No error", + "validationErr": "JSON Missing sequence", + "expected": [ + "0 | Chain ID : 1234567890AB", + "1 | fee : 1234" + ] + }, + { + "name": "badcase2", + "tx": { + "account_number": "6571", + "chain_id": "cosmoshub-2", + "fee": { + "amount": [ + { + "amount": "5000", + "denom": "uatom" + } + ], + "gas": "200000" + }, + "memo": "Delegated with Ledger from union.market", + "msgs": [ + { + "type": "cosmos-sdk/MsgDelegate", + "value": { + "amount": { + "amount": "1000000", + "denom": "uatom" + }, + "delegator_address": "cosmos102hty0jv2s29lyc4u0tv97z9v298e24t3vwtpl", + "validator_address": "cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt9m5s03xfytvz7" + } + } + ], + "sequence": "0" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Chain ID : cosmoshub-2", + "1 | Account : 6571", + "2 | Sequence : 0", + "3 | Fee : 5000 uatom", + "4 | Gas : 200000", + "5 | Memo : Delegated with Ledger from union.market", + "6 | Type : Delegate", + "7 | Amount : 1000000 uatom", + "8 | Delegator [1/2] : cosmos102hty0jv2s29lyc4u0tv97z9v298e24t", + "8 | Delegator [2/2] : 3vwtpl", + "9 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", + "9 | Validator [2/2] : 9m5s03xfytvz7" + ] + }, + { + "name": "oldStackOverflow", + "tx": { + "1": [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + { + "2": "4" + } + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + }, + "parsingErr": "No error", + "validationErr": "JSON Missing chain_id", + "expected": [ + ] + }, + { + "name": "grouping", + "tx": { + "account_number": "108", + "chain_id": "cosmoshub-2", + "fee": { + "amount": [ + { + "amount": "600", + "denom": "uatom" + } + ], + "gas": "200000" + }, + "memo": "", + "msgs": [ + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper1qwl879nx9t6kef4supyazayf7vjhennyh568ys" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper1x88j7vp2xnw3zec8ur3g4waxycyz7m0mahdv3p" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt9m5s03xfytvz7" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper1ttfytaf43nkytzp8hkfjfgjc693ky4t3y2n2ku" + } + } + ], + "sequence": "106" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Chain ID : cosmoshub-2", + "1 | Account : 108", + "2 | Sequence : 106", + "3 | Fee : 600 uatom", + "4 | Gas : 200000", + "5 | Type : Withdraw Reward", + "6 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "6 | Delegator [2/2] : hgqhwh", + "7 | Validator [1/2] : cosmosvaloper1qwl879nx9t6kef4supyazayf7", + "7 | Validator [2/2] : vjhennyh568ys", + + "8 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "8 | Delegator [2/2] : hgqhwh", + "9 | Validator [1/2] : cosmosvaloper1x88j7vp2xnw3zec8ur3g4waxy", + "9 | Validator [2/2] : cyz7m0mahdv3p", + + "10 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "10 | Delegator [2/2] : hgqhwh", + "11 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", + "11 | Validator [2/2] : 9m5s03xfytvz7", + + "12 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "12 | Delegator [2/2] : hgqhwh", + "13 | Validator [1/2] : cosmosvaloper1ttfytaf43nkytzp8hkfjfgjc6", + "13 | Validator [2/2] : 93ky4t3y2n2ku" + ] + }, + { + "name": "massive", + "tx": { + "account_number": "108", + "chain_id": "cosmoshub-2", + "fee": { + "amount": [ + { + "amount": "600", + "denom": "uatom" + } + ], + "gas": "200000" + }, + "memo": "", + "msgs": [ + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper1qwl879nx9t6kef4supyazayf7vjhennyh568ys" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper1x88j7vp2xnw3zec8ur3g4waxycyz7m0mahdv3p" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt9m5s03xfytvz7" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper1ttfytaf43nkytzp8hkfjfgjc693ky4t3y2n2ku" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper1wdrypwex63geqswmcy5qynv4w3z3dyef2qmyna" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper102ruvpv2srmunfffxavttxnhezln6fnc54at8c" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper1ssm0d433seakyak8kcf93yefhknjleeds4y3em" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper13sduv92y3xdhy3rpmhakrc3v7t37e7ps9l0kpv" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper15urq2dtp9qce4fyc85m6upwm9xul3049e02707" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper14kn0kk33szpwus9nh8n87fjel8djx0y070ymmj" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper14lultfckehtszvzw4ehu0apvsr77afvyju5zzy" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper1k9a0cs97vul8w2vwknlfmpez6prv8klv03lv3d" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper1kj0h4kn4z5xvedu2nd9c4a9a559wvpuvu0h6qn" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper1hjct6q7npsspsg3dgvzk3sdf89spmlpfdn6m9d" + } + } + ], + "sequence": "106" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Chain ID : cosmoshub-2", + "1 | Account : 108", + "2 | Sequence : 106", + "3 | Fee : 600 uatom", + "4 | Gas : 200000", + "5 | Type : Withdraw Reward", + "6 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "6 | Delegator [2/2] : hgqhwh", + "7 | Validator [1/2] : cosmosvaloper1qwl879nx9t6kef4supyazayf7", + "7 | Validator [2/2] : vjhennyh568ys", + "8 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "8 | Delegator [2/2] : hgqhwh", + "9 | Validator [1/2] : cosmosvaloper1x88j7vp2xnw3zec8ur3g4waxy", + "9 | Validator [2/2] : cyz7m0mahdv3p", + "10 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "10 | Delegator [2/2] : hgqhwh", + "11 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", + "11 | Validator [2/2] : 9m5s03xfytvz7", + "12 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "12 | Delegator [2/2] : hgqhwh", + "13 | Validator [1/2] : cosmosvaloper1ttfytaf43nkytzp8hkfjfgjc6", + "13 | Validator [2/2] : 93ky4t3y2n2ku", + "14 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "14 | Delegator [2/2] : hgqhwh", + "15 | Validator [1/2] : cosmosvaloper1wdrypwex63geqswmcy5qynv4w", + "15 | Validator [2/2] : 3z3dyef2qmyna", + "16 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "16 | Delegator [2/2] : hgqhwh", + "17 | Validator [1/2] : cosmosvaloper102ruvpv2srmunfffxavttxnhe", + "17 | Validator [2/2] : zln6fnc54at8c", + "18 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "18 | Delegator [2/2] : hgqhwh", + "19 | Validator [1/2] : cosmosvaloper10e4vsut6suau8tk9m6dnrm0sl", + "19 | Validator [2/2] : gd6npe3jx5xpv", + "20 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "20 | Delegator [2/2] : hgqhwh", + "21 | Validator [1/2] : cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkf", + "21 | Validator [2/2] : v8z992ax69k08", + "22 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "22 | Delegator [2/2] : hgqhwh", + "23 | Validator [1/2] : cosmosvaloper1ssm0d433seakyak8kcf93yefh", + "23 | Validator [2/2] : knjleeds4y3em", + "24 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "24 | Delegator [2/2] : hgqhwh", + "25 | Validator [1/2] : cosmosvaloper13sduv92y3xdhy3rpmhakrc3v7", + "25 | Validator [2/2] : t37e7ps9l0kpv", + "26 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "26 | Delegator [2/2] : hgqhwh", + "27 | Validator [1/2] : cosmosvaloper15urq2dtp9qce4fyc85m6upwm9", + "27 | Validator [2/2] : xul3049e02707", + "28 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "28 | Delegator [2/2] : hgqhwh", + "29 | Validator [1/2] : cosmosvaloper14kn0kk33szpwus9nh8n87fjel", + "29 | Validator [2/2] : 8djx0y070ymmj", + "30 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "30 | Delegator [2/2] : hgqhwh", + "31 | Validator [1/2] : cosmosvaloper14lultfckehtszvzw4ehu0apvs", + "31 | Validator [2/2] : r77afvyju5zzy", + "32 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "32 | Delegator [2/2] : hgqhwh", + "33 | Validator [1/2] : cosmosvaloper1k9a0cs97vul8w2vwknlfmpez6", + "33 | Validator [2/2] : prv8klv03lv3d", + "34 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "34 | Delegator [2/2] : hgqhwh", + "35 | Validator [1/2] : cosmosvaloper1kj0h4kn4z5xvedu2nd9c4a9a5", + "35 | Validator [2/2] : 59wvpuvu0h6qn", + "36 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "36 | Delegator [2/2] : hgqhwh", + "37 | Validator [1/2] : cosmosvaloper1hjct6q7npsspsg3dgvzk3sdf8", + "37 | Validator [2/2] : 9spmlpfdn6m9d" + ] + } +] diff --git a/tests/tx_parse.cpp b/tests/tx_parse.cpp new file mode 100644 index 00000000..df962e98 --- /dev/null +++ b/tests/tx_parse.cpp @@ -0,0 +1,153 @@ +/******************************************************************************* +* (c) 2018 Zondax GmbH +* +* 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 "gtest/gtest.h" +#include +#include +#include +#include +#include "util/common.h" + +namespace { + parser_error_t tx_traverse(int16_t root_token_index, uint8_t *numChunks) { + uint16_t ret_value_token_index; + parser_error_t err = tx_traverse_find(root_token_index, &ret_value_token_index); + + if (err != parser_ok) + return err; + + return tx_getToken(ret_value_token_index, + parser_tx_obj.query.out_val, parser_tx_obj.query.out_val_len, + parser_tx_obj.query.page_index, numChunks); + } + + TEST(TxParse, Tx_Traverse) { + auto transaction = R"({"keyA":"123456", "keyB":"abcdefg", "keyC":""})"; + + parser_tx_obj.tx = transaction; + parser_tx_obj.flags.cache_valid = 0; + parser_error_t err = JSON_PARSE(&parser_tx_obj.json, parser_tx_obj.tx); + ASSERT_EQ(err, parser_ok); + + char key[100]; + char val[100]; + uint8_t numChunks; + + // Try second key - first chunk + INIT_QUERY_CONTEXT(key, sizeof(key), val, sizeof(val), 0, 4) + parser_tx_obj.query.item_index = 1; + + err = tx_traverse(0, &numChunks); + EXPECT_EQ(err, parser_ok) << parser_getErrorDescription(err); + EXPECT_EQ(numChunks, 1) << "Incorrect number of chunks"; + EXPECT_EQ_STR(key, "keyB", "Incorrect key") + EXPECT_EQ_STR(val, "abcdefg", "Incorrect value") + + // Try second key - Second chunk + INIT_QUERY_CONTEXT(key, sizeof(key), val, sizeof(val), 1, 4) + parser_tx_obj.query.item_index = 1; + err = tx_traverse(0, &numChunks); + EXPECT_EQ(err, parser_display_page_out_of_range) << parser_getErrorDescription(err); + EXPECT_EQ(numChunks, 1) << "Incorrect number of chunks"; + + // Find first key + INIT_QUERY_CONTEXT(key, sizeof(key), val, sizeof(val), 0, 4) + parser_tx_obj.query.item_index = 0; + err = tx_traverse(0, &numChunks); + EXPECT_EQ(err, parser_ok) << parser_getErrorDescription(err); + EXPECT_EQ(numChunks, 1) << "Incorrect number of chunks"; + EXPECT_EQ_STR(key, "keyA", "Incorrect key") + EXPECT_EQ_STR(val, "123456", "Incorrect value") + + // Try the same again + INIT_QUERY_CONTEXT(key, sizeof(key), val, sizeof(val), 0, 4) + parser_tx_obj.query.item_index = 0; + err = tx_traverse(0, &numChunks); + EXPECT_EQ(err, parser_ok) << parser_getErrorDescription(err); + EXPECT_EQ(numChunks, 1) << "Incorrect number of chunks"; + EXPECT_EQ_STR(key, "keyA", "Incorrect key") + EXPECT_EQ_STR(val, "123456", "Incorrect value") + + // Try last key + INIT_QUERY_CONTEXT(key, sizeof(key), val, sizeof(val), 0, 4) + parser_tx_obj.query.item_index = 2; + err = tx_traverse(0, &numChunks); + EXPECT_EQ(err, parser_ok) << parser_getErrorDescription(err); + EXPECT_EQ(numChunks, 1) << "Incorrect number of chunks"; + EXPECT_EQ_STR(key, "keyC", "Incorrect key") + EXPECT_EQ_STR(val, "", "Incorrect value") + } + + TEST(TxParse, OutOfBoundsSmall) { + auto transaction = R"({"keyA":"123456", "keyB":"abcdefg"})"; + + parser_tx_obj.tx = transaction; + parser_tx_obj.flags.cache_valid = 0; + parser_error_t err = JSON_PARSE(&parser_tx_obj.json, parser_tx_obj.tx); + ASSERT_EQ(err, parser_ok); + + char key[1000]; + char val[1000]; + uint8_t numChunks; + + INIT_QUERY_CONTEXT(key, sizeof(key), val, sizeof(val), 5, 4) + err = tx_traverse(0, &numChunks); + EXPECT_EQ(err, parser_display_page_out_of_range) << "Item not found"; + + // We should find it.. but later tx_display should fail + INIT_QUERY_CONTEXT(key, sizeof(key), val, sizeof(val), 0, 4) + err = tx_traverse(0, &numChunks); + EXPECT_EQ(err, parser_ok); + EXPECT_EQ(numChunks, 1) << "Item not found"; + } + + TEST(TxParse, Count_Minimal) { + auto transaction = R"({"account_number":"0"})"; + + parser_tx_obj.tx = transaction; + parser_tx_obj.flags.cache_valid = 0; + parser_error_t err = JSON_PARSE(&parser_tx_obj.json, parser_tx_obj.tx); + EXPECT_EQ(err, parser_ok); + + auto num_pages = tx_display_numItems(); + + EXPECT_EQ(0, num_pages) << "Wrong number of pages"; + } + + TEST(TxParse, Tx_Page_Count) { + auto transaction = R"({"account_number":"0","chain_id":"test-chain-1","fee":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}],"sequence":"1"})"; + + parser_tx_obj.tx = transaction; + parser_tx_obj.flags.cache_valid = 0; + parser_error_t err = JSON_PARSE(&parser_tx_obj.json, parser_tx_obj.tx); + EXPECT_EQ(err, parser_ok); + + auto num_pages = tx_display_numItems(); + EXPECT_EQ(10, num_pages) << "Wrong number of pages"; + } + + TEST(TxParse, Page_Count_MultipleMsgs) { + auto transaction = + R"({"account_number":"0","chain_id":"test-chain-1","fee":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]},{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]},{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]},{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}],"sequence":"1"})"; + + parser_tx_obj.tx = transaction; + parser_tx_obj.flags.cache_valid = 0; + parser_error_t err = JSON_PARSE(&parser_tx_obj.json, parser_tx_obj.tx); + EXPECT_EQ(err, parser_ok); + + EXPECT_EQ(22, tx_display_numItems()) << "Wrong number of items"; + } +} diff --git a/tests/tx_validate.cpp b/tests/tx_validate.cpp new file mode 100644 index 00000000..67692728 --- /dev/null +++ b/tests/tx_validate.cpp @@ -0,0 +1,300 @@ +/******************************************************************************* +* (c) 2018 Zondax GmbH +* +* 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 "gtest/gtest.h" +#include +#include +#include +#include "util/common.h" + +namespace { + TEST(TxValidationTest, CorrectFormat) { + auto transaction = + R"({"account_number":"0","chain_id":"test-chain-1","fee":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}],"sequence":"1"})"; + + parsed_json_t json; + parser_error_t err; + + err = JSON_PARSE(&json, transaction); + ASSERT_EQ(err, parser_ok); + + err = tx_validate(&json); + EXPECT_EQ(err, parser_ok) << "Validation failed, error: " << parser_getErrorDescription(err); + } + + TEST(TxValidationTest, MissingAccountNumber) { + auto transaction = + R"({"chain_id":"test-chain-1","fee":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}],"sequence":"1"})"; + + parsed_json_t json; + parser_error_t err; + + err = JSON_PARSE(&json, transaction); + ASSERT_EQ(err, parser_ok); + + err = tx_validate(&json); + + EXPECT_EQ(err, parser_json_missing_account_number) << "Validation failed, error: " << parser_getErrorDescription(err); + } + + TEST(TxValidationTest, MissingChainId) { + auto transaction = + R"({"account_number":"0","fee":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}],"sequence":"1"})"; + + parsed_json_t json; + parser_error_t err; + + err = JSON_PARSE(&json, transaction); + ASSERT_EQ(err, parser_ok); + + err = tx_validate(&json); + EXPECT_EQ(err, parser_json_missing_chain_id) << "Validation failed, error: " << parser_getErrorDescription(err); + } + + TEST(TxValidationTest, MissingFee) { + auto transaction = + R"({"account_number":"0","chain_id":"test-chain-1","fees":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}],"sequence":"1"})"; + + parsed_json_t json; + parser_error_t err; + + err = JSON_PARSE(&json, transaction); + ASSERT_EQ(err, parser_ok); + + err = tx_validate(&json); + EXPECT_EQ(err, parser_json_missing_fee) << "Validation failed, error: " << parser_getErrorDescription(err); + } + + TEST(TxValidationTest, MissingMsgs) { + auto transaction = + R"({"account_number":"0","chain_id":"test-chain-1","fee":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","msgsble":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}],"sequence":"1"})"; + + parsed_json_t json; + parser_error_t err; + + err = JSON_PARSE(&json, transaction); + ASSERT_EQ(err, parser_ok); + + err = tx_validate(&json); + EXPECT_EQ(err, parser_json_missing_msgs) << "Validation failed, error: " << parser_getErrorDescription(err); + } + + TEST(TxValidationTest, MissingSequence) { + auto transaction = + R"({"account_number":"0","chain_id":"test-chain-1","fee":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}]})"; + + parsed_json_t json; + parser_error_t err; + + err = JSON_PARSE(&json, transaction); + ASSERT_EQ(err, parser_ok); + + err = tx_validate(&json); + EXPECT_EQ(err, parser_json_missing_sequence) << "Validation failed, error: " << parser_getErrorDescription(err); + } + + TEST(TxValidationTest, Spaces_InTheMiddle) { + auto transaction = + R"({"account_number":"0","chain_id":"test-chain-1", "fee":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}],"sequence":"1"})"; + + parsed_json_t json; + parser_error_t err; + + err = JSON_PARSE(&json, transaction); + ASSERT_EQ(err, parser_ok); + + err = tx_validate(&json); + EXPECT_EQ(err, parser_json_contains_whitespace) << "Validation failed, error: " << parser_getErrorDescription(err); + } + + TEST(TxValidationTest, Spaces_AtTheFront) { + auto transaction = + R"({ "account_number":"0","chain_id":"test-chain-1","fee":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}],"sequence":"1"})"; + + parsed_json_t json; + parser_error_t err; + + err = JSON_PARSE(&json, transaction); + ASSERT_EQ(err, parser_ok); + + err = tx_validate(&json); + EXPECT_EQ(err, parser_json_contains_whitespace) << "Validation failed, error: " << parser_getErrorDescription(err); + } + + TEST(TxValidationTest, Spaces_AtTheEnd) { + auto transaction = + R"({"account_number":"0","chain_id":"test-chain-1","fee":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}],"sequence":"1" })"; + + parsed_json_t json; + parser_error_t err; + + err = JSON_PARSE(&json, transaction); + ASSERT_EQ(err, parser_ok); + + err = tx_validate(&json); + EXPECT_EQ(err, parser_json_contains_whitespace) << "Validation failed, error: " << parser_getErrorDescription(err); + } + + TEST(TxValidationTest, Spaces_Lots) { + auto transaction = + R"({"account_number":"0", "chain_id":"test-chain-1", "fee":{"amount": [{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}],"sequence":"1"})"; + + parsed_json_t json; + parser_error_t err; + + err = JSON_PARSE(&json, transaction); + ASSERT_EQ(err, parser_ok); + + err = tx_validate(&json); + EXPECT_EQ(err, parser_json_contains_whitespace) << "Validation failed, error: " << parser_getErrorDescription(err); + } + + TEST(TxValidationTest, AllowSpacesInString) { + auto transaction = + R"({"account_number":"0","chain_id":" test-chain-1 ","fee":{"amount":[{"amount":"5","denom":" photon"}],"gas":"10000"},"memo":"testmemo","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}],"sequence":"1"})"; + + parsed_json_t json; + parser_error_t err; + + err = JSON_PARSE(&json, transaction); + ASSERT_EQ(err, parser_ok); + + err = tx_validate(&json); + EXPECT_EQ(err, parser_ok) << "Validation failed, error: " << parser_getErrorDescription(err); + } + + TEST(TxValidationTest, SortedDictionary) { + auto transaction = + R"({"account_number":"0","chain_id":"test-chain-1","fee":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}],"sequence":"1"})"; + + parsed_json_t json; + parser_error_t err; + + err = JSON_PARSE(&json, transaction); + ASSERT_EQ(err, parser_ok); + + err = tx_validate(&json); + EXPECT_EQ(err, parser_ok) << "Validation failed, error: " << parser_getErrorDescription(err); + } + + TEST(TxValidationTest, NotSortedDictionary_FirstElement) { + auto transaction = + R"({"chain_id":"test-chain-1","account_number":"0","fee":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}],"sequence":"1"})"; + + parsed_json_t json; + parser_error_t err; + + err = JSON_PARSE(&json, transaction); + ASSERT_EQ(err, parser_ok); + + err = tx_validate(&json); + EXPECT_EQ(err, parser_json_is_not_sorted) << "Validation failed, error: " << parser_getErrorDescription(err); + } + + TEST(TxValidationTest, NotSortedDictionary_MiddleElement) { + auto transaction = + R"({"account_number":"0","chain_id":"test-chain-1","memo":"testmemo","fee":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}],"sequence":"1"})"; + + parsed_json_t json; + parser_error_t err; + + err = JSON_PARSE(&json, transaction); + ASSERT_EQ(err, parser_ok); + + err = tx_validate(&json); + EXPECT_EQ(err, parser_json_is_not_sorted) << "Validation failed, error: " << parser_getErrorDescription(err); + } + + TEST(TxValidationTest, NotSortedDictionary_LastElement) { + auto transaction = + R"({"account_number":"0","chain_id":"test-chain-1","fee":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","sequence":"1","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}]})"; + + parsed_json_t json; + parser_error_t err; + + err = JSON_PARSE(&json, transaction); + ASSERT_EQ(err, parser_ok); + + err = tx_validate(&json); + EXPECT_EQ(err, parser_json_is_not_sorted) << "Validation failed, error: " << parser_getErrorDescription(err); + } + +// This json has been taken directly from goclient which uses cosmos to serialize a simple tx +// This test is currently failing the validation. +// We are reviewing the validation code and cosmos serialization to find the culprit. + + TEST(TxValidationTest, CosmosExample) { + auto transaction = + R"({"account_number":"0","chain_id":"test-chain-1","fee":{"amount":[{"amount":"5","denom":"photon"}],"gas":"10000"},"memo":"testmemo","msgs":[{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"amount":"10","denom":"atom"}]}]}],"sequence":"1"})"; + + parsed_json_t json; + parser_error_t err; + + err = JSON_PARSE(&json, transaction); + ASSERT_EQ(err, parser_ok); + + err = tx_validate(&json); + EXPECT_EQ(err, parser_ok) << "Validation failed, error: " << parser_getErrorDescription(err); + } + + TEST(TxValidationTest, GaiaCLIissue) { + auto transaction = R"({"account_number":"811","chain_id":"cosmoshub-1","fee":{"amount":[],"gas":"5000000"},"memo":"","msgs":[{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper14kn0kk33szpwus9nh8n87fjel8djx0y070ymmj","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper14kn0kk33szpwus9nh8n87fjel8djx0y070ymmj","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper14kn0kk33szpwus9nh8n87fjel8djx0y070ymmj","value":{"amount":"8000000000","denom":"uatom"}}}],"sequence":"1"})"; + + parsed_json_t json; + parser_error_t err; + + err = JSON_PARSE(&json, transaction); + ASSERT_EQ(err, parser_ok); + + err = tx_validate(&json); + EXPECT_EQ(err, parser_ok) << "Validation failed, error: " << parser_getErrorDescription(err); + } + + TEST(TxValidationTest, GaiaCLIissueBigTX) { + auto transaction = R"({"account_number":"811","chain_id":"cosmoshub-1","fee":{"amount":[],"gas":"5000000"},"memo":"","msgs":[{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", +"validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", +"validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx", + "validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper14kn0kk33szpwus9nh8n87fjel8djx0y070ymmj","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper14kn0kk33szpwus9nh8n87fjel8djx0y070ymmj","value":{"amount":"8000000000","denom":"uatom"}}},{"type":"cosmos-sdk/MsgDelegate","value":{"delegator_address":"cosmos13vfzpfmg6jgzfk4rke9glzpngrzucjtanq9awx","validator_address":"cosmosvaloper14kn0kk33szpwus9nh8n87fjel8djx0y070ymmj","value":{"amount":"8000000000","denom":"uatom"}}}],"sequence":"1"})"; + + parsed_json_t json; + parser_error_t err; + + err = JSON_PARSE(&json, transaction); + ASSERT_EQ( err, parser_json_too_many_tokens); + + err = tx_validate(&json); + EXPECT_EQ(err, parser_json_missing_chain_id) << "Validation failed, error: " << parser_getErrorDescription(err); + } +} diff --git a/tests/ui_output.cpp b/tests/ui_output.cpp new file mode 100644 index 00000000..04c2cc09 --- /dev/null +++ b/tests/ui_output.cpp @@ -0,0 +1,99 @@ +/******************************************************************************* +* (c) 2019 Zondax GmbH +* +* 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 +#include "util/testcases.h" + +#include +#include +#include "common/parser.h" +#include "util/common.h" + +using ::testing::TestWithParam; +using ::testing::Values; + +void validate_testcase(const testcase_t &tc) { + parser_context_t ctx; + parser_error_t err; + + const auto *buffer = (const uint8_t *) tc.tx.c_str(); + size_t bufferLen = tc.tx.size(); + + err = parser_parse(&ctx, buffer, bufferLen); + ASSERT_EQ(parser_getErrorDescription(err), tc.parsingErr) << "Parsing error mismatch"; + + if (err != parser_ok) + return; + + err = parser_validate(&ctx); + EXPECT_EQ( parser_getErrorDescription(err), tc.validationErr) << "Validation error mismatch"; +} + +void check_testcase(const testcase_t &tc) { + parser_context_t ctx; + parser_error_t err; + + const auto *buffer = (const uint8_t *) tc.tx.c_str(); + size_t bufferLen = tc.tx.size(); + + err = parser_parse(&ctx, buffer, bufferLen); + ASSERT_EQ(parser_getErrorDescription(err), tc.parsingErr) << "Parsing error mismatch"; + + if (err != parser_ok) + return; + + auto output = dumpUI(&ctx, 40, 40); + + for (const auto &i : output) { + std::cout << i << std::endl; + } + std::cout << std::endl << std::endl; + + EXPECT_EQ(output.size(), tc.expected.size()); + for (size_t i = 0; i < tc.expected.size(); i++) { + if (i < output.size()) { + EXPECT_THAT(output[i], testing::Eq(tc.expected[i])); + } + } +} + +/////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////// + +class JsonTests : public ::testing::TestWithParam { +public: + struct PrintToStringParamName { + template + std::string operator()(const testing::TestParamInfo &info) const { + auto p = static_cast(info.param); + std::stringstream ss; + ss << p.description; + return ss.str(); + } + }; +}; + +INSTANTIATE_TEST_SUITE_P ( + JsonTestCases, + JsonTests, + ::testing::ValuesIn(GetJsonTestCases("testcases/manual.json")), + JsonTests::PrintToStringParamName() +); + +TEST_P(JsonTests, ValidateTestcase) { validate_testcase(GetParam()); } + +TEST_P(JsonTests, CheckUIOutput) { check_testcase(GetParam()); } diff --git a/tests/util/common.cpp b/tests/util/common.cpp new file mode 100644 index 00000000..88a4578f --- /dev/null +++ b/tests/util/common.cpp @@ -0,0 +1,58 @@ +/******************************************************************************* +* (c) 2018 Zondax GmbH +* +* 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 +#include +#include +#include "common.h" + +std::vector dumpUI(parser_context_t *ctx, + uint16_t maxKeyLen, + uint16_t maxValueLen) { + uint16_t numItems = parser_getNumItems(ctx); + + auto answer = std::vector(); + + for (uint16_t idx = 0; idx < numItems; idx++) { + char keyBuffer[1000]; + char valueBuffer[1000]; + uint8_t pageIdx = 0; + uint8_t pageCount = 1; + + while (pageIdx < pageCount) { + std::stringstream ss; + + auto err = parser_getItem(ctx, + idx, + keyBuffer, maxKeyLen, + valueBuffer, maxValueLen, + pageIdx, &pageCount); + + ss << idx << " | " << keyBuffer << " : "; + + if (err == parser_ok) { + ss << valueBuffer; + } else { + ss << parser_getErrorDescription(err); + } + + answer.push_back(ss.str()); + + pageIdx++; + } + } + + return answer; +} diff --git a/tests/util/common.h b/tests/util/common.h new file mode 100644 index 00000000..e1c14d62 --- /dev/null +++ b/tests/util/common.h @@ -0,0 +1,31 @@ +/******************************************************************************* +* (c) 2018 Zondax GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#pragma once + +#include +#include +#include + +#define EXPECT_EQ_STR(_STR1, _STR2, _ERROR_MESSAGE) { if (_STR1 != nullptr & _STR2 != nullptr) \ +EXPECT_TRUE(!strcmp(_STR1, _STR2)) << _ERROR_MESSAGE << ", expected: " << _STR2 << ", received: " << _STR1; \ +else FAIL() << "One of the strings is null"; } + +parser_error_t parse_tx(parsed_json_t *parsed_json, const char *tx); + +std::vector dumpUI(parser_context_t *ctx, uint16_t maxKeyLen, uint16_t maxValueLen); + +#define JSON_PARSE(parsed_json, buffer) json_parse(parsed_json, buffer, strlen(buffer)) diff --git a/tests/util/testcases.cpp b/tests/util/testcases.cpp new file mode 100644 index 00000000..7b484d2e --- /dev/null +++ b/tests/util/testcases.cpp @@ -0,0 +1,67 @@ +/******************************************************************************* +* (c) 2019 Zondax GmbH +* +* 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 "testcases.h" +#include +#include +#include + +std::vector GetJsonTestCases(const std::string &filename) { + auto answer = std::vector(); + + Json::CharReaderBuilder builder; + std::shared_ptr obj(new Json::Value()); + + std::ifstream inFile(filename); + EXPECT_TRUE(inFile.is_open()) + << "\n" + << "******************\n" + << "Check that your working directory points to the tests directory\n" + << "In CLion use $PROJECT_DIR$\\tests\n" + << "******************\n"; + if (!inFile.is_open()) + return answer; + + // Retrieve all test cases + JSONCPP_STRING errs; + Json::parseFromStream(builder, inFile, obj.get(), &errs); + std::cout << "Number of testcases: " << obj->size() << std::endl; + answer.reserve(obj->size()); + + for (int i = 0; i < obj->size(); i++) { + auto v = (*obj)[i]; + + Json::StreamWriterBuilder wbuilder; + wbuilder["commentStyle"] = "None"; + wbuilder["indentation"] = ""; + std::string txStr = Json::writeString(wbuilder, v["tx"]); + + auto expected = std::vector(); + for (const auto j : v["expected"]) { + expected.push_back(j.asString()); + } + + answer.push_back( + testcase_t{ + v["name"].asString(), + txStr, + v["parsingErr"].asString(), + v["validationErr"].asString(), + expected, + }); + } + + return answer; +} diff --git a/src/lib/coin.h b/tests/util/testcases.h similarity index 66% rename from src/lib/coin.h rename to tests/util/testcases.h index 6bd6150d..0ebd3cdf 100644 --- a/src/lib/coin.h +++ b/tests/util/testcases.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 ZondaX GmbH +* (c) 2019 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,20 +14,15 @@ * limitations under the License. ********************************************************************************/ #pragma once +#include +#include -#ifdef __cplusplus -extern "C" { -#endif +typedef struct { + std::string description; + std::string tx; + std::string parsingErr; + std::string validationErr; + std::vector expected; +} testcase_t; -#include -#include - -#define BIP44_0_DEFAULT (0x80000000 | 0x2c) -#define BIP44_1_DEFAULT (0x80000000 | 0x76) // FIXME: Change derivation path -#define BIP44_2_DEFAULT (0x80000000 | 0) -#define BIP44_3_DEFAULT (0) -#define BIP44_4_DEFAULT (0) - -#ifdef __cplusplus -} -#endif +std::vector GetJsonTestCases(const std::string& filename); From ab2523370c0a180e1979c4b79910b415e3d09c6b Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Thu, 26 Mar 2020 21:57:11 +0100 Subject: [PATCH 53/78] upgrade to v3.0.1 --- app/Makefile | 2 +- app/src/tx_parser.c | 29 +++++++++++++++++------------ tests/tx_parse.cpp | 4 ++-- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/app/Makefile b/app/Makefile index 9c27e442..d56b9f5e 100755 --- a/app/Makefile +++ b/app/Makefile @@ -28,7 +28,7 @@ include $(BOLOS_SDK)/Makefile.defines APPNAME = "Cosmos" APPVERSION_M=3 APPVERSION_N=0 -APPVERSION_P=0 +APPVERSION_P=1 APPPATH = "44'/118'" APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path $(APPPATH) diff --git a/app/src/tx_parser.c b/app/src/tx_parser.c index 8c075f7b..ab8a2662 100644 --- a/app/src/tx_parser.c +++ b/app/src/tx_parser.c @@ -69,26 +69,31 @@ parser_error_t tx_getToken(uint16_t token_index, const int16_t token_start = parser_tx_obj.json.tokens[token_index].start; const int16_t token_end = parser_tx_obj.json.tokens[token_index].end; - if (token_start >= token_end) { + if (token_start > token_end) { return parser_unexpected_buffer_end; } const char *inValue = parser_tx_obj.tx + token_start; uint16_t inLen = token_end - token_start; - for (int8_t i = 0; i < array_length(value_substitutions); i++) { - const char *substStr = value_substitutions[i].str1; - const size_t substStrLen = strlen(substStr); - if (inLen == substStrLen && !MEMCMP(inValue, substStr, substStrLen)) { - inValue = value_substitutions[i].str2; - inLen = strlen(value_substitutions[i].str2); - break; + // empty strings are considered the first page + *pageCount = 1; + if (inLen > 0) { + for (int8_t i = 0; i < array_length(value_substitutions); i++) { + const char *substStr = value_substitutions[i].str1; + const size_t substStrLen = strlen(substStr); + if (inLen == substStrLen && !MEMCMP(inValue, substStr, substStrLen)) { + inValue = value_substitutions[i].str2; + inLen = strlen(value_substitutions[i].str2); + break; + } } - } - pageStringExt(out_val, out_val_len, - inValue, inLen, - pageIdx, pageCount); + pageStringExt(out_val, out_val_len, + inValue, inLen, + pageIdx, pageCount); + + } if (pageIdx >= *pageCount) { return parser_display_page_out_of_range; diff --git a/tests/tx_parse.cpp b/tests/tx_parse.cpp index df962e98..e721cc90 100644 --- a/tests/tx_parse.cpp +++ b/tests/tx_parse.cpp @@ -122,9 +122,9 @@ namespace { parser_error_t err = JSON_PARSE(&parser_tx_obj.json, parser_tx_obj.tx); EXPECT_EQ(err, parser_ok); - auto num_pages = tx_display_numItems(); + auto numItems = tx_display_numItems(); - EXPECT_EQ(0, num_pages) << "Wrong number of pages"; + EXPECT_EQ(1, numItems) << "Wrong number of pages"; } TEST(TxParse, Tx_Page_Count) { From bf841db102282d72f5665820ce5190fc68d23b70 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Fri, 27 Mar 2020 16:46:21 +0100 Subject: [PATCH 54/78] disable automatic publishing --- .circleci/config.yml | 52 ++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7dbf4a7f..532e514d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,24 +28,24 @@ jobs: cd /home/zondax/project make - build_package: - docker: - - image: zondax/ledger-docker-bolos:v1.0 - environment: - - BOLOS_SDK=/home/zondax/project/deps/nanos-secure-sdk - - BOLOS_ENV=/opt/bolos - steps: - - checkout - - run: git submodule update --init --recursive - - run: - name: Build - command: | - source /home/zondax/.cargo/env - cd /home/zondax/project - make - - store_artifacts: - path: /home/zondax/project/app/pkg/zxtool.sh - - run: /home/zondax/go/bin/ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete $(/home/zondax/project/app/pkg/zxtool.sh version) /home/zondax/project/app/pkg/zxtool.sh +# build_package: +# docker: +# - image: zondax/ledger-docker-bolos:v1.0 +# environment: +# - BOLOS_SDK=/home/zondax/project/deps/nanos-secure-sdk +# - BOLOS_ENV=/opt/bolos +# steps: +# - checkout +# - run: git submodule update --init --recursive +# - run: +# name: Build +# command: | +# source /home/zondax/.cargo/env +# cd /home/zondax/project +# make +# - store_artifacts: +# path: /home/zondax/project/app/pkg/zxtool.sh +# - run: /home/zondax/go/bin/ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete $(/home/zondax/project/app/pkg/zxtool.sh version) /home/zondax/project/app/pkg/zxtool.sh workflows: version: 2 @@ -53,11 +53,11 @@ workflows: jobs: - build - build_ledger - - build_package: - requires: - - build - - build_ledger - filters: - branches: - only: - - master +# - build_package: +# requires: +# - build +# - build_ledger +# filters: +# branches: +# only: +# - master From d736248ad4c73c60bbe143791da73b8ecb549dfc Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Mon, 30 Mar 2020 10:52:48 +0200 Subject: [PATCH 55/78] Revert version to 2.10.0 --- app/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Makefile b/app/Makefile index d56b9f5e..ce442350 100755 --- a/app/Makefile +++ b/app/Makefile @@ -26,9 +26,9 @@ include $(BOLOS_SDK)/Makefile.defines # Main app configuration APPNAME = "Cosmos" -APPVERSION_M=3 -APPVERSION_N=0 -APPVERSION_P=1 +APPVERSION_M=2 +APPVERSION_N=10 +APPVERSION_P=0 APPPATH = "44'/118'" APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path $(APPPATH) From f5bfce5ea18f7f77c495a67c608de04ad33be9af Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sun, 12 Apr 2020 23:33:33 +0200 Subject: [PATCH 56/78] Upgrade to v2.11.0 (#1) * upgrade zxlib * basic zemu tests * updating dependencies * adding signature test + emulator * fixing invalid caching issue * returning enum-based error codes * improve error handling --- app/Makefile | 4 +- app/src/common/parser.h | 2 +- app/src/common/tx.c | 18 +- app/src/common/tx.h | 2 +- app/src/crypto.c | 2 +- app/src/json/json_parser.c | 127 +- app/src/json/json_parser.h | 70 +- app/src/parser.c | 52 +- app/src/tx_display.c | 78 +- app/src/tx_display.h | 6 +- app/src/tx_parser.c | 35 +- app/src/tx_validate.c | 65 +- deps/ledger-zxlib/.editorconfig | 4 + deps/ledger-zxlib/cmake/dockerized_build.mk | 2 +- deps/ledger-zxlib/include/bech32.h | 14 +- deps/ledger-zxlib/include/zxerror.h | 32 + deps/ledger-zxlib/scripts/install_deps.sh | 2 + deps/ledger-zxlib/src/bech32.c | 39 +- deps/ledger-zxlib/tests/bech32.cpp | 39 +- integration/.babelrc | 13 + integration/.gitignore | 2 + integration/jest.config.js | 31 + integration/jest.js | 2 + integration/package.json | 41 + integration/snapshots/.gitignore | 1 + integration/tests/test.js | 125 + integration/yarn.lock | 6205 +++++++++++++++++++ tests/json_parser.cpp | 102 +- tests/testcases/manual.json | 74 + tests/tx_parse.cpp | 29 +- tests/util/common.cpp | 3 +- 31 files changed, 6966 insertions(+), 255 deletions(-) create mode 100644 deps/ledger-zxlib/include/zxerror.h create mode 100644 integration/.babelrc create mode 100644 integration/.gitignore create mode 100644 integration/jest.config.js create mode 100644 integration/jest.js create mode 100644 integration/package.json create mode 100644 integration/snapshots/.gitignore create mode 100644 integration/tests/test.js create mode 100644 integration/yarn.lock diff --git a/app/Makefile b/app/Makefile index ce442350..df8645ac 100755 --- a/app/Makefile +++ b/app/Makefile @@ -27,7 +27,7 @@ include $(BOLOS_SDK)/Makefile.defines # Main app configuration APPNAME = "Cosmos" APPVERSION_M=2 -APPVERSION_N=10 +APPVERSION_N=11 APPVERSION_P=0 APPPATH = "44'/118'" @@ -76,8 +76,8 @@ DEFINES += LEDGER_MAJOR_VERSION=$(APPVERSION_M) LEDGER_MINOR_VERSION=$(APPVERSIO DEFINES += HAVE_U2F HAVE_IO_U2F DEFINES += U2F_PROXY_MAGIC=\"CSM\" -DEFINES += USB_SEGMENT_SIZE=64 DEFINES += U2F_MAX_MESSAGE_SIZE=264 #257+5+2 +DEFINES += USB_SEGMENT_SIZE=64 DEFINES += HAVE_BOLOS_APP_STACK_CANARY DEFINES += NDEBUG diff --git a/app/src/common/parser.h b/app/src/common/parser.h index 8f5bbe04..ce899851 100644 --- a/app/src/common/parser.h +++ b/app/src/common/parser.h @@ -35,7 +35,7 @@ parser_error_t parser_parse(parser_context_t *ctx, parser_error_t parser_validate(const parser_context_t *ctx); //// returns the number of items in the current parsing context -uint8_t parser_getNumItems(const parser_context_t *ctx); +parser_error_t parser_getNumItems(const parser_context_t *ctx, uint16_t *num_items); // retrieves a readable output for each field / page parser_error_t parser_getItem(const parser_context_t *ctx, diff --git a/app/src/common/tx.c b/app/src/common/tx.c index 9a49de8d..655292ea 100644 --- a/app/src/common/tx.c +++ b/app/src/common/tx.c @@ -91,8 +91,14 @@ const char *tx_parse() { return NULL; } -uint8_t tx_getNumItems() { - return parser_getNumItems(&ctx_parsed_tx); +tx_error_t tx_getNumItems(uint16_t *num_items) { + parser_error_t err = parser_getNumItems(&ctx_parsed_tx, num_items); + + if (err != parser_ok) { + return tx_no_data; + } + + return tx_no_error; } tx_error_t tx_getItem(int8_t displayIdx, @@ -101,7 +107,13 @@ tx_error_t tx_getItem(int8_t displayIdx, uint8_t pageIdx, uint8_t *pageCount) { tx_error_t err = tx_no_error; - if (displayIdx < 0 || displayIdx > tx_getNumItems()) { + uint16_t numItems = 0; + err = tx_getNumItems(&numItems); + if (err != tx_no_error) { + return err; + } + + if (displayIdx < 0 || displayIdx > numItems) { return tx_no_data; } diff --git a/app/src/common/tx.h b/app/src/common/tx.h index 062842ba..600873e5 100644 --- a/app/src/common/tx.h +++ b/app/src/common/tx.h @@ -49,7 +49,7 @@ uint8_t *tx_get_buffer(); const char *tx_parse(); /// Return the number of items in the transaction -uint8_t tx_getNumItems(); +tx_error_t tx_getNumItems(uint16_t *num_items); /// Gets an specific item from the transaction (including paging) tx_error_t tx_getItem(int8_t displayIdx, diff --git a/app/src/crypto.c b/app/src/crypto.c index 9a08c165..074f5074 100644 --- a/app/src/crypto.c +++ b/app/src/crypto.c @@ -186,7 +186,7 @@ uint16_t crypto_fillAddress(uint8_t *buffer, uint16_t buffer_len) { ripemd160_32(hashed2_pk, hashed1_pk); char *addr = (char *) (buffer + PK_LEN); - bech32EncodeFromBytes(addr, bech32_hrp, hashed2_pk, CX_RIPEMD160_SIZE); + bech32EncodeFromBytes(addr, buffer_len - PK_LEN, bech32_hrp, hashed2_pk, CX_RIPEMD160_SIZE); return PK_LEN + strlen(addr); } diff --git a/app/src/json/json_parser.c b/app/src/json/json_parser.c index 526d1521..0763fd1a 100644 --- a/app/src/json/json_parser.c +++ b/app/src/json/json_parser.c @@ -68,15 +68,16 @@ parser_error_t json_parse(parsed_json_t *parsed_json, const char *buffer, uint16 return parser_ok; } -uint16_t array_get_element_count(uint16_t array_token_index, - const parsed_json_t *json) { +parser_error_t array_get_element_count(const parsed_json_t *json, + uint16_t array_token_index, + uint16_t *number_elements) { + *number_elements = 0; if (array_token_index < 0 || array_token_index > json->numberOfTokens) { - return 0; + return parser_no_data; } jsmntok_t array_token = json->tokens[array_token_index]; uint16_t token_index = array_token_index; - uint16_t element_count = 0; uint16_t prev_element_end = array_token.start; while (true) { token_index++; @@ -91,29 +92,31 @@ uint16_t array_get_element_count(uint16_t array_token_index, continue; } prev_element_end = current_token.end; - element_count++; + (*number_elements)++; } - return element_count; + return parser_ok; } -int16_t array_get_nth_element(uint16_t array_token_index, - uint16_t element_index, - const parsed_json_t *json) { +parser_error_t array_get_nth_element(const parsed_json_t *json, + uint16_t array_token_index, + uint16_t element_index, + uint16_t *token_index) { if (array_token_index < 0 || array_token_index > json->numberOfTokens) { - return -1; + return parser_no_data; } jsmntok_t array_token = json->tokens[array_token_index]; - uint16_t token_index = array_token_index; + *token_index = array_token_index; + uint16_t element_count = 0; uint16_t prev_element_end = array_token.start; while (true) { - token_index++; - if (token_index >= json->numberOfTokens) { + (*token_index)++; + if (*token_index >= json->numberOfTokens) { break; } - jsmntok_t current_token = json->tokens[token_index]; + jsmntok_t current_token = json->tokens[*token_index]; if (current_token.start > array_token.end) { break; } @@ -122,23 +125,24 @@ int16_t array_get_nth_element(uint16_t array_token_index, } prev_element_end = current_token.end; if (element_count == element_index) { - return token_index; + return parser_ok; } element_count++; } - return -1; + return parser_no_data; } -uint16_t object_get_element_count(uint16_t object_token_index, - const parsed_json_t *json) { +parser_error_t object_get_element_count(const parsed_json_t *json, + uint16_t object_token_index, + uint16_t *element_count) { + *element_count = 0; if (object_token_index < 0 || object_token_index > json->numberOfTokens) { - return 0; + return parser_no_data; } jsmntok_t object_token = json->tokens[object_token_index]; uint16_t token_index = object_token_index; - uint16_t element_count = 0; uint16_t prev_element_end = object_token.start; token_index++; while (true) { @@ -154,30 +158,31 @@ uint16_t object_get_element_count(uint16_t object_token_index, continue; } prev_element_end = value_token.end; - element_count++; + (*element_count)++; } - return element_count; + return parser_ok; } -int16_t object_get_nth_key(uint16_t object_token_index, - uint16_t object_element_index, - const parsed_json_t *parsed_transaction) { - if (object_token_index < 0 || object_token_index > parsed_transaction->numberOfTokens) { - return -1; +parser_error_t object_get_nth_key(const parsed_json_t *json, + uint16_t object_token_index, + uint16_t object_element_index, + uint16_t *token_index) { + *token_index = object_token_index; + if (object_token_index < 0 || object_token_index > json->numberOfTokens) { + return parser_no_data; } - jsmntok_t object_token = parsed_transaction->tokens[object_token_index]; - uint16_t token_index = object_token_index; + jsmntok_t object_token = json->tokens[object_token_index]; uint16_t element_count = 0; uint16_t prev_element_end = object_token.start; - token_index++; + (*token_index)++; while (true) { - if (token_index >= parsed_transaction->numberOfTokens) { + if (*token_index >= json->numberOfTokens) { break; } - jsmntok_t key_token = parsed_transaction->tokens[token_index++]; - jsmntok_t value_token = parsed_transaction->tokens[token_index]; + jsmntok_t key_token = json->tokens[(*token_index)++]; + jsmntok_t value_token = json->tokens[*token_index]; if (key_token.start > object_token.end) { break; } @@ -186,45 +191,47 @@ int16_t object_get_nth_key(uint16_t object_token_index, } prev_element_end = value_token.end; if (element_count == object_element_index) { - return token_index - 1; + (*token_index)--; + return parser_ok; } element_count++; } - return -1; + return parser_no_data; } -int16_t object_get_nth_value(uint16_t object_token_index, - uint16_t object_element_index, - const parsed_json_t *parsed_transaction) { - if (object_token_index < 0 || object_token_index > parsed_transaction->numberOfTokens) { - return -1; +parser_error_t object_get_nth_value(const parsed_json_t *json, + uint16_t object_token_index, + uint16_t object_element_index, + uint16_t *key_index) { + if (object_token_index < 0 || object_token_index > json->numberOfTokens) { + return parser_no_data; } - int key_index = object_get_nth_key(object_token_index, object_element_index, parsed_transaction); - if (key_index != -1) { - return key_index + 1; - } - return -1; + CHECK_PARSER_ERR(object_get_nth_key(json, object_token_index, object_element_index, key_index)); + (*key_index) ++; + + return parser_ok; } -int16_t object_get_value(const parsed_json_t *parsed_transaction, - uint16_t object_token_index, - const char *key_name) { - if (object_token_index < 0 || object_token_index > parsed_transaction->numberOfTokens) { - return -1; +parser_error_t object_get_value(const parsed_json_t *json, + uint16_t object_token_index, + const char *key_name, + uint16_t *token_index) { + if (object_token_index < 0 || object_token_index > json->numberOfTokens) { + return parser_no_data; } - const jsmntok_t object_token = parsed_transaction->tokens[object_token_index]; + const jsmntok_t object_token = json->tokens[object_token_index]; - int token_index = object_token_index; + *token_index = object_token_index; int prev_element_end = object_token.start; - token_index++; + (*token_index)++; - while (token_index < parsed_transaction->numberOfTokens) { - const jsmntok_t key_token = parsed_transaction->tokens[token_index]; - token_index++; - const jsmntok_t value_token = parsed_transaction->tokens[token_index]; + while (*token_index < json->numberOfTokens) { + const jsmntok_t key_token = json->tokens[*token_index]; + (*token_index)++; + const jsmntok_t value_token = json->tokens[*token_index]; if (key_token.start > object_token.end) { break; @@ -236,12 +243,12 @@ int16_t object_get_value(const parsed_json_t *parsed_transaction, if (((uint16_t) strlen(key_name)) == (key_token.end - key_token.start)) { if (EQUALS(key_name, - parsed_transaction->buffer + key_token.start, + json->buffer + key_token.start, key_token.end - key_token.start)) { - return token_index; + return parser_ok; } } } - return -1; + return parser_no_data; } diff --git a/app/src/json/json_parser.h b/app/src/json/json_parser.h index 19b4df7f..57409ecb 100644 --- a/app/src/json/json_parser.h +++ b/app/src/json/json_parser.h @@ -48,7 +48,7 @@ extern "C" { // - re-created SendMsg struct with indices pointing to tokens in parsed json typedef struct { uint8_t isValid; - int32_t numberOfTokens; + uint32_t numberOfTokens; jsmntok_t tokens[MAX_NUMBER_OF_TOKENS]; const char *buffer; uint16_t bufferLen; @@ -67,55 +67,65 @@ parser_error_t json_parse(parsed_json_t *parsed_json, uint16_t transaction_length); /// Get the number of elements in the array -/// \param array_token_index /// \param json -/// \return number of elements -uint16_t array_get_element_count(uint16_t array_token_index, - const parsed_json_t *json); +/// \param array_token_index +/// \param number of elements (out) +/// \return Error message +parser_error_t array_get_element_count(const parsed_json_t *json, + uint16_t array_token_index, + uint16_t *number_elements); /// Get the token index of the nth array's element +/// \param json /// \param array_token_index /// \param element_index -/// \param json -/// \return returns the token index or -1 if not found -int16_t array_get_nth_element(uint16_t array_token_index, - uint16_t element_index, - const parsed_json_t *json); +/// \param token index +/// \return Error message +parser_error_t array_get_nth_element(const parsed_json_t *json, + uint16_t array_token_index, + uint16_t element_index, + uint16_t *token_index); /// Get the number of dictionary elements (key/value pairs) under given object +/// \param json /// \param object_token_index: token index of the parent object -/// \param parsed_transaction -/// \return number of elements -uint16_t object_get_element_count(uint16_t object_token_index, - const parsed_json_t *parsed_transaction); +/// \param number of elements (out) +/// \return Error message +parser_error_t object_get_element_count(const parsed_json_t *json, + uint16_t object_token_index, + uint16_t *number_elements); /// Get the token index for the nth dictionary key +/// \param json /// \param object_token_index: token index of the parent object /// \param object_element_index -/// \param parsed_transaction -/// \return returns token index or -1 if not found -int16_t object_get_nth_key(uint16_t object_token_index, - uint16_t object_element_index, - const parsed_json_t *parsed_transaction); +/// \return token index (out) +/// \return Error message +parser_error_t object_get_nth_key(const parsed_json_t *json, + uint16_t object_token_index, + uint16_t object_element_index, + uint16_t *token_index); /// Get the token index for the nth dictionary value +/// \param json /// \param object_token_index: token index of the parent object /// \param object_element_index -/// \param parsed_transaction -/// \return returns token index or -1 if not found -int16_t object_get_nth_value(uint16_t object_token_index, - uint16_t object_element_index, - const parsed_json_t *parsed_transaction); +/// \return token index (out)) +/// \return Error message +parser_error_t object_get_nth_value(const parsed_json_t *json, + uint16_t object_token_index, + uint16_t object_element_index, + uint16_t *token_index); /// Get the token index of the value that matches the given key +/// \param json /// \param object_token_index: token index of the parent object /// \param key_name: key name of the wanted value -/// \param parsed_transaction -/// \param transaction -/// \return returns token index or -1 if not found -int16_t object_get_value(const parsed_json_t *parsed_transaction, - uint16_t object_token_index, - const char *key_name); +/// \return Error message +parser_error_t object_get_value(const parsed_json_t *json, + uint16_t object_token_index, + const char *key_name, + uint16_t *token_index); #ifdef __cplusplus } diff --git a/app/src/parser.c b/app/src/parser.c index 39378310..9198779e 100644 --- a/app/src/parser.c +++ b/app/src/parser.c @@ -23,12 +23,6 @@ #include "parser_impl.h" #include "common/parser.h" -__Z_INLINE parser_error_t parser_getItem_raw(const parser_context_t *ctx, - int8_t displayIdx, - char *outKey, uint16_t outKeyLen, - char *outVal, uint16_t outValLen, - uint8_t pageIdx, uint8_t *pageCount); - parser_error_t parser_parse(parser_context_t *ctx, const uint8_t *data, size_t dataLen) { @@ -40,7 +34,8 @@ parser_error_t parser_validate(const parser_context_t *ctx) { CHECK_PARSER_ERR(tx_validate(&parser_tx_obj.json)) // Iterate through all items to check that all can be shown and are valid - uint8_t numItems = parser_getNumItems(ctx); + uint16_t numItems = 0; + CHECK_PARSER_ERR(parser_getNumItems(ctx, &numItems)); char tmpKey[40]; char tmpVal[40]; @@ -53,8 +48,9 @@ parser_error_t parser_validate(const parser_context_t *ctx) { return parser_ok; } -uint8_t parser_getNumItems(const parser_context_t *ctx) { - return tx_display_numItems(); +parser_error_t parser_getNumItems(const parser_context_t *ctx, uint16_t *num_items) { + *num_items = 0; + return tx_display_numItems(num_items); } __Z_INLINE bool_t parser_areEqual(uint16_t tokenidx, char *expected) { @@ -63,7 +59,11 @@ __Z_INLINE bool_t parser_areEqual(uint16_t tokenidx, char *expected) { } int16_t len = parser_tx_obj.json.tokens[tokenidx].end - parser_tx_obj.json.tokens[tokenidx].start; - if (strlen(expected) != len) { + if (len < 0) { + return bool_false; + } + + if (strlen(expected) != (size_t) len) { return bool_false; } @@ -78,16 +78,16 @@ __Z_INLINE bool_t parser_areEqual(uint16_t tokenidx, char *expected) { } __Z_INLINE bool_t parser_isAmount(char *key) { - if (strcmp(parser_tx_obj.query.out_key, "fee/amount") == 0) + if (strcmp(key, "fee/amount") == 0) return bool_true; - if (strcmp(parser_tx_obj.query.out_key, "msgs/inputs/coins") == 0) + if (strcmp(key, "msgs/inputs/coins") == 0) return bool_true; - if (strcmp(parser_tx_obj.query.out_key, "msgs/outputs/coins") == 0) + if (strcmp(key, "msgs/outputs/coins") == 0) return bool_true; - if (strcmp(parser_tx_obj.query.out_key, "msgs/value/amount") == 0) + if (strcmp(key, "msgs/value/amount") == 0) return bool_true; return bool_false; @@ -101,7 +101,10 @@ __Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, amountToken++; } - uint16_t numElements = array_get_element_count(amountToken, &parser_tx_obj.json); + uint16_t numElements; + + CHECK_PARSER_ERR(array_get_element_count(&parser_tx_obj.json, amountToken, &numElements)); + if (numElements == 0) { *pageCount = 1; snprintf(outVal, outValLen, "Empty"); @@ -125,6 +128,10 @@ __Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, MEMZERO(bufferUI, sizeof(bufferUI)); const char *amountPtr = parser_tx_obj.tx + parser_tx_obj.json.tokens[amountToken + 2].start; + if (parser_tx_obj.json.tokens[amountToken + 2].start < 0) { + return parser_unexpected_buffer_end; + } + const int16_t amountLen = parser_tx_obj.json.tokens[amountToken + 2].end - parser_tx_obj.json.tokens[amountToken + 2].start; const char *denomPtr = parser_tx_obj.tx + parser_tx_obj.json.tokens[amountToken + 4].start; @@ -159,16 +166,21 @@ parser_error_t parser_getItem(const parser_context_t *ctx, uint8_t pageIdx, uint8_t *pageCount) { *pageCount = 0; - if (parser_getNumItems(ctx) == 0) { + MEMZERO(outKey, outKeyLen); + MEMZERO(outVal, outValLen); + + uint16_t numItems; + CHECK_PARSER_ERR(parser_getNumItems(ctx, &numItems)) + if (numItems == 0) { return parser_unexpected_number_items; } - if (displayIdx < 0 || displayIdx >= parser_getNumItems(ctx)) { + if (displayIdx < 0 || displayIdx >= numItems) { return parser_display_idx_out_of_range; } - uint16_t ret_value_token_index; - tx_display_query(displayIdx, outKey, outKeyLen, &ret_value_token_index); + uint16_t ret_value_token_index = 0; + CHECK_PARSER_ERR(tx_display_query(displayIdx, outKey, outKeyLen, &ret_value_token_index)); if (parser_isAmount(outKey)) { CHECK_PARSER_ERR(parser_formatAmount( @@ -182,7 +194,7 @@ parser_error_t parser_getItem(const parser_context_t *ctx, pageIdx, pageCount)) } - tx_display_make_friendly(); + CHECK_PARSER_ERR(tx_display_make_friendly()) if (*pageCount > 1) { size_t keyLen = strlen(outKey); diff --git a/app/src/tx_display.c b/app/src/tx_display.c index 91cad683..9858bb65 100644 --- a/app/src/tx_display.c +++ b/app/src/tx_display.c @@ -51,8 +51,10 @@ static const uint8_t root_max_level[NUM_REQUIRED_ROOT_PAGES] = { typedef struct { uint16_t total_item_count; + + uint8_t root_item_start_valid[NUM_REQUIRED_ROOT_PAGES]; // token where the root_item starts (negative for non-existing) - int16_t root_item_start_token_idx[NUM_REQUIRED_ROOT_PAGES]; + uint16_t root_item_start_token_idx[NUM_REQUIRED_ROOT_PAGES]; // number of items the root_item contains uint8_t root_item_number_subitems[NUM_REQUIRED_ROOT_PAGES]; } display_cache_t; @@ -62,6 +64,7 @@ display_cache_t display_cache; parser_error_t tx_display_readTx(parser_context_t *ctx, const uint8_t *data, size_t dataLen) { CHECK_PARSER_ERR(parser_init(ctx, data, dataLen)) CHECK_PARSER_ERR(_readTx(ctx, &parser_tx_obj)) + return parser_ok; } parser_error_t tx_indexRootFields() { @@ -74,24 +77,34 @@ parser_error_t tx_indexRootFields() { char tmp_key[20]; char tmp_val[40]; char tmp_val_ref[40]; + MEMZERO(&tmp_key, sizeof(tmp_key)); + MEMZERO(&tmp_val, sizeof(tmp_val)); + MEMZERO(&tmp_val_ref, sizeof(tmp_val_ref)); + parser_tx_obj.flags.msg_type_grouping = 1; parser_tx_obj.filter_msg_type_count = 0; for (int8_t root_item_idx = 0; root_item_idx < NUM_REQUIRED_ROOT_PAGES; root_item_idx++) { - const char *req_root_item_key = get_required_root_item( (root_item_e) root_item_idx); - const int16_t req_root_item_key_token_idx = object_get_value(&parser_tx_obj.json, - ROOT_TOKEN_INDEX, - req_root_item_key); + const char *req_root_item_key = get_required_root_item((root_item_e) root_item_idx); - // Remember root item start token - display_cache.root_item_start_token_idx[root_item_idx] = req_root_item_key_token_idx; + uint16_t req_root_item_key_token_idx = 0; + + parser_error_t err = object_get_value( + &parser_tx_obj.json, + ROOT_TOKEN_INDEX, + req_root_item_key, + &req_root_item_key_token_idx); - if (req_root_item_key_token_idx < 0) { + if (err == parser_no_data ) { continue; } + CHECK_PARSER_ERR(err) + + // Remember root item start token + display_cache.root_item_start_valid[root_item_idx] = 1; + display_cache.root_item_start_token_idx[root_item_idx] = req_root_item_key_token_idx; // Now count how many items can be found in this root item - parser_error_t err = parser_ok; int32_t current_item_idx = 0; while (err == parser_ok) { INIT_QUERY_CONTEXT(tmp_key, sizeof(tmp_key), @@ -102,18 +115,20 @@ parser_error_t tx_indexRootFields() { uint16_t ret_value_token_index; - err = tx_traverse_find(display_cache.root_item_start_token_idx[root_item_idx], - &ret_value_token_index); + err = tx_traverse_find( + display_cache.root_item_start_token_idx[root_item_idx], + &ret_value_token_index); if (err != parser_ok) { continue; } uint8_t pageCount; - CHECK_PARSER_ERR(tx_getToken(ret_value_token_index, - parser_tx_obj.query.out_val, - parser_tx_obj.query.out_key_len, - 0, &pageCount)) + CHECK_PARSER_ERR(tx_getToken( + ret_value_token_index, + parser_tx_obj.query.out_val, + parser_tx_obj.query.out_key_len, + 0, &pageCount)) if (root_item_idx == root_item_memo) { if (strlen(parser_tx_obj.query.out_val) == 0) { @@ -144,7 +159,7 @@ parser_error_t tx_indexRootFields() { current_item_idx++; } - if (err != parser_query_no_results) { + if (err != parser_query_no_results && err != parser_no_data) { return err; } @@ -156,26 +171,24 @@ parser_error_t tx_indexRootFields() { return parser_ok; } -uint16_t tx_display_numItems() { - parser_error_t err = tx_indexRootFields(); - if (err != parser_ok) { - return 0; - } +parser_error_t tx_display_numItems(uint16_t *num_items) { + CHECK_PARSER_ERR(tx_indexRootFields()) - uint16_t count = display_cache.total_item_count; + *num_items = display_cache.total_item_count; // Remove grouped items from list - if (parser_tx_obj.flags.msg_type_grouping == 1u && parser_tx_obj.filter_msg_type_count) { - count -= parser_tx_obj.filter_msg_type_count - 1; + if (parser_tx_obj.flags.msg_type_grouping == 1u && parser_tx_obj.filter_msg_type_count > 0) { + *num_items += 1; // we leave main type + *num_items -= parser_tx_obj.filter_msg_type_count; } - return count; + return parser_ok; } // This function assumes that the tx_ctx has been set properly parser_error_t tx_display_query(uint16_t displayIdx, char *outKey, uint16_t outKeyLen, uint16_t *ret_value_token_index) { - tx_indexRootFields(); + CHECK_PARSER_ERR(tx_indexRootFields()) if (displayIdx < 0 || displayIdx >= display_cache.total_item_count) { return parser_display_idx_out_of_range; @@ -205,7 +218,12 @@ parser_error_t tx_display_query(uint16_t displayIdx, parser_tx_obj.query._item_index_current = 0; parser_tx_obj.query.max_level = root_max_level[root_index]; - strncpy_s(outKey, get_required_root_item( (root_item_e) root_index), outKeyLen); + strncpy_s(outKey, get_required_root_item((root_item_e) root_index), outKeyLen); + + if (!display_cache.root_item_start_valid[root_index]) { + return parser_no_data; + } + CHECK_PARSER_ERR(tx_traverse_find( display_cache.root_item_start_token_idx[root_index], ret_value_token_index)) @@ -283,8 +301,8 @@ static const key_subst_t key_substitutions[] = { // {"msgs/value/validator_address", "Validator"}, // duplicated }; -void tx_display_make_friendly() { - tx_indexRootFields(); +parser_error_t tx_display_make_friendly() { + CHECK_PARSER_ERR(tx_indexRootFields()) // post process keys for (size_t i = 0; i < array_length(key_substitutions); i++) { @@ -293,5 +311,7 @@ void tx_display_make_friendly() { break; } } + + return parser_ok; } diff --git a/app/src/tx_display.h b/app/src/tx_display.h index ed0b0999..49d672e8 100644 --- a/app/src/tx_display.h +++ b/app/src/tx_display.h @@ -44,11 +44,9 @@ parser_error_t tx_display_readTx(parser_context_t *c, const uint8_t *data, size_t dataLen); -/// Return number of UI pages that we'll have for the current json transaction (only if the tx is valid) -/// \return number of pages (msg pages + 5 required) -uint16_t tx_display_numItems(); +parser_error_t tx_display_numItems(uint16_t *num_items); -void tx_display_make_friendly(); +parser_error_t tx_display_make_friendly(); //--------------------------------------------- diff --git a/app/src/tx_parser.c b/app/src/tx_parser.c index ab8a2662..0e4a8712 100644 --- a/app/src/tx_parser.c +++ b/app/src/tx_parser.c @@ -79,7 +79,7 @@ parser_error_t tx_getToken(uint16_t token_index, // empty strings are considered the first page *pageCount = 1; if (inLen > 0) { - for (int8_t i = 0; i < array_length(value_substitutions); i++) { + for (uint8_t i = 0; i < array_length(value_substitutions); i++) { const char *substStr = value_substitutions[i].str1; const size_t substStrLen = strlen(substStr); if (inLen == substStrLen && !MEMCMP(inValue, substStr, substStrLen)) { @@ -142,7 +142,10 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to const bool groupedField = strcmp("msgs/type", parser_tx_obj.query.out_key) == 0; const bool isMainIndex = parser_tx_obj.filter_msg_type_valid_idx != parser_tx_obj.query._item_index_current; - const bool skipItem = parser_tx_obj.flags.msg_type_grouping == 1u && groupedField && isMainIndex; + const bool skipItem = parser_tx_obj.flags.cache_valid == 1u && + parser_tx_obj.flags.msg_type_grouping == 1u && + groupedField && + isMainIndex; // Early bail out if (!skipItem && parser_tx_obj.query._item_index_current == parser_tx_obj.query.item_index) { @@ -158,14 +161,18 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to return parser_query_no_results; } - const int16_t el_count = object_get_element_count(root_token_index, &parser_tx_obj.json); + uint16_t el_count; + CHECK_PARSER_ERR(object_get_element_count(&parser_tx_obj.json, root_token_index, &el_count)) switch (token_type) { case JSMN_OBJECT: { const size_t key_len = strlen(parser_tx_obj.query.out_key); - for (int16_t i = 0; i < el_count; ++i) { - const int16_t key_index = object_get_nth_key(root_token_index, i, &parser_tx_obj.json); - const int16_t value_index = object_get_nth_value(root_token_index, i, &parser_tx_obj.json); + for (uint16_t i = 0; i < el_count; ++i) { + uint16_t key_index; + uint16_t value_index; + + CHECK_PARSER_ERR(object_get_nth_key(&parser_tx_obj.json, root_token_index, i, &key_index)); + CHECK_PARSER_ERR(object_get_nth_value(&parser_tx_obj.json, root_token_index, i, &value_index)); // Skip writing keys if we are actually exploring to count append_key_item(key_index); @@ -178,8 +185,9 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to parser_tx_obj.query.max_level++; parser_tx_obj.query.max_depth++; - if (err == parser_ok) - return err; + if (err == parser_ok) { + return parser_ok; + } *(parser_tx_obj.query.out_key + key_len) = 0; } @@ -187,8 +195,10 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to } case JSMN_ARRAY: { for (int16_t i = 0; i < el_count; ++i) { - const int16_t element_index = array_get_nth_element(root_token_index, i, - &parser_tx_obj.json); + uint16_t element_index; + CHECK_PARSER_ERR(array_get_nth_element(&parser_tx_obj.json, + root_token_index, i, + &element_index)); // When iterating along an array, // the level does not change but we need to count the recursion @@ -196,8 +206,9 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to err = tx_traverse_find(element_index, ret_value_token_index); parser_tx_obj.query.max_depth++; - if (err == parser_ok) - return err; + if (err == parser_ok) { + return parser_ok; + } } break; } diff --git a/app/src/tx_validate.c b/app/src/tx_validate.c index 50aadcf7..643738fa 100644 --- a/app/src/tx_validate.c +++ b/app/src/tx_validate.c @@ -89,11 +89,25 @@ int8_t dictionaries_sorted(parsed_json_t *json) { for (int i = 0; i < json->numberOfTokens; i++) { if (json->tokens[i].type == JSMN_OBJECT) { - const int count = object_get_element_count(i, json); + uint16_t count; + + if (object_get_element_count(json, i, &count) != parser_ok) { + return 0; + } + if (count > 1) { - int prev_token_index = object_get_nth_key(i, 0, json); + uint16_t prev_token_index; + if (object_get_nth_key(json, i, 0, &prev_token_index) != parser_ok) { + return 0; + } + for (int j = 1; j < count; j++) { - int next_token_index = object_get_nth_key(i, j, json); + uint16_t next_token_index; + + if (object_get_nth_key(json, i, j, &next_token_index) != parser_ok) { + return 0; + } + if (!is_sorted(prev_token_index, next_token_index, json)) { return 0; } @@ -114,47 +128,32 @@ parser_error_t tx_validate(parsed_json_t *json) { return parser_json_is_not_sorted; } - if (object_get_value( - json, - 0, - "chain_id") == -1) { + uint16_t token_index; + parser_error_t err; + + err = object_get_value(json, 0, "chain_id", &token_index); + if (err != parser_ok) return parser_json_missing_chain_id; - } - if (object_get_value( - json, - 0, - "sequence") == -1) { + err = object_get_value(json,0,"sequence", &token_index); + if (err != parser_ok) return parser_json_missing_sequence; - } - if (object_get_value( - json, - 0, - "fee") == -1) { + err = object_get_value(json,0,"fee", &token_index); + if (err != parser_ok) return parser_json_missing_fee; - } - if (object_get_value( - json, - 0, - "msgs") == -1) { + err = object_get_value(json,0,"msgs", &token_index); + if (err != parser_ok) return parser_json_missing_msgs; - } - if (object_get_value( - json, - 0, - "account_number") == -1) { + err = object_get_value(json,0,"account_number", &token_index); + if (err != parser_ok) return parser_json_missing_account_number; - } - if (object_get_value( - json, - 0, - "memo") == -1) { + err = object_get_value(json,0,"memo", &token_index); + if (err != parser_ok) return parser_json_missing_memo; - } return parser_ok; } diff --git a/deps/ledger-zxlib/.editorconfig b/deps/ledger-zxlib/.editorconfig index 4e47fb96..2d7db14f 100644 --- a/deps/ledger-zxlib/.editorconfig +++ b/deps/ledger-zxlib/.editorconfig @@ -10,3 +10,7 @@ insert_final_newline = true [*.{c,h,cpp,hpp}] indent_style = space indent_size = 4 + +[*.{yml,sh}] +indent_style = space +indent_size = 2 diff --git a/deps/ledger-zxlib/cmake/dockerized_build.mk b/deps/ledger-zxlib/cmake/dockerized_build.mk index 91ca574a..bd2f0362 100644 --- a/deps/ledger-zxlib/cmake/dockerized_build.mk +++ b/deps/ledger-zxlib/cmake/dockerized_build.mk @@ -61,7 +61,7 @@ check_python: deps: check_python @echo "Install dependencies" - $(CURDIR)/deps/ledger-zxlib/install_deps.sh + $(CURDIR)/deps/ledger-zxlib/scripts/install_deps.sh pull: docker pull $(DOCKER_IMAGE) diff --git a/deps/ledger-zxlib/include/bech32.h b/deps/ledger-zxlib/include/bech32.h index df8c2ad0..e3cb6ea7 100644 --- a/deps/ledger-zxlib/include/bech32.h +++ b/deps/ledger-zxlib/include/bech32.h @@ -19,13 +19,19 @@ extern "C" { #endif +#include "zxerror.h" + +#define MAX_INPUT_SIZE 64 + // the following function encodes directly from bytes // it will internally convert from 8 to 5 bits and return a // zero-terminated string in output -void bech32EncodeFromBytes(char *output, - const char *hrp, - const uint8_t *data, - size_t data_len); + +zxerr_t bech32EncodeFromBytes(char *out, + size_t out_len, + const char *hrp, + const uint8_t *in, + size_t in_len); #ifdef __cplusplus } diff --git a/deps/ledger-zxlib/include/zxerror.h b/deps/ledger-zxlib/include/zxerror.h new file mode 100644 index 00000000..e3edde76 --- /dev/null +++ b/deps/ledger-zxlib/include/zxerror.h @@ -0,0 +1,32 @@ +/******************************************************************************* +* (c) 2018 Zondax GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + zxerr_ok, + zxerr_buffer_too_small, + zxerr_out_of_bounds, + zxerr_encoding_failed, +} zxerr_t; + +#ifdef __cplusplus +} +#endif diff --git a/deps/ledger-zxlib/scripts/install_deps.sh b/deps/ledger-zxlib/scripts/install_deps.sh index 83c55b22..46b37651 100755 --- a/deps/ledger-zxlib/scripts/install_deps.sh +++ b/deps/ledger-zxlib/scripts/install_deps.sh @@ -21,10 +21,12 @@ case "${os_string}" in sudo apt-get install libusb-1.0.0 libudev-dev pip install -U setuptools pip install -U --no-cache ledgerblue ecpy + pip install -U conan ;; Darwin*) brew install libusb pip install -U ledgerblue ecpy + pip install -U conan ;; *) echo "OS not recognized" diff --git a/deps/ledger-zxlib/src/bech32.c b/deps/ledger-zxlib/src/bech32.c index ec1e85ee..741b4d28 100644 --- a/deps/ledger-zxlib/src/bech32.c +++ b/deps/ledger-zxlib/src/bech32.c @@ -16,22 +16,41 @@ #include #include +#include #include "bech32.h" #include "segwit_addr.h" #include "bittools.h" -void bech32EncodeFromBytes(char *output, - const char *hrp, - const uint8_t *data, - size_t data_len) { - output[0] = 0; - if (data_len > 128) { - return; +zxerr_t bech32EncodeFromBytes(char *out, + size_t out_len, + const char *hrp, + const uint8_t *in, + size_t in_len) { + MEMZERO(out, out_len); + + if (in_len > MAX_INPUT_SIZE) { + return zxerr_out_of_bounds; + } + + size_t hrplen = strlen(hrp); + // We set a lower bound to ensure this is safe + if (out_len < hrplen + (in_len*2) + 7) { + return zxerr_buffer_too_small; } - uint8_t tmp_data[128]; + // Overestimate required size *2==(8/4) instead of *(8/5) + uint8_t tmp_data[MAX_INPUT_SIZE * 2]; size_t tmp_size = 0; - convert_bits(tmp_data, &tmp_size, 5, data, data_len, 8, 0); - bech32_encode(output, hrp, tmp_data, tmp_size); + convert_bits(tmp_data, &tmp_size, 5, in, in_len, 8, 0); + if (tmp_size >= out_len) { + return zxerr_out_of_bounds; + } + + int err = bech32_encode(out, hrp, tmp_data, tmp_size); + if (err == 0) { + return zxerr_encoding_failed; + } + + return zxerr_ok; } diff --git a/deps/ledger-zxlib/tests/bech32.cpp b/deps/ledger-zxlib/tests/bech32.cpp index 5a682dde..4d8f204e 100644 --- a/deps/ledger-zxlib/tests/bech32.cpp +++ b/deps/ledger-zxlib/tests/bech32.cpp @@ -25,12 +25,47 @@ namespace { uint8_t data1[] = {1, 3, 5}; uint8_t data2[] = {1, 3, 5, 7, 9, 11, 13}; - bech32EncodeFromBytes(addr_out, hrp, data1, sizeof(data1)); + auto err = bech32EncodeFromBytes(addr_out, sizeof(addr_out), hrp, data1, sizeof(data1)); + ASSERT_EQ(err, zxerr_ok); std::cout << addr_out << std::endl; ASSERT_STREQ("zx1qypse825ac", addr_out); - bech32EncodeFromBytes(addr_out, hrp, data2, sizeof(data2)); + err = bech32EncodeFromBytes(addr_out, sizeof(addr_out), hrp, data2, sizeof(data2)); + ASSERT_EQ(err, zxerr_ok); std::cout << addr_out << std::endl; ASSERT_STREQ("zx1qyps2pcfpvx20dk22", addr_out); } + + TEST(BECH32, huge_input) { + char addr_out[200]; + const char *hrp = "zx"; + + auto data = std::vector(1000, 0x55); + + auto err = bech32EncodeFromBytes(addr_out, sizeof(addr_out), hrp, data.data(), data.size()); + ASSERT_EQ(err, zxerr_out_of_bounds); + + std::cout << addr_out << std::endl; + } + + TEST(BECH32, small_output) { + char addr_out[1000]; + const char *hrp = "zx"; + + auto data = std::vector(32, 0x55); + + MEMZERO(addr_out, sizeof(addr_out)); + + // declare size to be smaller + const size_t declared_size = 52; + + auto err = bech32EncodeFromBytes(addr_out, declared_size, hrp, data.data(), data.size()); + ASSERT_EQ(err, zxerr_buffer_too_small); + + for (int i = declared_size; i < sizeof(addr_out); i++) { + ASSERT_EQ(addr_out[i], 0); + } + + std::cout << addr_out << std::endl; + } } diff --git a/integration/.babelrc b/integration/.babelrc new file mode 100644 index 00000000..8806109a --- /dev/null +++ b/integration/.babelrc @@ -0,0 +1,13 @@ +{ + "presets": [ + "@babel/preset-env" + ], + "plugins": [ + [ + "@babel/plugin-transform-runtime", + { + "regenerator": true + } + ] + ] +} diff --git a/integration/.gitignore b/integration/.gitignore new file mode 100644 index 00000000..033833d9 --- /dev/null +++ b/integration/.gitignore @@ -0,0 +1,2 @@ +/node_modules +/yarn-error.log diff --git a/integration/jest.config.js b/integration/jest.config.js new file mode 100644 index 00000000..53ac6c65 --- /dev/null +++ b/integration/jest.config.js @@ -0,0 +1,31 @@ +// For a detailed explanation regarding each configuration property, visit: +// https://jestjs.io/docs/en/configuration.html + +module.exports = { + modulePaths: ["/src", "/tests"], + + moduleNameMapper: { + "^jest$": "/jest.js", + }, + + // Automatically clear mock calls and instances between every test + clearMocks: true, + + // The directory where Jest should output its coverage files + coverageDirectory: "coverage", + + // A list of paths to directories that Jest should use to search for files in + roots: [""], + + runner: "jest-serial-runner", + + // The test environment that will be used for testing + testEnvironment: "node", + + // The glob patterns Jest uses to detect test files + testMatch: [ + "**/__tests__/**/*.[jt]s?(x)", + "**/?(*.)+(spec|test).[tj]s?(x)", + "**/?(*.)+(ispec|test).[tj]s?(x)", + ], +}; diff --git a/integration/jest.js b/integration/jest.js new file mode 100644 index 00000000..2fa397e7 --- /dev/null +++ b/integration/jest.js @@ -0,0 +1,2 @@ +export default jest; +export const { expect, test } = global; diff --git a/integration/package.json b/integration/package.json new file mode 100644 index 00000000..c9fb88a5 --- /dev/null +++ b/integration/package.json @@ -0,0 +1,41 @@ +{ + "name": "integration-tests", + "author": "Zondax GmbH", + "license": "Apache-2.0", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "pull": "docker pull zondax/builder-bolos-emu:latest", + "test": "jest --detectOpenHandles" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Zondax/ledger-cosmos" + }, + "keywords": [ + "zondax" + ], + "dependencies": { + "@zondax/zemu": "^0.0.7", + "ledger-cosmos-js": "^2.1.7" + }, + "devDependencies": { + "@babel/cli": "^7.8.4", + "@babel/core": "^7.9.0", + "@babel/node": "^7.8.7", + "@babel/plugin-transform-runtime": "^7.9.0", + "@babel/preset-env": "^7.9.0", + "babel-eslint": "^10.1.0", + "babel-jest": "^25.2.6", + "eslint": "^6.8.0", + "eslint-config-airbnb-base": "^14.1.0", + "eslint-config-prettier": "^6.10.1", + "eslint-plugin-import": "^2.20.2", + "eslint-plugin-jest": "^23.8.2", + "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-vue": "^6.2.2", + "jest": "^25.2.7 ", + "jest-serial-runner": "^1.1.0 " + } +} diff --git a/integration/snapshots/.gitignore b/integration/snapshots/.gitignore new file mode 100644 index 00000000..33662f55 --- /dev/null +++ b/integration/snapshots/.gitignore @@ -0,0 +1 @@ +/* diff --git a/integration/tests/test.js b/integration/tests/test.js new file mode 100644 index 00000000..12d4ce28 --- /dev/null +++ b/integration/tests/test.js @@ -0,0 +1,125 @@ +import Zemu from "@zondax/zemu"; +import CosmosApp from "ledger-cosmos-js"; + +const Resolve = require("path").resolve; + +const APP_PATH = Resolve("../app/bin/app.elf"); + +console.log(APP_PATH); + +describe('Basic checks', function () { + it('can start and stop container', async function () { + const sim = new Zemu(APP_PATH); + try { + await sim.start({logging: true}); + } finally { + await sim.close(); + } + }); + + + it('get app version', async function () { + const sim = new Zemu(APP_PATH); + try { + await sim.start(); + const app = new CosmosApp(sim.getTransport()); + const version = await app.getVersion(); + + console.log(version) + } finally { + await sim.close(); + } + }); + + it('get app info', async function () { + const sim = new Zemu(APP_PATH); + try { + await sim.start(); + const app = new CosmosApp(sim.getTransport()); + const info = await app.appInfo(); + + console.log(info) + } finally { + await sim.close(); + } + }); + + const tx_str = { + "account_number": "108", + "chain_id": "cosmoshub-2", + "fee": { + "amount": [ + { + "amount": "600", + "denom": "uatom" + } + ], + "gas": "200000" + }, + "memo": "", + "msgs": [ + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl87jycah", + "validator_address": "cosmosvaloper1kn3wugetjuy4zetlq6wadchfhvu3x740ae6z6x" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl87jycah", + "validator_address": "cosmosvaloper1sjllsnramtg3ewxqwwrwjxfgc4n4ef9u2lcnj0" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl87jycah", + "validator_address": "cosmosvaloper1ey69r37gfxvxg62sh4r0ktpuc46pzjrm873ae8" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl87jycah", + "validator_address": "cosmosvaloper1648ynlpdw7fqa2axt0w2yp3fk542junl7rsvq6" + } + } + ], + "sequence": "106" + }; + + it('sign tx', async function () { + jest.setTimeout(60000); + const sim = new Zemu(APP_PATH); + + try { + await sim.start({ logging: true, X11: true }); + const app = new CosmosApp(sim.getTransport()); + + const path = [44, 118, 0, 0, 0]; + let tx = JSON.stringify(tx_str); + + // do not wait here.. + const signatureRequest = app.sign(path, tx); + + await Zemu.sleep(2000); + + // Reference window + await sim.snapshot(Resolve("snapshots/0.png")); + + for (let i = 1; i < 20; i++) { + await sim.clickRight(Resolve(`snapshots/${i}.png`)); + } + + await sim.close(); + + let signature = await signatureRequest; + console.log(signature); + + } finally { + await sim.close(); + } + }); +}); diff --git a/integration/yarn.lock b/integration/yarn.lock new file mode 100644 index 00000000..ae34c9a2 --- /dev/null +++ b/integration/yarn.lock @@ -0,0 +1,6205 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/cli@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.8.4.tgz#505fb053721a98777b2b175323ea4f090b7d3c1c" + integrity sha512-XXLgAm6LBbaNxaGhMAznXXaxtCWfuv6PIDJ9Alsy9JYTOh+j2jJz+L/162kkfU1j/pTSxK1xGmlwI4pdIMkoag== + dependencies: + commander "^4.0.1" + convert-source-map "^1.1.0" + fs-readdir-recursive "^1.1.0" + glob "^7.0.0" + lodash "^4.17.13" + make-dir "^2.1.0" + slash "^2.0.0" + source-map "^0.5.0" + optionalDependencies: + chokidar "^2.1.8" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" + integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== + dependencies: + "@babel/highlight" "^7.8.3" + +"@babel/compat-data@^7.8.6", "@babel/compat-data@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.0.tgz#04815556fc90b0c174abd2c0c1bb966faa036a6c" + integrity sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g== + dependencies: + browserslist "^4.9.1" + invariant "^2.2.4" + semver "^5.5.0" + +"@babel/core@^7.1.0", "@babel/core@^7.7.5", "@babel/core@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" + integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.9.0" + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helpers" "^7.9.0" + "@babel/parser" "^7.9.0" + "@babel/template" "^7.8.6" + "@babel/traverse" "^7.9.0" + "@babel/types" "^7.9.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.13" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.4.0", "@babel/generator@^7.9.0", "@babel/generator@^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.5.tgz#27f0917741acc41e6eaaced6d68f96c3fa9afaf9" + integrity sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ== + dependencies: + "@babel/types" "^7.9.5" + jsesc "^2.5.1" + lodash "^4.17.13" + source-map "^0.5.0" + +"@babel/helper-annotate-as-pure@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee" + integrity sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz#c84097a427a061ac56a1c30ebf54b7b22d241503" + integrity sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-compilation-targets@^7.8.7": + version "7.8.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz#dac1eea159c0e4bd46e309b5a1b04a66b53c1dde" + integrity sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw== + dependencies: + "@babel/compat-data" "^7.8.6" + browserslist "^4.9.1" + invariant "^2.2.4" + levenary "^1.1.1" + semver "^5.5.0" + +"@babel/helper-create-regexp-features-plugin@^7.8.3", "@babel/helper-create-regexp-features-plugin@^7.8.8": + version "7.8.8" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz#5d84180b588f560b7864efaeea89243e58312087" + integrity sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-regex" "^7.8.3" + regexpu-core "^4.7.0" + +"@babel/helper-define-map@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz#a0655cad5451c3760b726eba875f1cd8faa02c15" + integrity sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/types" "^7.8.3" + lodash "^4.17.13" + +"@babel/helper-explode-assignable-expression@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz#a728dc5b4e89e30fc2dfc7d04fa28a930653f982" + integrity sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw== + dependencies: + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-function-name@^7.8.3", "@babel/helper-function-name@^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz#2b53820d35275120e1874a82e5aabe1376920a5c" + integrity sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw== + dependencies: + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/types" "^7.9.5" + +"@babel/helper-get-function-arity@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" + integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-hoist-variables@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz#1dbe9b6b55d78c9b4183fc8cdc6e30ceb83b7134" + integrity sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-member-expression-to-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c" + integrity sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-module-imports@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498" + integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-module-transforms@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" + integrity sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA== + dependencies: + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.6" + "@babel/helper-simple-access" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/template" "^7.8.6" + "@babel/types" "^7.9.0" + lodash "^4.17.13" + +"@babel/helper-optimise-call-expression@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" + integrity sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" + integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== + +"@babel/helper-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965" + integrity sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ== + dependencies: + lodash "^4.17.13" + +"@babel/helper-remap-async-to-generator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz#273c600d8b9bf5006142c1e35887d555c12edd86" + integrity sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-wrap-function" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8" + integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.8.3" + "@babel/helper-optimise-call-expression" "^7.8.3" + "@babel/traverse" "^7.8.6" + "@babel/types" "^7.8.6" + +"@babel/helper-simple-access@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae" + integrity sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw== + dependencies: + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-split-export-declaration@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" + integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-validator-identifier@^7.9.0", "@babel/helper-validator-identifier@^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80" + integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g== + +"@babel/helper-wrap-function@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610" + integrity sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helpers@^7.9.0": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f" + integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA== + dependencies: + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.9.0" + "@babel/types" "^7.9.0" + +"@babel/highlight@^7.8.3": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" + integrity sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ== + dependencies: + "@babel/helper-validator-identifier" "^7.9.0" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/node@^7.8.7": + version "7.8.7" + resolved "https://registry.yarnpkg.com/@babel/node/-/node-7.8.7.tgz#4213ea99f0c86cc1cf460e61131e7acbb723e13a" + integrity sha512-o8cBT3cfRPLwoPh7VBYonSeZypIawGUeVfOIt1xSDgcDdirRGDPZ7/x+FLhhgQmKp3PKbz5Juh9/BNP4Jzrr9Q== + dependencies: + "@babel/register" "^7.8.3" + commander "^4.0.1" + core-js "^3.2.1" + lodash "^4.17.13" + node-environment-flags "^1.0.5" + regenerator-runtime "^0.13.4" + resolve "^1.13.1" + v8flags "^3.1.1" + +"@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0", "@babel/parser@^7.7.5", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0": + version "7.9.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8" + integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA== + +"@babel/plugin-proposal-async-generator-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f" + integrity sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-remap-async-to-generator" "^7.8.3" + "@babel/plugin-syntax-async-generators" "^7.8.0" + +"@babel/plugin-proposal-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054" + integrity sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + +"@babel/plugin-proposal-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz#da5216b238a98b58a1e05d6852104b10f9a70d6b" + integrity sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.0" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2" + integrity sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + +"@babel/plugin-proposal-numeric-separator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz#5d6769409699ec9b3b68684cd8116cedff93bad8" + integrity sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + +"@babel/plugin-proposal-object-rest-spread@^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.5.tgz#3fd65911306d8746014ec0d0cf78f0e39a149116" + integrity sha512-VP2oXvAf7KCYTthbUHwBlewbl1Iq059f6seJGsxMizaCdgHIeczOr7FBqELhSqfkIl04Fi8okzWzl63UKbQmmg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-transform-parameters" "^7.9.5" + +"@babel/plugin-proposal-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz#9dee96ab1650eed88646ae9734ca167ac4a9c5c9" + integrity sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + +"@babel/plugin-proposal-optional-chaining@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58" + integrity sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + +"@babel/plugin-proposal-unicode-property-regex@^7.4.4", "@babel/plugin-proposal-unicode-property-regex@^7.8.3": + version "7.8.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz#ee3a95e90cdc04fe8cd92ec3279fa017d68a0d1d" + integrity sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.8" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-async-generators@^7.8.0": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.0.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-dynamic-import@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-json-strings@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.0", "@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz#0e3fb63e09bea1b11e96467271c8308007e7c41f" + integrity sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz#3acdece695e6b13aaf57fc291d1a800950c71391" + integrity sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-arrow-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz#82776c2ed0cd9e1a49956daeb896024c9473b8b6" + integrity sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-async-to-generator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz#4308fad0d9409d71eafb9b1a6ee35f9d64b64086" + integrity sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ== + dependencies: + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-remap-async-to-generator" "^7.8.3" + +"@babel/plugin-transform-block-scoped-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz#437eec5b799b5852072084b3ae5ef66e8349e8a3" + integrity sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-block-scoping@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a" + integrity sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + lodash "^4.17.13" + +"@babel/plugin-transform-classes@^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz#800597ddb8aefc2c293ed27459c1fcc935a26c2c" + integrity sha512-x2kZoIuLC//O5iA7PEvecB105o7TLzZo8ofBVhP79N+DO3jaX+KYfww9TQcfBEZD0nikNyYcGB1IKtRq36rdmg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-define-map" "^7.8.3" + "@babel/helper-function-name" "^7.9.5" + "@babel/helper-optimise-call-expression" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.6" + "@babel/helper-split-export-declaration" "^7.8.3" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz#96d0d28b7f7ce4eb5b120bb2e0e943343c86f81b" + integrity sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-destructuring@^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.9.5.tgz#72c97cf5f38604aea3abf3b935b0e17b1db76a50" + integrity sha512-j3OEsGel8nHL/iusv/mRd5fYZ3DrOxWC82x0ogmdN/vHfAP4MYw+AFKYanzWlktNwikKvlzUV//afBW5FTp17Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-dotall-regex@^7.4.4", "@babel/plugin-transform-dotall-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz#c3c6ec5ee6125c6993c5cbca20dc8621a9ea7a6e" + integrity sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-duplicate-keys@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz#8d12df309aa537f272899c565ea1768e286e21f1" + integrity sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-exponentiation-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz#581a6d7f56970e06bf51560cd64f5e947b70d7b7" + integrity sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-for-of@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz#0f260e27d3e29cd1bb3128da5e76c761aa6c108e" + integrity sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-function-name@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz#279373cb27322aaad67c2683e776dfc47196ed8b" + integrity sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz#aef239823d91994ec7b68e55193525d76dbd5dc1" + integrity sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-member-expression-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz#963fed4b620ac7cbf6029c755424029fa3a40410" + integrity sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-modules-amd@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz#19755ee721912cf5bb04c07d50280af3484efef4" + integrity sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q== + dependencies: + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/plugin-transform-modules-commonjs@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz#e3e72f4cbc9b4a260e30be0ea59bdf5a39748940" + integrity sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g== + dependencies: + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-simple-access" "^7.8.3" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/plugin-transform-modules-systemjs@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz#e9fd46a296fc91e009b64e07ddaa86d6f0edeb90" + integrity sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ== + dependencies: + "@babel/helper-hoist-variables" "^7.8.3" + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/plugin-transform-modules-umd@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz#e909acae276fec280f9b821a5f38e1f08b480697" + integrity sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ== + dependencies: + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz#a2a72bffa202ac0e2d0506afd0939c5ecbc48c6c" + integrity sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + +"@babel/plugin-transform-new-target@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz#60cc2ae66d85c95ab540eb34babb6434d4c70c43" + integrity sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-object-super@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz#ebb6a1e7a86ffa96858bd6ac0102d65944261725" + integrity sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.3" + +"@babel/plugin-transform-parameters@^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz#173b265746f5e15b2afe527eeda65b73623a0795" + integrity sha512-0+1FhHnMfj6lIIhVvS4KGQJeuhe1GI//h5uptK4PvLt+BGBxsoUJbd3/IW002yk//6sZPlFgsG1hY6OHLcy6kA== + dependencies: + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-property-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz#33194300d8539c1ed28c62ad5087ba3807b98263" + integrity sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-regenerator@^7.8.7": + version "7.8.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz#5e46a0dca2bee1ad8285eb0527e6abc9c37672f8" + integrity sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA== + dependencies: + regenerator-transform "^0.14.2" + +"@babel/plugin-transform-reserved-words@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz#9a0635ac4e665d29b162837dd3cc50745dfdf1f5" + integrity sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-runtime@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.0.tgz#45468c0ae74cc13204e1d3b1f4ce6ee83258af0b" + integrity sha512-pUu9VSf3kI1OqbWINQ7MaugnitRss1z533436waNXp+0N3ur3zfut37sXiQMxkuCF4VUjwZucen/quskCh7NHw== + dependencies: + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + resolve "^1.8.1" + semver "^5.5.1" + +"@babel/plugin-transform-shorthand-properties@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8" + integrity sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8" + integrity sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-sticky-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz#be7a1290f81dae767475452199e1f76d6175b100" + integrity sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-regex" "^7.8.3" + +"@babel/plugin-transform-template-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz#7bfa4732b455ea6a43130adc0ba767ec0e402a80" + integrity sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-typeof-symbol@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz#ede4062315ce0aaf8a657a920858f1a2f35fc412" + integrity sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-unicode-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad" + integrity sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/preset-env@^7.9.0": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.5.tgz#8ddc76039bc45b774b19e2fc548f6807d8a8919f" + integrity sha512-eWGYeADTlPJH+wq1F0wNfPbVS1w1wtmMJiYk55Td5Yu28AsdR9AsC97sZ0Qq8fHqQuslVSIYSGJMcblr345GfQ== + dependencies: + "@babel/compat-data" "^7.9.0" + "@babel/helper-compilation-targets" "^7.8.7" + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-proposal-async-generator-functions" "^7.8.3" + "@babel/plugin-proposal-dynamic-import" "^7.8.3" + "@babel/plugin-proposal-json-strings" "^7.8.3" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-proposal-numeric-separator" "^7.8.3" + "@babel/plugin-proposal-object-rest-spread" "^7.9.5" + "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" + "@babel/plugin-proposal-optional-chaining" "^7.9.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" + "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.8.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + "@babel/plugin-transform-arrow-functions" "^7.8.3" + "@babel/plugin-transform-async-to-generator" "^7.8.3" + "@babel/plugin-transform-block-scoped-functions" "^7.8.3" + "@babel/plugin-transform-block-scoping" "^7.8.3" + "@babel/plugin-transform-classes" "^7.9.5" + "@babel/plugin-transform-computed-properties" "^7.8.3" + "@babel/plugin-transform-destructuring" "^7.9.5" + "@babel/plugin-transform-dotall-regex" "^7.8.3" + "@babel/plugin-transform-duplicate-keys" "^7.8.3" + "@babel/plugin-transform-exponentiation-operator" "^7.8.3" + "@babel/plugin-transform-for-of" "^7.9.0" + "@babel/plugin-transform-function-name" "^7.8.3" + "@babel/plugin-transform-literals" "^7.8.3" + "@babel/plugin-transform-member-expression-literals" "^7.8.3" + "@babel/plugin-transform-modules-amd" "^7.9.0" + "@babel/plugin-transform-modules-commonjs" "^7.9.0" + "@babel/plugin-transform-modules-systemjs" "^7.9.0" + "@babel/plugin-transform-modules-umd" "^7.9.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" + "@babel/plugin-transform-new-target" "^7.8.3" + "@babel/plugin-transform-object-super" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.9.5" + "@babel/plugin-transform-property-literals" "^7.8.3" + "@babel/plugin-transform-regenerator" "^7.8.7" + "@babel/plugin-transform-reserved-words" "^7.8.3" + "@babel/plugin-transform-shorthand-properties" "^7.8.3" + "@babel/plugin-transform-spread" "^7.8.3" + "@babel/plugin-transform-sticky-regex" "^7.8.3" + "@babel/plugin-transform-template-literals" "^7.8.3" + "@babel/plugin-transform-typeof-symbol" "^7.8.4" + "@babel/plugin-transform-unicode-regex" "^7.8.3" + "@babel/preset-modules" "^0.1.3" + "@babel/types" "^7.9.5" + browserslist "^4.9.1" + core-js-compat "^3.6.2" + invariant "^2.2.2" + levenary "^1.1.1" + semver "^5.5.0" + +"@babel/preset-modules@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72" + integrity sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/register@^7.8.3": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.9.0.tgz#02464ede57548bddbb5e9f705d263b7c3f43d48b" + integrity sha512-Tv8Zyi2J2VRR8g7pC5gTeIN8Ihultbmk0ocyNz8H2nEZbmhp1N6q0A1UGsQbDvGP/sNinQKUHf3SqXwqjtFv4Q== + dependencies: + find-cache-dir "^2.0.0" + lodash "^4.17.13" + make-dir "^2.1.0" + pirates "^4.0.0" + source-map-support "^0.5.16" + +"@babel/runtime@^7.7.4", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" + integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.4.0", "@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" + integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/parser" "^7.8.6" + "@babel/types" "^7.8.6" + +"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.4", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2" + integrity sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.9.5" + "@babel/helper-function-name" "^7.9.5" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/parser" "^7.9.0" + "@babel/types" "^7.9.5" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.13" + +"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444" + integrity sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg== + dependencies: + "@babel/helper-validator-identifier" "^7.9.5" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cnakazawa/watch@^1.0.3": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" + integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== + dependencies: + exec-sh "^0.3.2" + minimist "^1.2.0" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz#10602de5570baea82f8afbfa2630b24e7a8cfe5b" + integrity sha512-ZR0rq/f/E4f4XcgnDvtMWXCUJpi8eO0rssVhmztsZqLIEFA9UUP9zmpE0VxlM+kv/E1ul2I876Fwil2ayptDVg== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" + integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== + +"@jest/console@^24.7.1", "@jest/console@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.9.0.tgz#79b1bc06fb74a8cfb01cbdedf945584b1b9707f0" + integrity sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ== + dependencies: + "@jest/source-map" "^24.9.0" + chalk "^2.0.1" + slash "^2.0.0" + +"@jest/console@^25.2.6": + version "25.2.6" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-25.2.6.tgz#f594847ec8aef3cf27f448abe97e76e491212e97" + integrity sha512-bGp+0PicZVCEhb+ifnW9wpKWONNdkhtJsRE7ap729hiAfTvCN6VhGx0s/l/V/skA2pnyqq+N/7xl9ZWfykDpsg== + dependencies: + "@jest/source-map" "^25.2.6" + chalk "^3.0.0" + jest-util "^25.2.6" + slash "^3.0.0" + +"@jest/core@^25.2.7": + version "25.2.7" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-25.2.7.tgz#58d697687e94ee644273d15e4eed6a20e27187cd" + integrity sha512-Nd6ELJyR+j0zlwhzkfzY70m04hAur0VnMwJXVe4VmmD/SaQ6DEyal++ERQ1sgyKIKKEqRuui6k/R0wHLez4P+g== + dependencies: + "@jest/console" "^25.2.6" + "@jest/reporters" "^25.2.6" + "@jest/test-result" "^25.2.6" + "@jest/transform" "^25.2.6" + "@jest/types" "^25.2.6" + ansi-escapes "^4.2.1" + chalk "^3.0.0" + exit "^0.1.2" + graceful-fs "^4.2.3" + jest-changed-files "^25.2.6" + jest-config "^25.2.7" + jest-haste-map "^25.2.6" + jest-message-util "^25.2.6" + jest-regex-util "^25.2.6" + jest-resolve "^25.2.6" + jest-resolve-dependencies "^25.2.7" + jest-runner "^25.2.7" + jest-runtime "^25.2.7" + jest-snapshot "^25.2.7" + jest-util "^25.2.6" + jest-validate "^25.2.6" + jest-watcher "^25.2.7" + micromatch "^4.0.2" + p-each-series "^2.1.0" + realpath-native "^2.0.0" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.9.0.tgz#21e3afa2d65c0586cbd6cbefe208bafade44ab18" + integrity sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ== + dependencies: + "@jest/fake-timers" "^24.9.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + jest-mock "^24.9.0" + +"@jest/environment@^25.2.6": + version "25.2.6" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-25.2.6.tgz#8f7931e79abd81893ce88b7306f0cc4744835000" + integrity sha512-17WIw+wCb9drRNFw1hi8CHah38dXVdOk7ga9exThhGtXlZ9mK8xH4DjSB9uGDGXIWYSHmrxoyS6KJ7ywGr7bzg== + dependencies: + "@jest/fake-timers" "^25.2.6" + "@jest/types" "^25.2.6" + jest-mock "^25.2.6" + +"@jest/fake-timers@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.9.0.tgz#ba3e6bf0eecd09a636049896434d306636540c93" + integrity sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A== + dependencies: + "@jest/types" "^24.9.0" + jest-message-util "^24.9.0" + jest-mock "^24.9.0" + +"@jest/fake-timers@^25.2.6": + version "25.2.6" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.2.6.tgz#239dbde3f56badf7d05bcf888f5d669296077cad" + integrity sha512-A6qtDIA2zg/hVgUJJYzQSHFBIp25vHdSxW/s4XmTJAYxER6eL0NQdQhe4+232uUSviKitubHGXXirt5M7blPiA== + dependencies: + "@jest/types" "^25.2.6" + jest-message-util "^25.2.6" + jest-mock "^25.2.6" + jest-util "^25.2.6" + lolex "^5.0.0" + +"@jest/reporters@^25.2.6": + version "25.2.6" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-25.2.6.tgz#6d87e40fb15adb69e22bb83aa02a4d88b2253b5f" + integrity sha512-DRMyjaxcd6ZKctiXNcuVObnPwB1eUs7xrUVu0J2V0p5/aZJei5UM9GL3s/bmN4hRV8Mt3zXh+/9X2o0Q4ClZIA== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^25.2.6" + "@jest/test-result" "^25.2.6" + "@jest/transform" "^25.2.6" + "@jest/types" "^25.2.6" + chalk "^3.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.2" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^4.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.0" + jest-haste-map "^25.2.6" + jest-resolve "^25.2.6" + jest-util "^25.2.6" + jest-worker "^25.2.6" + slash "^3.0.0" + source-map "^0.6.0" + string-length "^3.1.0" + terminal-link "^2.0.0" + v8-to-istanbul "^4.0.1" + optionalDependencies: + node-notifier "^6.0.0" + +"@jest/source-map@^24.3.0", "@jest/source-map@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.9.0.tgz#0e263a94430be4b41da683ccc1e6bffe2a191714" + integrity sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.1.15" + source-map "^0.6.0" + +"@jest/source-map@^25.2.6": + version "25.2.6" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-25.2.6.tgz#0ef2209514c6d445ebccea1438c55647f22abb4c" + integrity sha512-VuIRZF8M2zxYFGTEhkNSvQkUKafQro4y+mwUxy5ewRqs5N/ynSFUODYp3fy1zCnbCMy1pz3k+u57uCqx8QRSQQ== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.2.3" + source-map "^0.6.0" + +"@jest/test-result@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.9.0.tgz#11796e8aa9dbf88ea025757b3152595ad06ba0ca" + integrity sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA== + dependencies: + "@jest/console" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/istanbul-lib-coverage" "^2.0.0" + +"@jest/test-result@^25.2.6": + version "25.2.6" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-25.2.6.tgz#f6082954955313eb96f6cabf9fb14f8017826916" + integrity sha512-gmGgcF4qz/pkBzyfJuVHo2DA24kIgVQ5Pf/VpW4QbyMLSegi8z+9foSZABfIt5se6k0fFj/3p/vrQXdaOgit0w== + dependencies: + "@jest/console" "^25.2.6" + "@jest/types" "^25.2.6" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz#f8f334f35b625a4f2f355f2fe7e6036dad2e6b31" + integrity sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A== + dependencies: + "@jest/test-result" "^24.9.0" + jest-haste-map "^24.9.0" + jest-runner "^24.9.0" + jest-runtime "^24.9.0" + +"@jest/test-sequencer@^25.2.7": + version "25.2.7" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-25.2.7.tgz#e4331f7b4850e34289b9a5c8ec8a2c03b400da8f" + integrity sha512-s2uYGOXONDSTJQcZJ9A3Zkg3hwe53RlX1HjUNqjUy3HIqwgwCKJbnAKYsORPbhxXi3ARMKA7JNBi9arsTxXoYw== + dependencies: + "@jest/test-result" "^25.2.6" + jest-haste-map "^25.2.6" + jest-runner "^25.2.7" + jest-runtime "^25.2.7" + +"@jest/transform@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.9.0.tgz#4ae2768b296553fadab09e9ec119543c90b16c56" + integrity sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^24.9.0" + babel-plugin-istanbul "^5.1.0" + chalk "^2.0.1" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.1.15" + jest-haste-map "^24.9.0" + jest-regex-util "^24.9.0" + jest-util "^24.9.0" + micromatch "^3.1.10" + pirates "^4.0.1" + realpath-native "^1.1.0" + slash "^2.0.0" + source-map "^0.6.1" + write-file-atomic "2.4.1" + +"@jest/transform@^25.2.6": + version "25.2.6" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-25.2.6.tgz#007fd946dedf12d2a9eb5d4154faf1991d5f61ff" + integrity sha512-rZnjCjZf9avPOf9q/w9RUZ9Uc29JmB53uIXNJmNz04QbDMD5cR/VjfikiMKajBsXe2vnFl5sJ4RTt+9HPicauQ== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^25.2.6" + babel-plugin-istanbul "^6.0.0" + chalk "^3.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.3" + jest-haste-map "^25.2.6" + jest-regex-util "^25.2.6" + jest-util "^25.2.6" + micromatch "^4.0.2" + pirates "^4.0.1" + realpath-native "^2.0.0" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + +"@jest/types@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59" + integrity sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^13.0.0" + +"@jest/types@^25.2.6": + version "25.2.6" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.2.6.tgz#c12f44af9bed444438091e4b59e7ed05f8659cb6" + integrity sha512-myJTTV37bxK7+3NgKc4Y/DlQ5q92/NOwZsZ+Uch7OXdElxOg61QYc72fPYNAjlvbnJ2YvbXLamIsa9tj48BmyQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^15.0.0" + chalk "^3.0.0" + +"@ledgerhq/devices@^4.78.0": + version "4.78.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-4.78.0.tgz#149b572f0616096e2bd5eb14ce14d0061c432be6" + integrity sha512-tWKS5WM/UU82czihnVjRwz9SXNTQzWjGJ/7+j/xZ70O86nlnGJ1aaFbs5/WTzfrVKpOKgj1ZoZkAswX67i/JTw== + dependencies: + "@ledgerhq/errors" "^4.78.0" + "@ledgerhq/logs" "^4.72.0" + rxjs "^6.5.3" + +"@ledgerhq/devices@^5.12.0": + version "5.12.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-5.12.0.tgz#974c6a418b80b3917a3a7527e80e82886a0b3580" + integrity sha512-6dduN4KkSMtBUmxLUBlTH7RXwnc0+1aiCjZMl2unL8CY9u4hGNzu1eN7y2SnR0V5PQQrDXCbQ+MAKmFJa0eR6w== + dependencies: + "@ledgerhq/errors" "^5.12.0" + "@ledgerhq/logs" "^5.11.0" + rxjs "^6.5.4" + +"@ledgerhq/errors@^4.78.0": + version "4.78.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-4.78.0.tgz#23daf3af54d03b1bda3e616002b555da1bdb705a" + integrity sha512-FX6zHZeiNtegBvXabK6M5dJ+8OV8kQGGaGtuXDeK/Ss5EmG4Ltxc6Lnhe8hiHpm9pCHtktOsnUVL7IFBdHhYUg== + +"@ledgerhq/errors@^5.12.0": + version "5.12.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-5.12.0.tgz#2fdfb08ee54c8bb73e0074835c24243cedf4583f" + integrity sha512-jF5Km9Zh9Swt9tsNoFtB4tfcb7PmK/tubx+Z9Kt8ivJ9pF8+zthlTNr6sL1hBsfkjln+gYqLIHSUWcitlMN/Kw== + +"@ledgerhq/hw-transport-http@^5.12.0": + version "5.12.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-http/-/hw-transport-http-5.12.0.tgz#19e8250e2d72a32c59bbfb9b85892b01ebbbca7c" + integrity sha512-SciMHeztVNjXwjwxI7JX4jdFmmZgTmGEHMRKqJXSA/ZUXp3mfeLy9EkghpO7YFS3x/s4BlbN7u/fn3FAOVKnFQ== + dependencies: + "@ledgerhq/errors" "^5.12.0" + "@ledgerhq/hw-transport" "^5.12.0" + "@ledgerhq/logs" "^5.11.0" + axios "^0.19.0" + ws "6" + +"@ledgerhq/hw-transport@^4.77.0": + version "4.78.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-4.78.0.tgz#714786658e1f2fbc0569e06e2abf8d15d310d931" + integrity sha512-xQu16OMPQjFYLjqCysij+8sXtdWv2YLxPrB6FoLvEWGTlQ7yL1nUBRQyzyQtWIYqZd4THQowQmzm1VjxuN6SZw== + dependencies: + "@ledgerhq/devices" "^4.78.0" + "@ledgerhq/errors" "^4.78.0" + events "^3.0.0" + +"@ledgerhq/hw-transport@^5.12.0": + version "5.12.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-5.12.0.tgz#1fc6ca7e96851e4ffbb550c2980dc676abf4ed5e" + integrity sha512-TsGLDY4anie4f3nMPPC4defJAVfuakkTOKntKcPhnGBGFO4HhJGlpZ5LWx50RhYGP7c86QpwPhmxsnx8sLIJjQ== + dependencies: + "@ledgerhq/devices" "^5.12.0" + "@ledgerhq/errors" "^5.12.0" + events "^3.1.0" + +"@ledgerhq/logs@^4.72.0": + version "4.72.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-4.72.0.tgz#43df23af013ad1135407e5cf33ca6e4c4c7708d5" + integrity sha512-o+TYF8vBcyySRsb2kqBDv/KMeme8a2nwWoG+lAWzbDmWfb2/MrVWYCVYDYvjXdSoI/Cujqy1i0gIDrkdxa9chA== + +"@ledgerhq/logs@^5.11.0": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.11.0.tgz#9ad2aefceeef48cf9d77972f67e63ba478dd04cc" + integrity sha512-NiFDdxLU/z1VGQy0/cbpv7UScMDQ/rU8SznqILSHYTnhK2xvvNFTUkd1W2mpmf9E/hzXFI0UAOieLQ44qovX3w== + +"@sinonjs/commons@^1.7.0": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.1.tgz#da5fd19a5f71177a53778073978873964f49acf1" + integrity sha512-Debi3Baff1Qu1Unc3mjJ96MgpbwTn43S1+9yJ0llWygPwDNu2aaWBD6yc9y/Z8XDRNhx7U+u2UDg2OGQXkclUQ== + dependencies: + type-detect "4.0.8" + +"@types/babel__core@^7.1.0": + version "7.1.7" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.7.tgz#1dacad8840364a57c98d0dd4855c6dd3752c6b89" + integrity sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.1" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.1.tgz#4901767b397e8711aeb99df8d396d7ba7b7f0e04" + integrity sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.2.tgz#4ff63d6b52eddac1de7b975a5223ed32ecea9307" + integrity sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.0.10" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.10.tgz#d9a99f017317d9b3d1abc2ced45d3bca68df0daf" + integrity sha512-74fNdUGrWsgIB/V9kTO5FGHPWYY6Eqn+3Z7L6Hc4e/BxjYV7puvBqp5HwsVYYfLm6iURYBNCx4Ut37OF9yitCw== + dependencies: + "@babel/types" "^7.3.0" + +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" + integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz#7a8cbf6a406f36c8add871625b278eaf0b0d255a" + integrity sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA== + dependencies: + "@types/istanbul-lib-coverage" "*" + "@types/istanbul-lib-report" "*" + +"@types/json-schema@^7.0.3": + version "7.0.4" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" + integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA== + +"@types/prettier@^1.19.0": + version "1.19.1" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f" + integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ== + +"@types/stack-utils@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" + integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== + +"@types/yargs-parser@*": + version "15.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" + integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== + +"@types/yargs@^13.0.0": + version "13.0.8" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.8.tgz#a38c22def2f1c2068f8971acb3ea734eb3c64a99" + integrity sha512-XAvHLwG7UQ+8M4caKIH0ZozIOYay5fQkAgyIXegXT9jPtdIGdhga+sUEdAr1CiG46aB+c64xQEYyEzlwWVTNzA== + dependencies: + "@types/yargs-parser" "*" + +"@types/yargs@^15.0.0": + version "15.0.4" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.4.tgz#7e5d0f8ca25e9d5849f2ea443cf7c402decd8299" + integrity sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/experimental-utils@^2.5.0": + version "2.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.27.0.tgz#801a952c10b58e486c9a0b36cf21e2aab1e9e01a" + integrity sha512-vOsYzjwJlY6E0NJRXPTeCGqjv5OHgRU1kzxHKWJVPjDYGbPgLudBXjIlc+OD1hDBZ4l1DLbOc5VjofKahsu9Jw== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/typescript-estree" "2.27.0" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/typescript-estree@2.27.0": + version "2.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.27.0.tgz#a288e54605412da8b81f1660b56c8b2e42966ce8" + integrity sha512-t2miCCJIb/FU8yArjAvxllxbTiyNqaXJag7UOpB5DVoM3+xnjeOngtqlJkLRnMtzaRcJhe3CIR9RmL40omubhg== + dependencies: + debug "^4.1.1" + eslint-visitor-keys "^1.1.0" + glob "^7.1.6" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^6.3.0" + tsutils "^3.17.1" + +"@zondax/zemu@^0.0.7": + version "0.0.7" + resolved "https://registry.yarnpkg.com/@zondax/zemu/-/zemu-0.0.7.tgz#ac58f8d1a65ba378989cedd97b9b88acf384c4e5" + integrity sha512-Mk86LHEYQ4mu4TEMFfDvGub4e10bPVqYZGet4/P5cB0rQA3Z6N/vMNTptc1WkVFiHV6yQ+WDejLKjFWZpqMZXA== + dependencies: + "@babel/runtime" "^7.9.2" + "@ledgerhq/hw-transport" "^5.12.0" + "@ledgerhq/hw-transport-http" "^5.12.0" + dockerode "^3.2.0" + path "^0.12.7" + pngjs "^3.4.0" + rfb2 "^0.2.2" + sleep "^6.1.0" + +abab@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" + integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg== + +acorn-globals@^4.1.0, acorn-globals@^4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" + integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== + dependencies: + acorn "^6.0.1" + acorn-walk "^6.0.1" + +acorn-jsx@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" + integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== + +acorn-walk@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" + integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== + +acorn@^5.5.3: + version "5.7.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" + integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== + +acorn@^6.0.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" + integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== + +acorn@^7.1.0, acorn@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" + integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg== + +ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5: + version "6.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" + integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" + integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== + dependencies: + type-fest "^0.11.0" + +ansi-regex@^4.0.0, ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +anymatch@^3.0.3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +array-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" + integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= + +array-includes@^3.0.3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" + integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0" + is-string "^1.0.5" + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +array.prototype.flat@^1.2.1: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" + integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +asn1@~0.2.0, asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +async-each@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" + integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== + +axios@^0.19.0: + version "0.19.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" + integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== + dependencies: + follow-redirects "1.5.10" + +babel-eslint@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" + integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" + eslint-visitor-keys "^1.0.0" + resolve "^1.12.0" + +babel-jest@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.9.0.tgz#3fc327cb8467b89d14d7bc70e315104a783ccd54" + integrity sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw== + dependencies: + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/babel__core" "^7.1.0" + babel-plugin-istanbul "^5.1.0" + babel-preset-jest "^24.9.0" + chalk "^2.4.2" + slash "^2.0.0" + +babel-jest@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-25.2.6.tgz#fe67ff4d0db3626ca8082da8881dd5e84e07ae75" + integrity sha512-MDJOAlwtIeIQiGshyX0d2PxTbV73xZMpNji40ivVTPQOm59OdRR9nYCkffqI7ugtsK4JR98HgNKbDbuVf4k5QQ== + dependencies: + "@jest/transform" "^25.2.6" + "@jest/types" "^25.2.6" + "@types/babel__core" "^7.1.0" + babel-plugin-istanbul "^6.0.0" + babel-preset-jest "^25.2.6" + chalk "^3.0.0" + slash "^3.0.0" + +babel-plugin-dynamic-import-node@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" + integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ== + dependencies: + object.assign "^4.1.0" + +babel-plugin-istanbul@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz#df4ade83d897a92df069c4d9a25cf2671293c854" + integrity sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + find-up "^3.0.0" + istanbul-lib-instrument "^3.3.0" + test-exclude "^5.2.3" + +babel-plugin-istanbul@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765" + integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^4.0.0" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz#4f837091eb407e01447c8843cbec546d0002d756" + integrity sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw== + dependencies: + "@types/babel__traverse" "^7.0.6" + +babel-plugin-jest-hoist@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.2.6.tgz#2af07632b8ac7aad7d414c1e58425d5fc8e84909" + integrity sha512-qE2xjMathybYxjiGFJg0mLFrz0qNp83aNZycWDY/SuHiZNq+vQfRQtuINqyXyue1ELd8Rd+1OhFSLjms8msMbw== + dependencies: + "@types/babel__traverse" "^7.0.6" + +babel-preset-jest@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz#192b521e2217fb1d1f67cf73f70c336650ad3cdc" + integrity sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg== + dependencies: + "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + babel-plugin-jest-hoist "^24.9.0" + +babel-preset-jest@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-25.2.6.tgz#5d3f7c99e2a8508d61775c9d68506d143b7f71b5" + integrity sha512-Xh2eEAwaLY9+SyMt/xmGZDnXTW/7pSaBPG0EMo7EuhvosFKVWYB6CqwYD31DaEQuoTL090oDZ0FEqygffGRaSQ== + dependencies: + "@babel/plugin-syntax-bigint" "^7.0.0" + "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + babel-plugin-jest-hoist "^25.2.6" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base64-js@^1.0.2: + version "1.3.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" + integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +bech32@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.3.tgz#bd47a8986bbb3eec34a56a097a84b8d3e9a2dfcd" + integrity sha512-yuVFUvrNcoJi0sv5phmqc6P+Fl1HjRDRNOOkHY2X/3LBy2bIGNSFx4fZ95HMaXHupuS7cZR15AsvtmCIF4UEyg== + +binary-extensions@^1.0.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" + integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bl@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a" + integrity sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1, braces@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +braces@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + +browser-resolve@^1.11.3: + version "1.11.3" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" + integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== + dependencies: + resolve "1.1.7" + +browserslist@^4.8.3, browserslist@^4.9.1: + version "4.11.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.11.1.tgz#92f855ee88d6e050e7e7311d987992014f1a1f1b" + integrity sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g== + dependencies: + caniuse-lite "^1.0.30001038" + electron-to-chromium "^1.3.390" + node-releases "^1.1.53" + pkg-up "^2.0.0" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +buffer@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.5.0.tgz#9c3caa3d623c33dd1c7ef584b89b88bf9c9bc1ce" + integrity sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +caniuse-lite@^1.0.30001038: + version "1.0.30001039" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001039.tgz#b3814a1c38ffeb23567f8323500c09526a577bbe" + integrity sha512-SezbWCTT34eyFoWHgx8UWso7YtvtM7oosmFoXbCkdC6qJzRfBTeTgE9REtKtiuKXuMwWTZEvdnFNGAyVMorv8Q== + +capture-exit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" + integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== + dependencies: + rsvp "^4.8.4" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +chokidar@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" + integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.1" + braces "^2.3.2" + glob-parent "^3.1.0" + inherits "^2.0.3" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^3.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.2.1" + upath "^1.1.1" + optionalDependencies: + fsevents "^1.2.7" + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-width@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" + integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +concat-stream@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" + integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.0.2" + typedarray "^0.0.6" + +confusing-browser-globals@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz#72bc13b483c0276801681871d4898516f8f54fdd" + integrity sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw== + +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= + +convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + +core-js-compat@^3.6.2: + version "3.6.4" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.4.tgz#938476569ebb6cda80d339bcf199fae4f16fff17" + integrity sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA== + dependencies: + browserslist "^4.8.3" + semver "7.0.0" + +core-js@^3.2.1: + version "3.6.4" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647" + integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw== + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cross-spawn@^6.0.0, cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.2.tgz#d0d7dcfa74e89115c7619f4f721a94e1fdb716d6" + integrity sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0", cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssom@^0.4.1: + version "0.4.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== + +cssstyle@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1" + integrity sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA== + dependencies: + cssom "0.3.x" + +cssstyle@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.2.0.tgz#e4c44debccd6b7911ed617a4395e5754bba59992" + integrity sha512-sEb3XFPx3jNnCAMtqrXPDeSgQr+jojtCeNf8cvMNMh1cG970+lljssvQDzPq6lmmJu2Vhqood/gtEomBiHOGnA== + dependencies: + cssom "~0.3.6" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +data-urls@^1.0.0, data-urls@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" + integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== + dependencies: + abab "^2.0.0" + whatwg-mimetype "^2.2.0" + whatwg-url "^7.0.0" + +debug@=3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + +debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +detect-newline@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" + integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" + integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== + +diff-sequences@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" + integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg== + +docker-modem@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-2.1.2.tgz#7c346bfa7b3e4f6d5832eb80f13d603862639519" + integrity sha512-fwlfnsK9WV+m+qc/NZCiGt+oYAMjmCUeir0a/l3oHb0yc8FhRAucdwT4htKD3aLtV+1w2syQePH9pQFxsq1GFA== + dependencies: + debug "^4.1.1" + readable-stream "^3.5.0" + split-ca "^1.0.1" + ssh2 "^0.8.7" + +dockerode@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-3.2.0.tgz#80e5bd9c2a988bdde4c995ae4aa2584fa0166c23" + integrity sha512-C+y/W4Kks7YLBsfUOTMkk1IVilb4cdj+rE+UZ5hnE+rpcn2frSs7kX+6H8GteTqHcv8sln+GyxuP1qdno3IzIw== + dependencies: + concat-stream "~2.0.0" + docker-modem "^2.1.0" + tar-fs "~2.0.1" + +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +domexception@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" + integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug== + dependencies: + webidl-conversions "^4.0.2" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +electron-to-chromium@^1.3.390: + version "1.3.398" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.398.tgz#4c01e29091bf39e578ac3f66c1f157d92fa5725d" + integrity sha512-BJjxuWLKFbM5axH3vES7HKMQgAknq9PZHBkMK/rEXUQG9i1Iw5R+6hGkm6GtsQSANjSUrh/a6m32nzCNDNo/+w== + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +error-ex@^1.2.0, error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: + version "1.17.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" + integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.1.5" + is-regex "^1.0.5" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimleft "^2.1.1" + string.prototype.trimright "^2.1.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escodegen@^1.11.1, escodegen@^1.9.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.1.tgz#ba01d0c8278b5e95a9a45350142026659027a457" + integrity sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +eslint-config-airbnb-base@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.1.0.tgz#2ba4592dd6843258221d9bff2b6831bd77c874e4" + integrity sha512-+XCcfGyCnbzOnktDVhwsCAx+9DmrzEmuwxyHUJpw+kqBVT744OUBrB09khgFKlK1lshVww6qXGsYPZpavoNjJw== + dependencies: + confusing-browser-globals "^1.0.9" + object.assign "^4.1.0" + object.entries "^1.1.1" + +eslint-config-prettier@^6.10.1: + version "6.10.1" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.10.1.tgz#129ef9ec575d5ddc0e269667bf09defcd898642a" + integrity sha512-svTy6zh1ecQojvpbJSgH3aei/Rt7C6i090l5f2WQ4aB05lYHeZIR1qL4wZyyILTbtmnbHP5Yn8MrsOJMGa8RkQ== + dependencies: + get-stdin "^6.0.0" + +eslint-import-resolver-node@^0.3.2: + version "0.3.3" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz#dbaa52b6b2816b50bc6711af75422de808e98404" + integrity sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg== + dependencies: + debug "^2.6.9" + resolve "^1.13.1" + +eslint-module-utils@^2.4.1: + version "2.6.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" + integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== + dependencies: + debug "^2.6.9" + pkg-dir "^2.0.0" + +eslint-plugin-import@^2.20.2: + version "2.20.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz#91fc3807ce08be4837141272c8b99073906e588d" + integrity sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg== + dependencies: + array-includes "^3.0.3" + array.prototype.flat "^1.2.1" + contains-path "^0.1.0" + debug "^2.6.9" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.2" + eslint-module-utils "^2.4.1" + has "^1.0.3" + minimatch "^3.0.4" + object.values "^1.1.0" + read-pkg-up "^2.0.0" + resolve "^1.12.0" + +eslint-plugin-jest@^23.8.2: + version "23.8.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.8.2.tgz#6f28b41c67ef635f803ebd9e168f6b73858eb8d4" + integrity sha512-xwbnvOsotSV27MtAe7s8uGWOori0nUsrXh2f1EnpmXua8sDfY6VZhHAhHg2sqK7HBNycRQExF074XSZ7DvfoFg== + dependencies: + "@typescript-eslint/experimental-utils" "^2.5.0" + +eslint-plugin-prettier@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz#432e5a667666ab84ce72f945c72f77d996a5c9ba" + integrity sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-plugin-vue@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-6.2.2.tgz#27fecd9a3a24789b0f111ecdd540a9e56198e0fe" + integrity sha512-Nhc+oVAHm0uz/PkJAWscwIT4ijTrK5fqNqz9QB1D35SbbuMG1uB6Yr5AJpvPSWg+WOw7nYNswerYh0kOk64gqQ== + dependencies: + natural-compare "^1.4.0" + semver "^5.6.0" + vue-eslint-parser "^7.0.0" + +eslint-scope@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" + integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-utils@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" + integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd" + integrity sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" + integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== + +eslint@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" + integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.10.0" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^5.0.0" + eslint-utils "^1.4.3" + eslint-visitor-keys "^1.1.0" + espree "^6.1.2" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^7.0.0" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.14" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.3" + progress "^2.0.0" + regexpp "^2.0.1" + semver "^6.1.2" + strip-ansi "^5.2.0" + strip-json-comments "^3.0.1" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" + integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== + dependencies: + acorn "^7.1.1" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.1.0" + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.2.0.tgz#a010a519c0288f2530b3404124bfb5f02e9797fe" + integrity sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q== + dependencies: + estraverse "^5.0.0" + +esrecurse@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" + integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== + dependencies: + estraverse "^4.1.0" + +estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.0.0.tgz#ac81750b482c11cca26e4b07e83ed8f75fbcdc22" + integrity sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +events@^3.0.0, events@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" + integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== + +exec-sh@^0.3.2: + version "0.3.4" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" + integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A== + +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +execa@^3.2.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-3.4.0.tgz#c08ed4550ef65d858fac269ffc8572446f37eb89" + integrity sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + p-finally "^2.0.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expect@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-24.9.0.tgz#b75165b4817074fa4a157794f46fe9f1ba15b6ca" + integrity sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q== + dependencies: + "@jest/types" "^24.9.0" + ansi-styles "^3.2.0" + jest-get-type "^24.9.0" + jest-matcher-utils "^24.9.0" + jest-message-util "^24.9.0" + jest-regex-util "^24.9.0" + +expect@^25.2.7: + version "25.2.7" + resolved "https://registry.yarnpkg.com/expect/-/expect-25.2.7.tgz#509b79f47502835f4071ff3ecc401f2eaecca709" + integrity sha512-yA+U2Ph0MkMsJ9N8q5hs9WgWI6oJYfecdXta6LkP/alY/jZZL1MHlJ2wbLh60Ucqf3G+51ytbqV3mlGfmxkpNw== + dependencies: + "@jest/types" "^25.2.6" + ansi-styles "^4.0.0" + jest-get-type "^25.2.6" + jest-matcher-utils "^25.2.7" + jest-message-util "^25.2.6" + jest-regex-util "^25.2.6" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" + integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== + +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-cache-dir@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" + integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== + dependencies: + commondir "^1.0.1" + make-dir "^2.0.0" + pkg-dir "^3.0.0" + +find-up@^2.0.0, find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== + dependencies: + debug "=3.1.0" + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs-readdir-recursive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" + integrity sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^1.2.7: + version "1.2.12" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.12.tgz#db7e0d8ec3b0b45724fd4d83d43554a8f1f0de5c" + integrity sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q== + dependencies: + bindings "^1.5.0" + nan "^2.12.1" + +fsevents@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" + integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +gensync@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" + integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-stdin@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" + integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== + +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-stream@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" + integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== + dependencies: + pump "^3.0.0" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob-parent@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + dependencies: + is-glob "^4.0.1" + +glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.0, has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hash-base@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" + integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +homedir-polyfill@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" + integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== + dependencies: + parse-passwd "^1.0.0" + +hosted-git-info@^2.1.4: + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + +html-encoding-sniffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" + integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw== + dependencies: + whatwg-encoding "^1.0.1" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + +iconv-lite@0.4.24, iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ieee754@^1.1.4: + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +import-fresh@^3.0.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" + integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" + integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +inquirer@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.1.0.tgz#1298a01859883e17c7264b82870ae1034f92dd29" + integrity sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg== + dependencies: + ansi-escapes "^4.2.1" + chalk "^3.0.0" + cli-cursor "^3.1.0" + cli-width "^2.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.15" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.5.3" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +invariant@^2.2.2, invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +ip-regex@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" + integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-callable@^1.1.4, is-callable@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" + integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0, is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= + +is-regex@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" + integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== + dependencies: + has "^1.0.3" + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + +is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +is-typedarray@^1.0.0, is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-wsl@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d" + integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog== + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +istanbul-lib-coverage@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49" + integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== + +istanbul-lib-coverage@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" + integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== + +istanbul-lib-instrument@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630" + integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA== + dependencies: + "@babel/generator" "^7.4.0" + "@babel/parser" "^7.4.3" + "@babel/template" "^7.4.0" + "@babel/traverse" "^7.4.3" + "@babel/types" "^7.4.0" + istanbul-lib-coverage "^2.0.5" + semver "^6.0.0" + +istanbul-lib-instrument@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz#61f13ac2c96cfefb076fe7131156cc05907874e6" + integrity sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg== + dependencies: + "@babel/core" "^7.7.5" + "@babel/parser" "^7.7.5" + "@babel/template" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9" + integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b" + integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-25.2.6.tgz#7d569cd6b265b1a84db3914db345d9c452f26b71" + integrity sha512-F7l2m5n55jFnJj4ItB9XbAlgO+6umgvz/mdK76BfTd2NGkvGf9x96hUXP/15a1K0k14QtVOoutwpRKl360msvg== + dependencies: + "@jest/types" "^25.2.6" + execa "^3.2.0" + throat "^5.0.0" + +jest-cli@^25.2.7: + version "25.2.7" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-25.2.7.tgz#515b61fee402c397ffa8d570532f7b039c3159f4" + integrity sha512-OOAZwY4Jkd3r5WhVM5L3JeLNFaylvHUczMLxQDVLrrVyb1Cy+DNJ6MVsb5TLh6iBklB42m5TOP+IbOgKGGOtMw== + dependencies: + "@jest/core" "^25.2.7" + "@jest/test-result" "^25.2.6" + "@jest/types" "^25.2.6" + chalk "^3.0.0" + exit "^0.1.2" + import-local "^3.0.2" + is-ci "^2.0.0" + jest-config "^25.2.7" + jest-util "^25.2.6" + jest-validate "^25.2.6" + prompts "^2.0.1" + realpath-native "^2.0.0" + yargs "^15.3.1" + +jest-config@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.9.0.tgz#fb1bbc60c73a46af03590719efa4825e6e4dd1b5" + integrity sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ== + dependencies: + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^24.9.0" + "@jest/types" "^24.9.0" + babel-jest "^24.9.0" + chalk "^2.0.1" + glob "^7.1.1" + jest-environment-jsdom "^24.9.0" + jest-environment-node "^24.9.0" + jest-get-type "^24.9.0" + jest-jasmine2 "^24.9.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" + micromatch "^3.1.10" + pretty-format "^24.9.0" + realpath-native "^1.1.0" + +jest-config@^25.2.7: + version "25.2.7" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-25.2.7.tgz#a14e5b96575987ce913dd9fc20ac8cd4b35a8c29" + integrity sha512-rIdPPXR6XUxi+7xO4CbmXXkE6YWprvlKc4kg1SrkCL2YV5m/8MkHstq9gBZJ19Qoa3iz/GP+0sTG/PcIwkFojg== + dependencies: + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^25.2.7" + "@jest/types" "^25.2.6" + babel-jest "^25.2.6" + chalk "^3.0.0" + deepmerge "^4.2.2" + glob "^7.1.1" + jest-environment-jsdom "^25.2.6" + jest-environment-node "^25.2.6" + jest-get-type "^25.2.6" + jest-jasmine2 "^25.2.7" + jest-regex-util "^25.2.6" + jest-resolve "^25.2.6" + jest-util "^25.2.6" + jest-validate "^25.2.6" + micromatch "^4.0.2" + pretty-format "^25.2.6" + realpath-native "^2.0.0" + +jest-diff@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da" + integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ== + dependencies: + chalk "^2.0.1" + diff-sequences "^24.9.0" + jest-get-type "^24.9.0" + pretty-format "^24.9.0" + +jest-diff@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.2.6.tgz#a6d70a9ab74507715ea1092ac513d1ab81c1b5e7" + integrity sha512-KuadXImtRghTFga+/adnNrv9s61HudRMR7gVSbP35UKZdn4IK2/0N0PpGZIqtmllK9aUyye54I3nu28OYSnqOg== + dependencies: + chalk "^3.0.0" + diff-sequences "^25.2.6" + jest-get-type "^25.2.6" + pretty-format "^25.2.6" + +jest-docblock@^24.3.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2" + integrity sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA== + dependencies: + detect-newline "^2.1.0" + +jest-docblock@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-25.2.6.tgz#4b09f1e7b7d6b3f39242ef3647ac7106770f722b" + integrity sha512-VAYrljEq0upq0oERfIaaNf28gC6p9gORndhHstCYF8NWGNQJnzoaU//S475IxfWMk4UjjVmS9rJKLe5Jjjbixw== + dependencies: + detect-newline "^3.0.0" + +jest-each@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.9.0.tgz#eb2da602e2a610898dbc5f1f6df3ba86b55f8b05" + integrity sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog== + dependencies: + "@jest/types" "^24.9.0" + chalk "^2.0.1" + jest-get-type "^24.9.0" + jest-util "^24.9.0" + pretty-format "^24.9.0" + +jest-each@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-25.2.6.tgz#026f6dea2ccc443c35cea793265620aab1b278b6" + integrity sha512-OgQ01VINaRD6idWJOhCYwUc5EcgHBiFlJuw+ON2VgYr7HLtMFyCcuo+3mmBvuLUH4QudREZN7cDCZviknzsaJQ== + dependencies: + "@jest/types" "^25.2.6" + chalk "^3.0.0" + jest-get-type "^25.2.6" + jest-util "^25.2.6" + pretty-format "^25.2.6" + +jest-environment-jsdom@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz#4b0806c7fc94f95edb369a69cc2778eec2b7375b" + integrity sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA== + dependencies: + "@jest/environment" "^24.9.0" + "@jest/fake-timers" "^24.9.0" + "@jest/types" "^24.9.0" + jest-mock "^24.9.0" + jest-util "^24.9.0" + jsdom "^11.5.1" + +jest-environment-jsdom@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-25.2.6.tgz#b7ae41c6035905b8e58d63a8f63cf8eaa00af279" + integrity sha512-/o7MZIhGmLGIEG5j7r5B5Az0umWLCHU+F5crwfbm0BzC4ybHTJZOQTFQWhohBg+kbTCNOuftMcqHlVkVduJCQQ== + dependencies: + "@jest/environment" "^25.2.6" + "@jest/fake-timers" "^25.2.6" + "@jest/types" "^25.2.6" + jest-mock "^25.2.6" + jest-util "^25.2.6" + jsdom "^15.2.1" + +jest-environment-node@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.9.0.tgz#333d2d2796f9687f2aeebf0742b519f33c1cbfd3" + integrity sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA== + dependencies: + "@jest/environment" "^24.9.0" + "@jest/fake-timers" "^24.9.0" + "@jest/types" "^24.9.0" + jest-mock "^24.9.0" + jest-util "^24.9.0" + +jest-environment-node@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-25.2.6.tgz#ad4398432867113f474d94fe37b071ed04b30f3d" + integrity sha512-D1Ihj14fxZiMHGeTtU/LunhzSI+UeBvlr/rcXMTNyRMUMSz2PEhuqGbB78brBY6Dk3FhJDk7Ta+8reVaGjLWhA== + dependencies: + "@jest/environment" "^25.2.6" + "@jest/fake-timers" "^25.2.6" + "@jest/types" "^25.2.6" + jest-mock "^25.2.6" + jest-util "^25.2.6" + semver "^6.3.0" + +jest-get-type@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" + integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q== + +jest-get-type@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" + integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig== + +jest-haste-map@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d" + integrity sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ== + dependencies: + "@jest/types" "^24.9.0" + anymatch "^2.0.0" + fb-watchman "^2.0.0" + graceful-fs "^4.1.15" + invariant "^2.2.4" + jest-serializer "^24.9.0" + jest-util "^24.9.0" + jest-worker "^24.9.0" + micromatch "^3.1.10" + sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^1.2.7" + +jest-haste-map@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-25.2.6.tgz#4aa6bcfa15310afccdb9ca77af58a98add8cedb8" + integrity sha512-nom0+fnY8jwzelSDQnrqaKAcDZczYQvMEwcBjeL3PQ4MlcsqeB7dmrsAniUw/9eLkngT5DE6FhnenypilQFsgA== + dependencies: + "@jest/types" "^25.2.6" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.3" + jest-serializer "^25.2.6" + jest-util "^25.2.6" + jest-worker "^25.2.6" + micromatch "^4.0.2" + sane "^4.0.3" + walker "^1.0.7" + which "^2.0.2" + optionalDependencies: + fsevents "^2.1.2" + +jest-jasmine2@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz#1f7b1bd3242c1774e62acabb3646d96afc3be6a0" + integrity sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + chalk "^2.0.1" + co "^4.6.0" + expect "^24.9.0" + is-generator-fn "^2.0.0" + jest-each "^24.9.0" + jest-matcher-utils "^24.9.0" + jest-message-util "^24.9.0" + jest-runtime "^24.9.0" + jest-snapshot "^24.9.0" + jest-util "^24.9.0" + pretty-format "^24.9.0" + throat "^4.0.0" + +jest-jasmine2@^25.2.7: + version "25.2.7" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-25.2.7.tgz#55ff87f8f462ef0e2f16fd19430b8be8bcebef0e" + integrity sha512-HeQxEbonp8fUvik9jF0lkU9ab1u5TQdIb7YSU9Fj7SxWtqHNDGyCpF6ZZ3r/5yuertxi+R95Ba9eA91GMQ38eA== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^25.2.6" + "@jest/source-map" "^25.2.6" + "@jest/test-result" "^25.2.6" + "@jest/types" "^25.2.6" + chalk "^3.0.0" + co "^4.6.0" + expect "^25.2.7" + is-generator-fn "^2.0.0" + jest-each "^25.2.6" + jest-matcher-utils "^25.2.7" + jest-message-util "^25.2.6" + jest-runtime "^25.2.7" + jest-snapshot "^25.2.7" + jest-util "^25.2.6" + pretty-format "^25.2.6" + throat "^5.0.0" + +jest-leak-detector@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz#b665dea7c77100c5c4f7dfcb153b65cf07dcf96a" + integrity sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA== + dependencies: + jest-get-type "^24.9.0" + pretty-format "^24.9.0" + +jest-leak-detector@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-25.2.6.tgz#68fbaf651142292b03e30641f33e15af9b8c62b1" + integrity sha512-n+aJUM+j/x1kIaPVxzerMqhAUuqTU1PL5kup46rXh+l9SP8H6LqECT/qD1GrnylE1L463/0StSPkH4fUpkuEjA== + dependencies: + jest-get-type "^25.2.6" + pretty-format "^25.2.6" + +jest-matcher-utils@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073" + integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA== + dependencies: + chalk "^2.0.1" + jest-diff "^24.9.0" + jest-get-type "^24.9.0" + pretty-format "^24.9.0" + +jest-matcher-utils@^25.2.7: + version "25.2.7" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.2.7.tgz#53fad3c11fc42e92e374306df543026712c957a3" + integrity sha512-jNYmKQPRyPO3ny0KY1I4f0XW4XnpJ3Nx5ovT4ik0TYDOYzuXJW40axqOyS61l/voWbVT9y9nZ1THL1DlpaBVpA== + dependencies: + chalk "^3.0.0" + jest-diff "^25.2.6" + jest-get-type "^25.2.6" + pretty-format "^25.2.6" + +jest-message-util@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.9.0.tgz#527f54a1e380f5e202a8d1149b0ec872f43119e3" + integrity sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/stack-utils" "^1.0.1" + chalk "^2.0.1" + micromatch "^3.1.10" + slash "^2.0.0" + stack-utils "^1.0.1" + +jest-message-util@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.2.6.tgz#9d5523bebec8cd9cdef75f0f3069d6ec9a2252df" + integrity sha512-Hgg5HbOssSqOuj+xU1mi7m3Ti2nwSQJQf/kxEkrz2r2rp2ZLO1pMeKkz2WiDUWgSR+APstqz0uMFcE5yc0qdcg== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/types" "^25.2.6" + "@types/stack-utils" "^1.0.1" + chalk "^3.0.0" + micromatch "^4.0.2" + slash "^3.0.0" + stack-utils "^1.0.1" + +jest-mock@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.9.0.tgz#c22835541ee379b908673ad51087a2185c13f1c6" + integrity sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w== + dependencies: + "@jest/types" "^24.9.0" + +jest-mock@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.2.6.tgz#8df66eaa55a713d0f2a7dfb4f14507289d24dfa3" + integrity sha512-vc4nibavi2RGPdj/MyZy/azuDjZhpYZLvpfgq1fxkhbyTpKVdG7CgmRVKJ7zgLpY5kuMjTzDYA6QnRwhsCU+tA== + dependencies: + "@jest/types" "^25.2.6" + +jest-pnp-resolver@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a" + integrity sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ== + +jest-regex-util@^24.3.0, jest-regex-util@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.9.0.tgz#c13fb3380bde22bf6575432c493ea8fe37965636" + integrity sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA== + +jest-regex-util@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-25.2.6.tgz#d847d38ba15d2118d3b06390056028d0f2fd3964" + integrity sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw== + +jest-resolve-dependencies@^25.2.7: + version "25.2.7" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-25.2.7.tgz#9ca4c62d67cce031a27fa5d5705b4b5b5c029d23" + integrity sha512-IrnMzCAh11Xd2gAOJL+ThEW6QO8DyqNdvNkQcaCticDrOAr9wtKT7yT6QBFFjqKFgjjvaVKDs59WdgUhgYnHnQ== + dependencies: + "@jest/types" "^25.2.6" + jest-regex-util "^25.2.6" + jest-snapshot "^25.2.7" + +jest-resolve@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.9.0.tgz#dff04c7687af34c4dd7e524892d9cf77e5d17321" + integrity sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ== + dependencies: + "@jest/types" "^24.9.0" + browser-resolve "^1.11.3" + chalk "^2.0.1" + jest-pnp-resolver "^1.2.1" + realpath-native "^1.1.0" + +jest-resolve@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-25.2.6.tgz#84694ead5da13c2890ac04d4a78699ba937f3896" + integrity sha512-7O61GVdcAXkLz/vNGKdF+00A80/fKEAA47AEXVNcZwj75vEjPfZbXDaWFmAQCyXj4oo9y9dC9D+CLA11t8ieGw== + dependencies: + "@jest/types" "^25.2.6" + browser-resolve "^1.11.3" + chalk "^3.0.0" + jest-pnp-resolver "^1.2.1" + realpath-native "^2.0.0" + resolve "^1.15.1" + +jest-runner@^24.8.0, jest-runner@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.9.0.tgz#574fafdbd54455c2b34b4bdf4365a23857fcdf42" + integrity sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg== + dependencies: + "@jest/console" "^24.7.1" + "@jest/environment" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + chalk "^2.4.2" + exit "^0.1.2" + graceful-fs "^4.1.15" + jest-config "^24.9.0" + jest-docblock "^24.3.0" + jest-haste-map "^24.9.0" + jest-jasmine2 "^24.9.0" + jest-leak-detector "^24.9.0" + jest-message-util "^24.9.0" + jest-resolve "^24.9.0" + jest-runtime "^24.9.0" + jest-util "^24.9.0" + jest-worker "^24.6.0" + source-map-support "^0.5.6" + throat "^4.0.0" + +jest-runner@^25.2.7: + version "25.2.7" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-25.2.7.tgz#3676c01dc0104caa8a0ebb8507df382c88f2a1e2" + integrity sha512-RFEr71nMrtNwcpoHzie5+fe1w3JQCGMyT2xzNwKe3f88+bK+frM2o1v24gEcPxQ2QqB3COMCe2+1EkElP+qqqQ== + dependencies: + "@jest/console" "^25.2.6" + "@jest/environment" "^25.2.6" + "@jest/test-result" "^25.2.6" + "@jest/types" "^25.2.6" + chalk "^3.0.0" + exit "^0.1.2" + graceful-fs "^4.2.3" + jest-config "^25.2.7" + jest-docblock "^25.2.6" + jest-haste-map "^25.2.6" + jest-jasmine2 "^25.2.7" + jest-leak-detector "^25.2.6" + jest-message-util "^25.2.6" + jest-resolve "^25.2.6" + jest-runtime "^25.2.7" + jest-util "^25.2.6" + jest-worker "^25.2.6" + source-map-support "^0.5.6" + throat "^5.0.0" + +jest-runtime@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.9.0.tgz#9f14583af6a4f7314a6a9d9f0226e1a781c8e4ac" + integrity sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw== + dependencies: + "@jest/console" "^24.7.1" + "@jest/environment" "^24.9.0" + "@jest/source-map" "^24.3.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/yargs" "^13.0.0" + chalk "^2.0.1" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.1.15" + jest-config "^24.9.0" + jest-haste-map "^24.9.0" + jest-message-util "^24.9.0" + jest-mock "^24.9.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.9.0" + jest-snapshot "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" + realpath-native "^1.1.0" + slash "^2.0.0" + strip-bom "^3.0.0" + yargs "^13.3.0" + +jest-runtime@^25.2.7: + version "25.2.7" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-25.2.7.tgz#cb10e695d036671a83aec3a3803163c354043ac9" + integrity sha512-Gw3X8KxTTFylu2T/iDSNKRUQXQiPIYUY0b66GwVYa7W8wySkUljKhibQHSq0VhmCAN7vRBEQjlVQ+NFGNmQeBw== + dependencies: + "@jest/console" "^25.2.6" + "@jest/environment" "^25.2.6" + "@jest/source-map" "^25.2.6" + "@jest/test-result" "^25.2.6" + "@jest/transform" "^25.2.6" + "@jest/types" "^25.2.6" + "@types/yargs" "^15.0.0" + chalk "^3.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.3" + jest-config "^25.2.7" + jest-haste-map "^25.2.6" + jest-message-util "^25.2.6" + jest-mock "^25.2.6" + jest-regex-util "^25.2.6" + jest-resolve "^25.2.6" + jest-snapshot "^25.2.7" + jest-util "^25.2.6" + jest-validate "^25.2.6" + realpath-native "^2.0.0" + slash "^3.0.0" + strip-bom "^4.0.0" + yargs "^15.3.1" + +"jest-serial-runner@^1.1.0 ": + version "1.1.0" + resolved "https://registry.yarnpkg.com/jest-serial-runner/-/jest-serial-runner-1.1.0.tgz#867fcd3ce0284afdf742a7306a9cbfd998631aaf" + integrity sha512-QSCMVZMYPAB8ALys43sxgVt4R6slizz7wj2rbCQPvczMh/AOImKeRil6T0dqaXQUTT9UXYzq00zb1bdK5uGEVQ== + dependencies: + jest-runner "^24.8.0" + +jest-serializer@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.9.0.tgz#e6d7d7ef96d31e8b9079a714754c5d5c58288e73" + integrity sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ== + +jest-serializer@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-25.2.6.tgz#3bb4cc14fe0d8358489dbbefbb8a4e708ce039b7" + integrity sha512-RMVCfZsezQS2Ww4kB5HJTMaMJ0asmC0BHlnobQC6yEtxiFKIxohFA4QSXSabKwSggaNkqxn6Z2VwdFCjhUWuiQ== + +jest-snapshot@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.9.0.tgz#ec8e9ca4f2ec0c5c87ae8f925cf97497b0e951ba" + integrity sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew== + dependencies: + "@babel/types" "^7.0.0" + "@jest/types" "^24.9.0" + chalk "^2.0.1" + expect "^24.9.0" + jest-diff "^24.9.0" + jest-get-type "^24.9.0" + jest-matcher-utils "^24.9.0" + jest-message-util "^24.9.0" + jest-resolve "^24.9.0" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + pretty-format "^24.9.0" + semver "^6.2.0" + +jest-snapshot@^25.2.7: + version "25.2.7" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-25.2.7.tgz#7eeafeef4dcbda1c47c8503d2bf5212b6430aac6" + integrity sha512-Rm8k7xpGM4tzmYhB6IeRjsOMkXaU8/FOz5XlU6oYwhy53mq6txVNqIKqN1VSiexzpC80oWVxVDfUDt71M6XPOA== + dependencies: + "@babel/types" "^7.0.0" + "@jest/types" "^25.2.6" + "@types/prettier" "^1.19.0" + chalk "^3.0.0" + expect "^25.2.7" + jest-diff "^25.2.6" + jest-get-type "^25.2.6" + jest-matcher-utils "^25.2.7" + jest-message-util "^25.2.6" + jest-resolve "^25.2.6" + make-dir "^3.0.0" + natural-compare "^1.4.0" + pretty-format "^25.2.6" + semver "^6.3.0" + +jest-util@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.9.0.tgz#7396814e48536d2e85a37de3e4c431d7cb140162" + integrity sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg== + dependencies: + "@jest/console" "^24.9.0" + "@jest/fake-timers" "^24.9.0" + "@jest/source-map" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + callsites "^3.0.0" + chalk "^2.0.1" + graceful-fs "^4.1.15" + is-ci "^2.0.0" + mkdirp "^0.5.1" + slash "^2.0.0" + source-map "^0.6.0" + +jest-util@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.2.6.tgz#3c1c95cdfd653126728b0ed861a86610e30d569c" + integrity sha512-gpXy0H5ymuQ0x2qgl1zzHg7LYHZYUmDEq6F7lhHA8M0eIwDB2WteOcCnQsohl9c/vBKZ3JF2r4EseipCZz3s4Q== + dependencies: + "@jest/types" "^25.2.6" + chalk "^3.0.0" + is-ci "^2.0.0" + make-dir "^3.0.0" + +jest-validate@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.9.0.tgz#0775c55360d173cd854e40180756d4ff52def8ab" + integrity sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ== + dependencies: + "@jest/types" "^24.9.0" + camelcase "^5.3.1" + chalk "^2.0.1" + jest-get-type "^24.9.0" + leven "^3.1.0" + pretty-format "^24.9.0" + +jest-validate@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-25.2.6.tgz#ab3631fb97e242c42b09ca53127abe0b12e9125e" + integrity sha512-a4GN7hYbqQ3Rt9iHsNLFqQz7HDV7KiRPCwPgo5nqtTIWNZw7gnT8KchG+Riwh+UTSn8REjFCodGp50KX/fRNgQ== + dependencies: + "@jest/types" "^25.2.6" + camelcase "^5.3.1" + chalk "^3.0.0" + jest-get-type "^25.2.6" + leven "^3.1.0" + pretty-format "^25.2.6" + +jest-watcher@^25.2.7: + version "25.2.7" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-25.2.7.tgz#01db4332d34d14c03c9ef22255125a3b07f997bc" + integrity sha512-RdHuW+f49tahWtluTnUdZ2iPliebleROI2L/J5phYrUS6DPC9RB3SuUtqYyYhGZJsbvRSuLMIlY/cICJ+PIecw== + dependencies: + "@jest/test-result" "^25.2.6" + "@jest/types" "^25.2.6" + ansi-escapes "^4.2.1" + chalk "^3.0.0" + jest-util "^25.2.6" + string-length "^3.1.0" + +jest-worker@^24.6.0, jest-worker@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5" + integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw== + dependencies: + merge-stream "^2.0.0" + supports-color "^6.1.0" + +jest-worker@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.2.6.tgz#d1292625326794ce187c38f51109faced3846c58" + integrity sha512-FJn9XDUSxcOR4cwDzRfL1z56rUofNTFs539FGASpd50RHdb6EVkhxQqktodW2mI49l+W3H+tFJDotCHUQF6dmA== + dependencies: + merge-stream "^2.0.0" + supports-color "^7.0.0" + +"jest@^25.2.7 ": + version "25.2.7" + resolved "https://registry.yarnpkg.com/jest/-/jest-25.2.7.tgz#3929a5f35cdd496f7756876a206b99a94e1e09ae" + integrity sha512-XV1n/CE2McCikl4tfpCY950RytHYvxdo/wvtgmn/qwA8z1s16fuvgFL/KoPrrmkqJTaPMUlLVE58pwiaTX5TdA== + dependencies: + "@jest/core" "^25.2.7" + import-local "^3.0.2" + jest-cli "^25.2.7" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +jsdom@^11.5.1: + version "11.12.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8" + integrity sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw== + dependencies: + abab "^2.0.0" + acorn "^5.5.3" + acorn-globals "^4.1.0" + array-equal "^1.0.0" + cssom ">= 0.3.2 < 0.4.0" + cssstyle "^1.0.0" + data-urls "^1.0.0" + domexception "^1.0.1" + escodegen "^1.9.1" + html-encoding-sniffer "^1.0.2" + left-pad "^1.3.0" + nwsapi "^2.0.7" + parse5 "4.0.0" + pn "^1.1.0" + request "^2.87.0" + request-promise-native "^1.0.5" + sax "^1.2.4" + symbol-tree "^3.2.2" + tough-cookie "^2.3.4" + w3c-hr-time "^1.0.1" + webidl-conversions "^4.0.2" + whatwg-encoding "^1.0.3" + whatwg-mimetype "^2.1.0" + whatwg-url "^6.4.1" + ws "^5.2.0" + xml-name-validator "^3.0.0" + +jsdom@^15.2.1: + version "15.2.1" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-15.2.1.tgz#d2feb1aef7183f86be521b8c6833ff5296d07ec5" + integrity sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g== + dependencies: + abab "^2.0.0" + acorn "^7.1.0" + acorn-globals "^4.3.2" + array-equal "^1.0.0" + cssom "^0.4.1" + cssstyle "^2.0.0" + data-urls "^1.1.0" + domexception "^1.0.1" + escodegen "^1.11.1" + html-encoding-sniffer "^1.0.2" + nwsapi "^2.2.0" + parse5 "5.1.0" + pn "^1.1.0" + request "^2.88.0" + request-promise-native "^1.0.7" + saxes "^3.1.9" + symbol-tree "^3.2.2" + tough-cookie "^3.0.1" + w3c-hr-time "^1.0.1" + w3c-xmlserializer "^1.1.2" + webidl-conversions "^4.0.2" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^7.0.0" + ws "^7.0.0" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +json5@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" + integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== + dependencies: + minimist "^1.2.5" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +ledger-cosmos-js@^2.1.7: + version "2.1.7" + resolved "https://registry.yarnpkg.com/ledger-cosmos-js/-/ledger-cosmos-js-2.1.7.tgz#362d1139ac2504ccb56029abda053b2c5b290e8d" + integrity sha512-RyaP+6lRhllQTgk5X14PiZtsUE8bYP7mOiFkOMAzPQvUDdy8IZr17mDaH9whqHtE0wZd95YPN89VAhbfV+Re8w== + dependencies: + "@babel/runtime" "^7.7.4" + "@ledgerhq/hw-transport" "^4.77.0" + bech32 "^1.1.3" + ripemd160 "^2.0.2" + +left-pad@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" + integrity sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levenary@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/levenary/-/levenary-1.1.1.tgz#842a9ee98d2075aa7faeedbe32679e9205f46f77" + integrity sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ== + dependencies: + leven "^3.1.0" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + +lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +lolex@^5.0.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367" + integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A== + dependencies: + "@sinonjs/commons" "^1.7.0" + +loose-envify@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +make-dir@^2.0.0, make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +make-dir@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.2.tgz#04a1acbf22221e1d6ef43559f43e05a90dbb4392" + integrity sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w== + dependencies: + semver "^6.0.0" + +makeerror@1.0.x: + version "1.0.11" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= + dependencies: + tmpl "1.0.x" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + +mime-db@1.43.0: + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + dependencies: + mime-db "1.43.0" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp-classic@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz#54c441ce4c96cd7790e10b41a87aa51068ecab2b" + integrity sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g== + +mkdirp@^0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +nan@^2.12.1, nan@^2.13.2: + version "2.14.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" + integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +node-environment-flags@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" + integrity sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw== + dependencies: + object.getownpropertydescriptors "^2.0.3" + semver "^5.7.0" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + +node-modules-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" + integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= + +node-notifier@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-6.0.0.tgz#cea319e06baa16deec8ce5cd7f133c4a46b68e12" + integrity sha512-SVfQ/wMw+DesunOm5cKqr6yDcvUTDl/yc97ybGHMrteNEY6oekXpNpS3lZwgLlwz0FLgHoiW28ZpmBHUDg37cw== + dependencies: + growly "^1.3.0" + is-wsl "^2.1.1" + semver "^6.3.0" + shellwords "^0.1.1" + which "^1.3.1" + +node-releases@^1.1.53: + version "1.1.53" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4" + integrity sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ== + +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + +npm-run-path@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nwsapi@^2.0.7, nwsapi@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-inspect@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" + integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.entries@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.1.tgz#ee1cf04153de02bb093fec33683900f57ce5399b" + integrity sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + +object.values@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" + integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" + integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.8.1, optionator@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +p-each-series@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48" + integrity sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ== + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +p-finally@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" + integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^2.0.0, p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= + dependencies: + error-ex "^1.2.0" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= + +parse5@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" + integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== + +parse5@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" + integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= + dependencies: + pify "^2.0.0" + +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== + dependencies: + pify "^3.0.0" + +path@^0.12.7: + version "0.12.7" + resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f" + integrity sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8= + dependencies: + process "^0.11.1" + util "^0.10.3" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +picomatch@^2.0.4, picomatch@^2.0.5: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pirates@^4.0.0, pirates@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" + integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== + dependencies: + node-modules-regexp "^1.0.0" + +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= + dependencies: + find-up "^2.1.0" + +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== + dependencies: + find-up "^3.0.0" + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" + integrity sha1-yBmscoBZpGHKscOImivjxJoATX8= + dependencies: + find-up "^2.1.0" + +pn@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" + integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== + +pngjs@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" + integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +pretty-format@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" + integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA== + dependencies: + "@jest/types" "^24.9.0" + ansi-regex "^4.0.0" + ansi-styles "^3.2.0" + react-is "^16.8.4" + +pretty-format@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.2.6.tgz#542a1c418d019bbf1cca2e3620443bc1323cb8d7" + integrity sha512-DEiWxLBaCHneffrIT4B+TpMvkV9RNvvJrd3lY9ew1CEQobDzEXmYT1mg0hJhljZty7kCc10z13ohOFAE8jrUDg== + dependencies: + "@jest/types" "^25.2.6" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^16.12.0" + +private@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +process@^0.11.1: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +prompts@^2.0.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068" + integrity sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.4" + +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +react-is@^16.12.0, react-is@^16.8.4: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg-up@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" + integrity sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA== + dependencies: + find-up "^3.0.0" + read-pkg "^3.0.0" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +read-pkg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" + integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= + dependencies: + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" + +readable-stream@^2.0.2: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== + dependencies: + graceful-fs "^4.1.11" + micromatch "^3.1.10" + readable-stream "^2.0.2" + +realpath-native@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c" + integrity sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA== + dependencies: + util.promisify "^1.0.0" + +realpath-native@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-2.0.0.tgz#7377ac429b6e1fd599dc38d08ed942d0d7beb866" + integrity sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q== + +regenerate-unicode-properties@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" + integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== + dependencies: + regenerate "^1.4.0" + +regenerate@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" + integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== + +regenerator-runtime@^0.13.4: + version "0.13.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" + integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== + +regenerator-transform@^0.14.2: + version "0.14.4" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.4.tgz#5266857896518d1616a78a0479337a30ea974cc7" + integrity sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw== + dependencies: + "@babel/runtime" "^7.8.4" + private "^0.1.8" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== + +regexpu-core@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938" + integrity sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^8.2.0" + regjsgen "^0.5.1" + regjsparser "^0.6.4" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.2.0" + +regjsgen@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" + integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg== + +regjsparser@^0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" + integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw== + dependencies: + jsesc "~0.5.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +request-promise-core@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" + integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== + dependencies: + lodash "^4.17.15" + +request-promise-native@^1.0.5, request-promise-native@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" + integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== + dependencies: + request-promise-core "1.1.3" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" + +request@^2.87.0, request@^2.88.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +resolve@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= + +resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.8.1: + version "1.15.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" + integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== + dependencies: + path-parse "^1.0.6" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +rfb2@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/rfb2/-/rfb2-0.2.2.tgz#f9444b8803e6a31848e57911ace562ce0fee5598" + integrity sha512-+Aw0oED0zsoNQYFE3FUsD+a/lm9y8YwdQaERlWHm7G5hey3tiSQGq7tfe5sFAw5fbN7Zms38bVEDxIUAOq3mRw== + +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +ripemd160@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +rsvp@^4.8.4: + version "4.8.5" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" + integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== + +run-async@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8" + integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg== + dependencies: + is-promise "^2.1.0" + +rxjs@^6.5.3, rxjs@^6.5.4: + version "6.5.5" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" + integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== + dependencies: + tslib "^1.9.0" + +safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sane@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" + integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== + dependencies: + "@cnakazawa/watch" "^1.0.3" + anymatch "^2.0.0" + capture-exit "^2.0.0" + exec-sh "^0.3.2" + execa "^1.0.0" + fb-watchman "^2.0.0" + micromatch "^3.1.4" + minimist "^1.1.1" + walker "~1.0.5" + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +saxes@^3.1.9: + version "3.1.11" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b" + integrity sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g== + dependencies: + xmlchars "^2.1.1" + +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + +semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shellwords@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +sisteransi@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +sleep@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/sleep/-/sleep-6.1.0.tgz#5507b520556a82ffb983d39123c5459470fa2a9e" + integrity sha512-Z1x4JjJxsru75Tqn8F4tnOFeEu3HjtITTsumYUiuz54sGKdISgLCek9AUlXlVVrkhltRFhNUsJDJE76SFHTDIQ== + dependencies: + nan "^2.13.2" + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +source-map-resolve@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@^0.5.16, source-map-support@^0.5.6: + version "0.5.16" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" + integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + +source-map@^0.5.0, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +spdx-correct@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" + integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" + integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.5" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" + integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== + +split-ca@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6" + integrity sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY= + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +ssh2-streams@~0.4.10: + version "0.4.10" + resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.10.tgz#48ef7e8a0e39d8f2921c30521d56dacb31d23a34" + integrity sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ== + dependencies: + asn1 "~0.2.0" + bcrypt-pbkdf "^1.0.2" + streamsearch "~0.1.2" + +ssh2@^0.8.7: + version "0.8.9" + resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3" + integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw== + dependencies: + ssh2-streams "~0.4.10" + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stack-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" + integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +stealthy-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= + +streamsearch@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" + integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= + +string-length@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-3.1.0.tgz#107ef8c23456e187a8abd4a61162ff4ac6e25837" + integrity sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA== + dependencies: + astral-regex "^1.0.0" + strip-ansi "^5.2.0" + +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +string.prototype.trimend@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz#ee497fd29768646d84be2c9b819e292439614373" + integrity sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string.prototype.trimleft@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz#4408aa2e5d6ddd0c9a80739b087fbc067c03b3cc" + integrity sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string.prototype.trimstart "^1.0.0" + +string.prototype.trimright@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz#c76f1cef30f21bbad8afeb8db1511496cfb0f2a3" + integrity sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string.prototype.trimend "^1.0.0" + +string.prototype.trimstart@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz#afe596a7ce9de905496919406c9734845f01a2f2" + integrity sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" + integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz#f663df252af5f37c5d49bbd7eeefa9e0b9e59e47" + integrity sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +symbol-tree@^3.2.2: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +tar-fs@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2" + integrity sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.0.0" + +tar-stream@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.2.tgz#6d5ef1a7e5783a95ff70b69b97455a5968dc1325" + integrity sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q== + dependencies: + bl "^4.0.1" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +test-exclude@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" + integrity sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g== + dependencies: + glob "^7.1.3" + minimatch "^3.0.4" + read-pkg-up "^4.0.0" + require-main-filename "^2.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +throat@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" + integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo= + +throat@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" + integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tough-cookie@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" + integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== + dependencies: + ip-regex "^2.1.0" + psl "^1.1.28" + punycode "^2.1.1" + +tr46@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" + integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= + dependencies: + punycode "^2.1.0" + +tslib@^1.8.1, tslib@^1.9.0: + version "1.11.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" + integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== + +tsutils@^3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" + integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== + dependencies: + tslib "^1.8.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" + integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +unicode-canonical-property-names-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" + integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== + +unicode-match-property-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" + integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== + dependencies: + unicode-canonical-property-names-ecmascript "^1.0.4" + unicode-property-aliases-ecmascript "^1.0.4" + +unicode-match-property-value-ecmascript@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" + integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== + +unicode-property-aliases-ecmascript@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" + integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +upath@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util.promisify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" + integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.2" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.0" + +util@^0.10.3: + version "0.10.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" + integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== + dependencies: + inherits "2.0.3" + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +v8-compile-cache@^2.0.3: + version "2.1.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" + integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== + +v8-to-istanbul@^4.0.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-4.1.3.tgz#22fe35709a64955f49a08a7c7c959f6520ad6f20" + integrity sha512-sAjOC+Kki6aJVbUOXJbcR0MnbfjvBzwKZazEJymA2IX49uoOdEdk+4fBq5cXgYgiyKtAyrrJNtBZdOeDIF+Fng== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" + +v8flags@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.3.tgz#fc9dc23521ca20c5433f81cc4eb9b3033bb105d8" + integrity sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w== + dependencies: + homedir-polyfill "^1.0.1" + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +vue-eslint-parser@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-7.0.0.tgz#a4ed2669f87179dedd06afdd8736acbb3a3864d6" + integrity sha512-yR0dLxsTT7JfD2YQo9BhnQ6bUTLsZouuzt9SKRP7XNaZJV459gvlsJo4vT2nhZ/2dH9j3c53bIx9dnqU2prM9g== + dependencies: + debug "^4.1.1" + eslint-scope "^5.0.0" + eslint-visitor-keys "^1.1.0" + espree "^6.1.2" + esquery "^1.0.1" + lodash "^4.17.15" + +w3c-hr-time@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794" + integrity sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg== + dependencies: + domexception "^1.0.1" + webidl-conversions "^4.0.2" + xml-name-validator "^3.0.0" + +walker@^1.0.7, walker@~1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= + dependencies: + makeerror "1.0.x" + +webidl-conversions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== + +whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + +whatwg-url@^6.4.1: + version "6.5.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" + integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +whatwg-url@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" + integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which@^1.2.9, which@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1, which@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write-file-atomic@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.1.tgz#d0b05463c188ae804396fd5ab2a370062af87529" + integrity sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg== + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + signal-exit "^3.0.2" + +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" + +ws@6: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + +ws@^5.2.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" + integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA== + dependencies: + async-limiter "~1.0.0" + +ws@^7.0.0: + version "7.2.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46" + integrity sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ== + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xmlchars@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + +yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs-parser@^18.1.1: + version "18.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.2.tgz#2f482bea2136dbde0861683abea7756d30b504f1" + integrity sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@^13.3.0: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.2" + +yargs@^15.3.1: + version "15.3.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b" + integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.1" diff --git a/tests/json_parser.cpp b/tests/json_parser.cpp index e983c50a..99e3ca5a 100644 --- a/tests/json_parser.cpp +++ b/tests/json_parser.cpp @@ -125,7 +125,9 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - EXPECT_EQ(array_get_element_count(2, &parsed_json), 3) << "Wrong number of array elements"; + uint16_t token; + EXPECT_EQ(array_get_element_count(&parsed_json, 2, &token), parser_ok); + EXPECT_EQ(token, 3) << "Wrong number of array elements"; } TEST(JsonParserTest, ArrayElementCount_primitives) { @@ -134,7 +136,9 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - EXPECT_EQ(array_get_element_count(2, &parsed_json), 7) << "Wrong number of array elements"; + uint16_t token; + EXPECT_EQ(array_get_element_count(&parsed_json, 2, &token), parser_ok); + EXPECT_EQ(token, 7) << "Wrong number of array elements"; } TEST(JsonParserTest, ArrayElementCount_strings) { @@ -143,7 +147,9 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - EXPECT_EQ(array_get_element_count(2, &parsed_json), 2) << "Wrong number of array elements"; + uint16_t token; + EXPECT_EQ(array_get_element_count(&parsed_json, 2, &token), parser_ok); + EXPECT_EQ(token, 2) << "Wrong number of array elements"; } TEST(JsonParserTest, ArrayElementCount_empty) { @@ -152,7 +158,8 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - EXPECT_EQ(array_get_element_count(2, &parsed_json), 0) << "Wrong number of array elements"; + uint16_t token; + EXPECT_EQ(array_get_element_count(&parsed_json, 2, &token), parser_no_data); } TEST(JsonParserTest, ArrayElementGet_objects) { @@ -162,7 +169,8 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - int token_index = array_get_nth_element(2, 1, &parsed_json); + uint16_t token_index; + EXPECT_EQ(array_get_nth_element(&parsed_json, 2, 1, &token_index), parser_ok); EXPECT_EQ(token_index, 8) << "Wrong token index returned"; EXPECT_EQ(parsed_json.tokens[token_index].type, JSMN_OBJECT) << "Wrong token type returned"; } @@ -173,7 +181,8 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - int token_index = array_get_nth_element(2, 5, &parsed_json); + uint16_t token_index; + EXPECT_EQ(array_get_nth_element(&parsed_json, 2, 5, &token_index), parser_ok); EXPECT_EQ(token_index, 8) << "Wrong token index returned"; EXPECT_EQ(parsed_json.tokens[token_index].type, JSMN_PRIMITIVE) << "Wrong token type returned"; } @@ -184,7 +193,8 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - int token_index = array_get_nth_element(2, 0, &parsed_json); + uint16_t token_index; + EXPECT_EQ(array_get_nth_element(&parsed_json, 2, 0, &token_index), parser_ok); EXPECT_EQ(token_index, 3) << "Wrong token index returned"; EXPECT_EQ(parsed_json.tokens[token_index].type, JSMN_STRING) << "Wrong token type returned"; } @@ -195,8 +205,9 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - int token_index = array_get_nth_element(2, 0, &parsed_json); - EXPECT_EQ(token_index, -1) << "Token index should be invalid (not found)."; + uint16_t token_index; + EXPECT_EQ(array_get_nth_element(&parsed_json, 2, 0, &token_index), parser_no_data) + << "Token index should be invalid (not found)."; } TEST(TxValidationTest, ArrayElementGet_out_of_bounds_negative) { @@ -205,8 +216,9 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - int token_index = array_get_nth_element(2, -1, &parsed_json); - EXPECT_EQ(token_index, -1) << "Token index should be invalid (not found)."; + uint16_t token_index; + EXPECT_EQ(array_get_nth_element(&parsed_json, 2, -1, &token_index), parser_no_data) + << "Token index should be invalid (not found)."; } TEST(TxValidationTest, ArrayElementGet_out_of_bounds) { @@ -215,8 +227,9 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - int token_index = array_get_nth_element(2, 3, &parsed_json); - EXPECT_EQ(token_index, -1) << "Token index should be invalid (not found)."; + uint16_t token_index; + EXPECT_EQ(array_get_nth_element(&parsed_json, 2, 3, &token_index), parser_no_data) + << "Token index should be invalid (not found)."; } TEST(TxValidationTest, ObjectElementCount_primitives) { @@ -225,7 +238,9 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - EXPECT_EQ(object_get_element_count(0, &parsed_json), 3) << "Wrong number of object elements"; + uint16_t count; + EXPECT_EQ(object_get_element_count(&parsed_json, 0, &count), parser_ok); + EXPECT_EQ(count, 3) << "Wrong number of object elements"; } TEST(TxValidationTest, ObjectElementCount_string) { @@ -234,7 +249,9 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - EXPECT_EQ(object_get_element_count(0, &parsed_json), 4) << "Wrong number of object elements"; + uint16_t count; + EXPECT_EQ(object_get_element_count(&parsed_json, 0, &count), parser_ok); + EXPECT_EQ(count, 4) << "Wrong number of object elements"; } TEST(TxValidationTest, ObjectElementCount_array) { @@ -246,7 +263,9 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - EXPECT_EQ(object_get_element_count(0, &parsed_json), 4) << "Wrong number of object elements"; + uint16_t count; + EXPECT_EQ(object_get_element_count(&parsed_json, 0, &count), parser_ok); + EXPECT_EQ(count, 4) << "Wrong number of object elements"; } TEST(TxValidationTest, ObjectElementCount_object) { @@ -257,7 +276,9 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - EXPECT_EQ(object_get_element_count(0, &parsed_json), 3) << "Wrong number of object elements"; + uint16_t count; + EXPECT_EQ(object_get_element_count(&parsed_json, 0, &count), parser_ok); + EXPECT_EQ(count, 3) << "Wrong number of object elements"; } TEST(TxValidationTest, ObjectElementCount_deep) { @@ -268,7 +289,9 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - EXPECT_EQ(object_get_element_count(0, &parsed_json), 3) << "Wrong number of object elements"; + uint16_t count; + EXPECT_EQ(object_get_element_count(&parsed_json, 0, &count), parser_ok); + EXPECT_EQ(count, 3) << "Wrong number of object elements"; } TEST(TxValidationTest, ObjectElementGet_primitives) { @@ -277,7 +300,8 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - int token_index = object_get_nth_key(0, 0, &parsed_json); + uint16_t token_index; + EXPECT_EQ(object_get_nth_key(&parsed_json, 0, 0, &token_index), parser_ok); EXPECT_EQ(token_index, 1) << "Wrong token index"; EXPECT_EQ(parsed_json.tokens[token_index].type, JSMN_STRING) << "Wrong token type returned"; EXPECT_EQ(memcmp(transaction + parsed_json.tokens[token_index].start, "age", strlen("age")), 0) @@ -290,7 +314,8 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - int token_index = object_get_nth_value(0, 3, &parsed_json); + uint16_t token_index; + EXPECT_EQ(object_get_nth_value(&parsed_json, 0, 3, &token_index), parser_ok); EXPECT_EQ(token_index, 8) << "Wrong token index"; EXPECT_EQ(parsed_json.tokens[token_index].type, JSMN_STRING) << "Wrong token type returned"; EXPECT_EQ(memcmp(transaction + parsed_json.tokens[token_index].start, "july", strlen("july")), 0) @@ -303,8 +328,9 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - int token_index = object_get_nth_key(0, -1, &parsed_json); - EXPECT_EQ(token_index, -1) << "Wrong token index, should be invalid"; + uint16_t token_index; + EXPECT_EQ(object_get_nth_key(&parsed_json, 0, -1, &token_index), parser_no_data) + << "Wrong token index, should be invalid"; } TEST(TxValidationTest, ObjectElementGet_out_of_bounds) { @@ -313,8 +339,9 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - int token_index = object_get_nth_key(0, 5, &parsed_json); - EXPECT_EQ(token_index, -1) << "Wrong token index, should be invalid"; + uint16_t token_index; + EXPECT_EQ(object_get_nth_key(&parsed_json, 0, 5, &token_index), parser_no_data) + << "Wrong token index, should be invalid"; } TEST(TxValidationTest, ObjectElementGet_array) { @@ -326,11 +353,14 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - int token_index = object_get_value(&parsed_json, 0, "years"); + uint16_t token_index; + EXPECT_EQ(object_get_value(&parsed_json, 0, "years", &token_index), parser_ok); EXPECT_EQ(token_index, 14) << "Wrong token index"; EXPECT_EQ(parsed_json.tokens[token_index].type, JSMN_ARRAY) << "Wrong token type returned"; - EXPECT_EQ(array_get_element_count(token_index, &parsed_json), 5) << "Wrong number of array elements"; + uint16_t number_elements; + EXPECT_EQ(array_get_element_count(&parsed_json, token_index, &number_elements), parser_ok); + EXPECT_EQ(number_elements, 5) << "Wrong number of array elements"; } TEST(TxValidationTest, ObjectGetValueCorrectFormat) { @@ -339,17 +369,23 @@ namespace { parsed_json_t parsed_json; JSON_PARSE(&parsed_json, transaction); - int token_index = object_get_value(&parsed_json, 0, "alt_bytes"); - EXPECT_EQ(token_index, -1) << "Wrong token index"; // alt_bytes should not be found - token_index = object_get_value(&parsed_json, 0, "account_number"); + uint16_t token_index; + EXPECT_EQ(object_get_value(&parsed_json, 0, "alt_bytes", &token_index), parser_no_data) + << "Wrong token index"; // alt_bytes should not be found + + EXPECT_EQ(object_get_value(&parsed_json, 0, "account_number", &token_index), parser_ok); EXPECT_EQ(token_index, 2) << "Wrong token index"; // alt_bytes should not be found - token_index = object_get_value(&parsed_json, 0, "chain_id"); + + EXPECT_EQ(object_get_value(&parsed_json, 0, "chain_id", &token_index), parser_ok); EXPECT_EQ(token_index, 4) << "Wrong token index"; - token_index = object_get_value(&parsed_json, 0, "fee"); + + EXPECT_EQ(object_get_value(&parsed_json, 0, "fee", &token_index), parser_ok); EXPECT_EQ(token_index, 6) << "Wrong token index"; - token_index = object_get_value(&parsed_json, 0, "msgs"); + + EXPECT_EQ(object_get_value(&parsed_json, 0, "msgs", &token_index), parser_ok); EXPECT_EQ(token_index, 19) << "Wrong token index"; - token_index = object_get_value(&parsed_json, 0, "sequence"); + + EXPECT_EQ(object_get_value(&parsed_json, 0, "sequence", &token_index), parser_ok); EXPECT_EQ(token_index, 46) << "Wrong token index"; } } diff --git a/tests/testcases/manual.json b/tests/testcases/manual.json index 8efe5d90..9d840250 100644 --- a/tests/testcases/manual.json +++ b/tests/testcases/manual.json @@ -650,6 +650,80 @@ "13 | Validator [2/2] : 93ky4t3y2n2ku" ] }, + { + "name": "grouping_ledger_testcase", + "tx": { + "account_number": "108", + "chain_id": "cosmoshub-2", + "fee": { + "amount": [ + { + "amount": "600", + "denom": "uatom" + } + ], + "gas": "200000" + }, + "memo": "", + "msgs": [ + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl87jycah", + "validator_address": "cosmosvaloper1kn3wugetjuy4zetlq6wadchfhvu3x740ae6z6x" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl87jycah", + "validator_address": "cosmosvaloper1sjllsnramtg3ewxqwwrwjxfgc4n4ef9u2lcnj0" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl87jycah", + "validator_address": "cosmosvaloper1ey69r37gfxvxg62sh4r0ktpuc46pzjrm873ae8" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl87jycah", + "validator_address": "cosmosvaloper1648ynlpdw7fqa2axt0w2yp3fk542junl7rsvq6" + } + } + ], + "sequence": "106" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Chain ID : cosmoshub-2", + "1 | Account : 108", + "2 | Sequence : 106", + "3 | Fee : 600 uatom", + "4 | Gas : 200000", + "5 | Type : Withdraw Reward", + "6 | Delegator [1/2] : cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl8", + "6 | Delegator [2/2] : 7jycah", + "7 | Validator [1/2] : cosmosvaloper1kn3wugetjuy4zetlq6wadchfh", + "7 | Validator [2/2] : vu3x740ae6z6x", + "8 | Delegator [1/2] : cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl8", + "8 | Delegator [2/2] : 7jycah", + "9 | Validator [1/2] : cosmosvaloper1sjllsnramtg3ewxqwwrwjxfgc", + "9 | Validator [2/2] : 4n4ef9u2lcnj0", + "10 | Delegator [1/2] : cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl8", + "10 | Delegator [2/2] : 7jycah", + "11 | Validator [1/2] : cosmosvaloper1ey69r37gfxvxg62sh4r0ktpuc", + "11 | Validator [2/2] : 46pzjrm873ae8", + "12 | Delegator [1/2] : cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl8", + "12 | Delegator [2/2] : 7jycah", + "13 | Validator [1/2] : cosmosvaloper1648ynlpdw7fqa2axt0w2yp3fk", + "13 | Validator [2/2] : 542junl7rsvq6" + ] + }, { "name": "massive", "tx": { diff --git a/tests/tx_parse.cpp b/tests/tx_parse.cpp index e721cc90..3378cbc6 100644 --- a/tests/tx_parse.cpp +++ b/tests/tx_parse.cpp @@ -23,11 +23,12 @@ namespace { parser_error_t tx_traverse(int16_t root_token_index, uint8_t *numChunks) { - uint16_t ret_value_token_index; + uint16_t ret_value_token_index = 0; parser_error_t err = tx_traverse_find(root_token_index, &ret_value_token_index); - if (err != parser_ok) + if (err != parser_ok){ return err; + } return tx_getToken(ret_value_token_index, parser_tx_obj.query.out_val, parser_tx_obj.query.out_val_len, @@ -40,7 +41,15 @@ namespace { parser_tx_obj.tx = transaction; parser_tx_obj.flags.cache_valid = 0; parser_error_t err = JSON_PARSE(&parser_tx_obj.json, parser_tx_obj.tx); + ASSERT_EQ(err, parser_ok); + // Check some tokens + ASSERT_EQ(parser_tx_obj.json.numberOfTokens, 7) << "It should contain 7 = 1 (dict) + 6 (key+value)"; + ASSERT_EQ(parser_tx_obj.json.tokens[0].start, 0); + ASSERT_EQ(parser_tx_obj.json.tokens[0].end, 46); + ASSERT_EQ(parser_tx_obj.json.tokens[0].size, 3) << "size should be 3 = 3 key/values contained in the dict"; + ASSERT_EQ(parser_tx_obj.json.tokens[3].start, 19); + ASSERT_EQ(parser_tx_obj.json.tokens[3].end, 23); char key[100]; char val[100]; @@ -105,7 +114,7 @@ namespace { INIT_QUERY_CONTEXT(key, sizeof(key), val, sizeof(val), 5, 4) err = tx_traverse(0, &numChunks); - EXPECT_EQ(err, parser_display_page_out_of_range) << "Item not found"; + EXPECT_EQ(err, parser_display_page_out_of_range) << "This call should have resulted in a display out of range"; // We should find it.. but later tx_display should fail INIT_QUERY_CONTEXT(key, sizeof(key), val, sizeof(val), 0, 4) @@ -122,9 +131,10 @@ namespace { parser_error_t err = JSON_PARSE(&parser_tx_obj.json, parser_tx_obj.tx); EXPECT_EQ(err, parser_ok); - auto numItems = tx_display_numItems(); + uint16_t numItems; + tx_display_numItems(&numItems); - EXPECT_EQ(1, numItems) << "Wrong number of pages"; + EXPECT_EQ(1, numItems) << "Wrong number of items"; } TEST(TxParse, Tx_Page_Count) { @@ -135,8 +145,9 @@ namespace { parser_error_t err = JSON_PARSE(&parser_tx_obj.json, parser_tx_obj.tx); EXPECT_EQ(err, parser_ok); - auto num_pages = tx_display_numItems(); - EXPECT_EQ(10, num_pages) << "Wrong number of pages"; + uint16_t numItems; + tx_display_numItems(&numItems); + EXPECT_EQ(10, numItems) << "Wrong number of items"; } TEST(TxParse, Page_Count_MultipleMsgs) { @@ -148,6 +159,8 @@ namespace { parser_error_t err = JSON_PARSE(&parser_tx_obj.json, parser_tx_obj.tx); EXPECT_EQ(err, parser_ok); - EXPECT_EQ(22, tx_display_numItems()) << "Wrong number of items"; + uint16_t numItems; + tx_display_numItems(&numItems); + EXPECT_EQ(22, numItems) << "Wrong number of items"; } } diff --git a/tests/util/common.cpp b/tests/util/common.cpp index 88a4578f..22dde7de 100644 --- a/tests/util/common.cpp +++ b/tests/util/common.cpp @@ -21,7 +21,8 @@ std::vector dumpUI(parser_context_t *ctx, uint16_t maxKeyLen, uint16_t maxValueLen) { - uint16_t numItems = parser_getNumItems(ctx); + uint16_t numItems; + parser_error_t err = parser_getNumItems(ctx, &numItems); auto answer = std::vector(); From cb88e36b7c2c295d8310a6f27e677880de0eee98 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Thu, 30 Apr 2020 17:38:57 +0200 Subject: [PATCH 57/78] fix hdpath formatting --- app/Makefile | 2 +- deps/ledger-zxlib/app/common/view_s.c | 10 ++++---- deps/ledger-zxlib/include/zxformat.h | 36 ++++++++++++++++++++++----- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/app/Makefile b/app/Makefile index df8645ac..8694b25e 100755 --- a/app/Makefile +++ b/app/Makefile @@ -28,7 +28,7 @@ include $(BOLOS_SDK)/Makefile.defines APPNAME = "Cosmos" APPVERSION_M=2 APPVERSION_N=11 -APPVERSION_P=0 +APPVERSION_P=1 APPPATH = "44'/118'" APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path $(APPPATH) diff --git a/deps/ledger-zxlib/app/common/view_s.c b/deps/ledger-zxlib/app/common/view_s.c index 58b91d7f..495861e8 100644 --- a/deps/ledger-zxlib/app/common/view_s.c +++ b/deps/ledger-zxlib/app/common/view_s.c @@ -68,7 +68,7 @@ void h_review(unsigned int _) { UNUSED(_); view_sign_show_impl(); } const ux_menu_entry_t menu_sign[] = { {NULL, h_review, 0, NULL, "View transaction", NULL, 0, 0}, {NULL, h_sign_accept, 0, NULL, "Sign transaction", NULL, 0, 0}, - {NULL, h_sign_reject, 0, &C_icon_back, "Reject", NULL, 60, 40}, + {NULL, h_sign_reject, 0, NULL, "Reject", NULL, 0, 0}, UX_MENU_END }; @@ -140,6 +140,7 @@ void h_review_button_left() { switch(err) { case view_no_error: view_review_show(); + UX_WAIT(); break; case view_no_data: view_sign_show_s(); @@ -147,10 +148,9 @@ void h_review_button_left() { case view_error_detected: default: view_error_show(); + UX_WAIT(); break; } - - UX_WAIT(); } void h_review_button_right() { @@ -161,6 +161,7 @@ void h_review_button_right() { switch(err) { case view_no_error: view_review_show(); + UX_WAIT(); break; case view_no_data: view_sign_show_s(); @@ -168,10 +169,9 @@ void h_review_button_right() { case view_error_detected: default: view_error_show(); + UX_WAIT(); break; } - - UX_WAIT(); } void splitValueField() { diff --git a/deps/ledger-zxlib/include/zxformat.h b/deps/ledger-zxlib/include/zxformat.h index 6ed3cb52..b5fbb4ba 100644 --- a/deps/ledger-zxlib/include/zxformat.h +++ b/deps/ledger-zxlib/include/zxformat.h @@ -46,6 +46,8 @@ NUM_TO_STR(int64) NUM_TO_STR(uint64) __Z_INLINE void bip32_to_str(char *s, uint32_t max, const uint32_t *path, uint8_t pathLen) { + MEMZERO(s, max); + if (pathLen == 0) { snprintf(s, max, "EMPTY PATH"); return; @@ -57,16 +59,38 @@ __Z_INLINE void bip32_to_str(char *s, uint32_t max, const uint32_t *path, uint8_ } uint32_t offset = 0; - for (int i = 0; i < pathLen; i++) { - uint32_t written = snprintf(s + offset, max - offset, "%d%s%s", - path[i] & 0x7FFFFFFFu, - (path[i] & 0x80000000u) != 0 ? "'" : "", - i == pathLen - 1 ? "" : "/"); - if (written >= max - offset) { + for (uint16_t i = 0; i < pathLen; i++) { + size_t written = 0; + + // Warning: overcomplicated because Ledger's snprintf does not return number of written bytes + + snprintf(s + offset, max - offset, "%d", path[i] & 0x7FFFFFFFu); + written = strlen(s + offset); + if (written == 0 || written >= max - offset) { snprintf(s, max, "ERROR"); return; } offset += written; + + if ((path[i] & 0x80000000u) != 0) { + snprintf(s + offset, max - offset, "'"); + written = strlen(s + offset); + if (written == 0 || written >= max - offset) { + snprintf(s, max, "ERROR"); + return; + } + offset += written; + } + + if (i != pathLen - 1) { + snprintf(s + offset, max - offset, "/"); + written = strlen(s + offset); + if (written == 0 || written >= max - offset) { + snprintf(s, max, "ERROR"); + return; + } + offset += written; + } } } From b70cf96a698bec63acca39e8d1147bea2bf7864b Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Fri, 1 May 2020 12:46:12 +0200 Subject: [PATCH 58/78] upgraded zxlib --- app/src/coin.h | 7 +- app/src/common/app_main.c | 2 +- deps/ledger-zxlib/app/common/view.c | 117 ++++++++++++++---- deps/ledger-zxlib/app/common/view.h | 3 +- deps/ledger-zxlib/app/common/view_internal.h | 14 ++- deps/ledger-zxlib/app/common/view_s.c | 122 ++++++++++++++++++- deps/ledger-zxlib/app/common/view_x.c | 12 +- deps/ledger-zxlib/cmake/dockerized_build.mk | 89 +++++++++++--- deps/ledger-zxlib/include/view_templates.h | 1 + integration/snapshots/.gitignore | 1 - {integration => tests_zemu}/.babelrc | 0 {integration => tests_zemu}/.gitignore | 0 {integration => tests_zemu}/jest.config.js | 0 {integration => tests_zemu}/jest.js | 0 {integration => tests_zemu}/package.json | 0 {integration => tests_zemu}/tests/test.js | 0 {integration => tests_zemu}/yarn.lock | 0 17 files changed, 309 insertions(+), 59 deletions(-) delete mode 100644 integration/snapshots/.gitignore rename {integration => tests_zemu}/.babelrc (100%) rename {integration => tests_zemu}/.gitignore (100%) rename {integration => tests_zemu}/jest.config.js (100%) rename {integration => tests_zemu}/jest.js (100%) rename {integration => tests_zemu}/package.json (100%) rename {integration => tests_zemu}/tests/test.js (100%) rename {integration => tests_zemu}/yarn.lock (100%) diff --git a/app/src/coin.h b/app/src/coin.h index f33d058b..f5e1a23b 100644 --- a/app/src/coin.h +++ b/app/src/coin.h @@ -28,14 +28,19 @@ extern "C" { #define HDPATH_3_DEFAULT (0) #define HDPATH_4_DEFAULT (0) -#define MENU_MAIN_APP_LINE1 "Cosmos" +typedef enum { + addr_secp256k1 = 0, +} address_kind_e; +#define MENU_MAIN_APP_LINE1 "Cosmos" #ifdef TESTING_ENABLED #define MENU_MAIN_APP_LINE2 "Cosmos TEST!" #else #define MENU_MAIN_APP_LINE2 "App" #endif +#define APPVERSION_LINE2 "" +#define VIEW_ADDRESS_ITEM_COUNT 2 #define VIEW_ADDRESS_BUFFER_OFFSET (PK_LEN) #ifdef __cplusplus diff --git a/app/src/common/app_main.c b/app/src/common/app_main.c index 109be673..20ce4254 100644 --- a/app/src/common/app_main.c +++ b/app/src/common/app_main.c @@ -185,7 +185,7 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { if (requireConfirmation) { app_fill_address(); - view_address_show(); + view_address_show(addr_secp256k1); *flags |= IO_ASYNCH_REPLY; break; } diff --git a/deps/ledger-zxlib/app/common/view.c b/deps/ledger-zxlib/app/common/view.c index bf012980..8ecbda88 100644 --- a/deps/ledger-zxlib/app/common/view.c +++ b/deps/ledger-zxlib/app/common/view.c @@ -37,14 +37,14 @@ void h_address_accept(unsigned int _) { UNUSED(_); view_idle_show(0); UX_WAIT(); - app_reply_address(); + app_reply_address(viewdata.addrKind); } void h_error_accept(unsigned int _) { UNUSED(_); view_idle_show(0); UX_WAIT(); - app_reply_address(); + app_reply_error(); } void h_sign_accept(unsigned int _) { @@ -73,25 +73,41 @@ void h_sign_reject(unsigned int _) { io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); } -void h_review_init() { - viewdata.idx = 0; +void h_paging_init() { + viewdata.itemIdx = 0; viewdata.pageIdx = 0; viewdata.pageCount = 1; } -void h_review_increase() { - viewdata.pageIdx++; - if (viewdata.pageIdx >= viewdata.pageCount) { - viewdata.idx++; - viewdata.pageIdx = 0; +void h_paging_increase() { + if (viewdata.pageIdx + 1 < viewdata.pageCount) { + // increase page + viewdata.pageIdx++; + } else { + // passed page count, go to next index + if (viewdata.itemIdx + 1 < viewdata.itemCount) { + viewdata.itemIdx++; + viewdata.pageIdx = 0; + } + } +} + +void h_paging_decrease() { + if (viewdata.pageIdx != 0) { + viewdata.pageIdx--; + } else { + if (viewdata.itemIdx > 0) { + viewdata.itemIdx--; + // jump to last page. update will cap this value + viewdata.pageIdx = 255; + } } } -void h_review_decrease() { - viewdata.pageIdx--; - if (viewdata.pageIdx < 0) { - viewdata.idx--; - viewdata.pageIdx = 0; +__Z_INLINE void h_paging_set_page_count(uint8_t pageCount) { + viewdata.pageCount = pageCount; + if (viewdata.pageIdx > viewdata.pageCount) { + viewdata.pageIdx = viewdata.pageCount - 1; } } @@ -99,7 +115,7 @@ view_error_t h_review_update_data() { tx_error_t err = tx_no_error; do { - err = tx_getItem(viewdata.idx, + err = tx_getItem(viewdata.itemIdx, viewdata.key, MAX_CHARS_PER_KEY_LINE, viewdata.value, MAX_CHARS_PER_VALUE1_LINE, viewdata.pageIdx, &viewdata.pageCount); @@ -109,7 +125,7 @@ view_error_t h_review_update_data() { } if (viewdata.pageCount == 0) { - h_review_increase(); + h_paging_increase(); } } while (viewdata.pageCount == 0); @@ -121,16 +137,67 @@ view_error_t h_review_update_data() { return view_no_error; } -view_error_t h_addr_update_item(uint8_t idx) { - MEMZERO(viewdata.addr, MAX_CHARS_ADDR); - switch (idx) { - case 0: - snprintf(viewdata.addr, MAX_CHARS_ADDR, "%s", (char *) (G_io_apdu_buffer + VIEW_ADDRESS_BUFFER_OFFSET)); +__Z_INLINE view_error_t printAddr() { +#if !defined(HAVE_UX_FLOW) + if (viewdata.addrKind != addr_secp256k1 && + viewdata.addrKind != addr_sapling) { + return view_error_detected; + } + + char *p = NULL; + switch (viewdata.addrKind) { + case addr_secp256k1: { + h_paging_set_page_count(1); + snprintf(viewdata.key, MAX_CHARS_PER_KEY_LINE, "unshielded"); + p = (char *) (G_io_apdu_buffer + ADDR_OFFSET_SECP256K1); + p += MAX_CHARS_PER_VALUE1_LINE * viewdata.pageIdx; + snprintf(viewdata.value, MAX_CHARS_PER_VALUE1_LINE, "%s", (char *) (G_io_apdu_buffer + ADDR_OFFSET_SECP256K1)); break; - case 1: - bip32_to_str(viewdata.addr, MAX_CHARS_ADDR, hdPath, HDPATH_LEN_DEFAULT); + } + + case addr_sapling: { + h_paging_set_page_count(3); + + snprintf(viewdata.key, MAX_CHARS_PER_KEY_LINE, "shielded [%d/%d]", viewdata.pageIdx + 1, viewdata.pageCount); + p = (char *) (G_io_apdu_buffer + ADDR_OFFSET_SAPLING); + p += MAX_CHARS_PER_VALUE1_LINE * viewdata.pageIdx; + snprintf(viewdata.value, MAX_CHARS_PER_VALUE1_LINE, "%s", p); break; + } + + default: + return view_error_detected; } +#else + snprintf(viewdata.addr, MAX_CHARS_ADDR, "%s", (char *) (G_io_apdu_buffer + VIEW_ADDRESS_BUFFER_OFFSET)); +#endif + splitValueField(); + return view_no_error; +} + +__Z_INLINE view_error_t printPath() { +#if !defined(HAVE_UX_FLOW) + h_paging_set_page_count(2); + snprintf(viewdata.key, MAX_CHARS_PER_KEY_LINE, "path [%d/%d]", viewdata.pageIdx + 1, viewdata.pageCount); + snprintf(viewdata.value, MAX_CHARS_PER_VALUE1_LINE, "SOME_PATH %d", viewdata.pageIdx + 1); +#else + bip32_to_str(viewdata.addr, MAX_CHARS_ADDR, hdPath, HDPATH_LEN_DEFAULT); +#endif + splitValueField(); + return view_no_error; +} + +view_error_t h_addr_update_item(uint8_t idx) { + MEMZERO(viewdata.value, MAX_CHARS_PER_VALUE1_LINE); + + switch (idx) { + case 0: return printAddr(); + case 1: return printPath(); + default: + return view_error_detected; + } + + splitValueField(); return view_no_error; } @@ -146,7 +213,9 @@ void view_idle_show(unsigned int ignored) { view_idle_show_impl(); } -void view_address_show() { +void view_address_show(address_kind_e addressKind) { + viewdata.addrKind = addressKind; + viewdata.itemCount = VIEW_ADDRESS_ITEM_COUNT; // Address, path, etc. view_address_show_impl(); } diff --git a/deps/ledger-zxlib/app/common/view.h b/deps/ledger-zxlib/app/common/view.h index 1908dcdf..e9389a54 100644 --- a/deps/ledger-zxlib/app/common/view.h +++ b/deps/ledger-zxlib/app/common/view.h @@ -17,6 +17,7 @@ #pragma once #include +#include "coin.h" #if defined(LEDGER_SPECIFIC) #include "bolos_target.h" @@ -36,7 +37,7 @@ void view_idle_show(unsigned int ignored); void view_error_show(); // shows address in the screen -void view_address_show(); +void view_address_show(address_kind_e addressKind); // Shows review screen + later sign menu void view_sign_show(); diff --git a/deps/ledger-zxlib/app/common/view_internal.h b/deps/ledger-zxlib/app/common/view_internal.h index 1775eafc..35f5811d 100644 --- a/deps/ledger-zxlib/app/common/view_internal.h +++ b/deps/ledger-zxlib/app/common/view_internal.h @@ -34,7 +34,7 @@ #endif #define MAX_CHARS_ADDR (MAX_CHARS_PER_KEY_LINE + MAX_CHARS_PER_VALUE1_LINE) -// This typically will point to G_io_apdu_buffer that is prefilled with the address +// This takes data from G_io_apdu_buffer that is prefilled with the address typedef struct { union { @@ -49,8 +49,10 @@ typedef struct { char addr[MAX_CHARS_ADDR]; }; }; - int8_t idx; - int8_t pageIdx; + address_kind_e addrKind; + uint8_t itemIdx; + uint8_t itemCount; + uint8_t pageIdx; uint8_t pageCount; } view_t; @@ -97,11 +99,11 @@ void h_sign_accept(unsigned int _); void h_sign_reject(unsigned int _); -void h_review_init(); +void h_paging_init(); -void h_review_increase(); +void h_paging_increase(); -void h_review_decrease(); +void h_paging_decrease(); view_error_t h_review_update_data(); diff --git a/deps/ledger-zxlib/app/common/view_s.c b/deps/ledger-zxlib/app/common/view_s.c index 495861e8..7a9127eb 100644 --- a/deps/ledger-zxlib/app/common/view_s.c +++ b/deps/ledger-zxlib/app/common/view_s.c @@ -43,11 +43,24 @@ void os_exit(uint32_t id) { const ux_menu_entry_t menu_main[] = { {NULL, NULL, 0, &C_icon_app, MENU_MAIN_APP_LINE1, MENU_MAIN_APP_LINE2, 33, 12}, - {NULL, NULL, 0, NULL, "v"APPVERSION, NULL, 0, 0}, + {NULL, NULL, 0, &C_icon_app, "v"APPVERSION, APPVERSION_LINE2, 33, 12}, {NULL, os_exit, 0, &C_icon_dashboard, "Quit", NULL, 50, 29}, UX_MENU_END }; +#if !defined(HAVE_UX_FLOW) +void h_addr_button_left(); +void h_addr_button_right(); +void h_addr_button_both(); +void view_addr_show(); + +static const bagl_element_t view_address[] = { + UI_BACKGROUND_LEFT_RIGHT_ICONS, + UI_LabelLine(UIID_LABEL + 0, 0, 8, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, viewdata.key), + UI_LabelLine(UIID_LABEL + 0, 0, 19, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, viewdata.value), + UI_LabelLineScrolling(UIID_LABELSCROLL, 0, 30, 128, UI_11PX, UI_WHITE, UI_BLACK, viewdata.value2), +}; +#else UX_STEP_NOCB_INIT(ux_addr_flow_1_step, paging, { h_addr_update_item(CUR_FLOW.index); }, { .title = "Address", .text = viewdata.addr, }); @@ -63,6 +76,8 @@ UX_FLOW( &ux_addr_flow_3_step ); +#endif + void h_review(unsigned int _) { UNUSED(_); view_sign_show_impl(); } const ux_menu_entry_t menu_sign[] = { @@ -87,6 +102,28 @@ static const bagl_element_t view_error[] = { UI_LabelLineScrolling(UIID_LABELSCROLL, 0, 30, 128, UI_11PX, UI_WHITE, UI_BLACK, viewdata.value2), }; +#if !defined(HAVE_UX_FLOW) +static unsigned int view_address_button(unsigned int button_mask, unsigned int button_mask_counter) { + switch (button_mask) { + case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: + // Press both left and right buttons to quit + h_addr_button_both(); + break; + + case BUTTON_EVT_RELEASED | BUTTON_LEFT: + // Press left to progress to the previous element + h_addr_button_left(); + break; + + case BUTTON_EVT_RELEASED | BUTTON_RIGHT: + // Press right to progress to the next element + h_addr_button_right(); + break; + } + return 0; +} +#endif + static unsigned int view_error_button(unsigned int button_mask, unsigned int button_mask_counter) { switch (button_mask) { case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: @@ -134,7 +171,7 @@ const bagl_element_t *view_prepro(const bagl_element_t *element) { } void h_review_button_left() { - h_review_decrease(); + h_paging_decrease(); view_error_t err = h_review_update_data(); switch(err) { @@ -154,7 +191,7 @@ void h_review_button_left() { } void h_review_button_right() { - h_review_increase(); + h_paging_increase(); view_error_t err = h_review_update_data(); @@ -194,11 +231,26 @@ void view_idle_show_impl() { } void view_address_show_impl() { +#if !defined(HAVE_UX_FLOW) + h_paging_init(); + + view_error_t err = h_addr_update_item(viewdata.itemIdx); + switch(err) { + case view_no_error: + view_addr_show(); + break; + case view_error_detected: + default: + view_error_show(); + break; + } +#else ux_layout_paging_reset(); if(G_ux.stack_count == 0) { ux_stack_push(); } ux_flow_init(0, ux_addr_flow, NULL); +#endif } void view_error_show_impl() { @@ -206,7 +258,7 @@ void view_error_show_impl() { } void view_sign_show_impl() { - h_review_init(); + h_paging_init(); view_error_t err = h_review_update_data(); switch(err) { @@ -231,4 +283,66 @@ void view_review_show() { UX_DISPLAY(view_review, view_prepro); } +#if !defined(HAVE_UX_FLOW) +void h_addr_button_left() { + h_paging_decrease(); + + view_error_t err = h_addr_update_item(viewdata.itemIdx); + + switch(err) { + case view_no_error: + view_addr_show(); + break; + case view_no_data: + // FIXME: + case view_error_detected: + default: + view_error_show(); + break; + } + + UX_WAIT(); +} + +void h_addr_button_right() { + h_paging_increase(); + view_error_t err = h_addr_update_item(viewdata.itemIdx); + + switch(err) { + case view_no_error: + view_addr_show(); + break; + case view_no_data: + // FIXME: + case view_error_detected: + default: + view_error_show(); + break; + } + + UX_WAIT(); +} + +void h_addr_button_both() { + view_error_t err = h_addr_update_item(viewdata.itemIdx); + + switch(err) { + case view_no_error: + h_address_accept(0); + break; + case view_no_data: + // FIXME: + case view_error_detected: + default: + view_error_show(); + break; + } + UX_WAIT(); +} + +void view_addr_show() { + UX_DISPLAY(view_address, view_prepro); +} +#endif + #endif diff --git a/deps/ledger-zxlib/app/common/view_x.c b/deps/ledger-zxlib/app/common/view_x.c index c3519e03..a863333a 100644 --- a/deps/ledger-zxlib/app/common/view_x.c +++ b/deps/ledger-zxlib/app/common/view_x.c @@ -105,7 +105,7 @@ const ux_flow_step_t *const ux_sign_flow[] = { void h_review_loop_start() { if (flow_inside_loop) { // coming from right - h_review_decrease(); + h_paging_decrease(); if (viewdata.idx<0) { // exit to the left flow_inside_loop = 0; @@ -114,7 +114,7 @@ void h_review_loop_start() { } } else { // coming from left - h_review_init(); + h_paging_init(); } view_error_t err = h_review_update_data(); @@ -138,7 +138,7 @@ void h_review_loop_inside() { void h_review_loop_end() { if (flow_inside_loop) { // coming from left - h_review_increase(); + h_paging_increase(); view_error_t err = h_review_update_data(); switch(err) { @@ -157,7 +157,7 @@ void h_review_loop_end() { } } else { // coming from right - h_review_decrease(); + h_paging_decrease(); view_error_t err = h_review_update_data(); switch(err) { @@ -214,8 +214,8 @@ void view_error_show_impl() { } void view_sign_show_impl(){ - h_review_init(); - h_review_decrease(); + h_paging_init(); + h_paging_decrease(); //// flow_inside_loop = 0; if(G_ux.stack_count == 0) { diff --git a/deps/ledger-zxlib/cmake/dockerized_build.mk b/deps/ledger-zxlib/cmake/dockerized_build.mk index bd2f0362..0b1047ac 100644 --- a/deps/ledger-zxlib/cmake/dockerized_build.mk +++ b/deps/ledger-zxlib/cmake/dockerized_build.mk @@ -16,11 +16,12 @@ .PHONY: all deps build clean load delete check_python show_info_recovery_mode +TESTS_ZEMU_DIR ?= $(CURDIR)/tests_zemu + LEDGER_SRC=$(CURDIR)/app DOCKER_APP_SRC=/project DOCKER_APP_BIN=$(DOCKER_APP_SRC)/app/bin/app.elf -DOCKER_IMAGE=zondax/ledger-docker-bolos:latest DOCKER_BOLOS_SDK=/project/deps/nanos-secure-sdk DOCKER_BOLOS_SDKX=/project/deps/nano2-sdk @@ -28,6 +29,15 @@ SCP_PUBKEY=049bc79d139c70c83a4b19e8922e5ee3e0080bb14a2e8b0752aa42cda90a1463f689b SCP_PRIVKEY=ff701d781f43ce106f72dc26a46b6a83e053b5d07bb3d4ceab79c91ca822a66b INTERACTIVE:=$(shell [ -t 0 ] && echo 1) +USERID:=$(shell id -u) +$(info USERID: $(USERID)) + +ifeq ($(USERID),1001) +# TODO: Use podman inside circleci machines? +DOCKER_IMAGE=zondax/builder-bolos-1001:latest +else +DOCKER_IMAGE=zondax/builder-bolos:latest +endif ifdef INTERACTIVE INTERACTIVE_SETTING:="-i" @@ -39,66 +49,64 @@ endif define run_docker docker run $(TTY_SETTING) $(INTERACTIVE_SETTING) --rm \ - --privileged \ -e SCP_PRIVKEY=$(SCP_PRIVKEY) \ -e BOLOS_SDK=$(1) \ -e BOLOS_ENV=/opt/bolos \ - -p 1234:1234 \ - -p 8001:8001 \ - -p 9998-9999:9998-9999 \ - -u $(shell id -u) \ + -u $(USERID) \ -v $(shell pwd):/project \ - -e DISPLAY=$(shell echo ${DISPLAY}) \ - -v /tmp/.X11-unix:/tmp/.X11-unix:ro \ $(DOCKER_IMAGE) \ "$(2)" endef all: build +.PHONY: check_python check_python: @python -c 'import sys; sys.exit(3-sys.version_info.major)' || (echo "The python command does not point to Python 3"; exit 1) +.PHONY: deps deps: check_python @echo "Install dependencies" $(CURDIR)/deps/ledger-zxlib/scripts/install_deps.sh +.PHONY: pull pull: docker pull $(DOCKER_IMAGE) +.PHONY: build_rust build_rust: $(call run_docker,$(DOCKER_BOLOS_SDK),make -C $(DOCKER_APP_SRC) rust) +.PHONY: build build: build_rust $(info Replacing app icon) @cp $(LEDGER_SRC)/nanos_icon.gif $(LEDGER_SRC)/glyphs/icon_app.gif $(info calling make inside docker) $(call run_docker,$(DOCKER_BOLOS_SDK),make -C $(DOCKER_APP_SRC)) +.PHONY: buildX buildX: build_rust @cp $(LEDGER_SRC)/nanos_icon.gif $(LEDGER_SRC)/glyphs/icon_app.gif @convert $(LEDGER_SRC)/nanos_icon.gif -crop 14x14+1+1 +repage -negate $(LEDGER_SRC)/nanox_icon.gif $(call run_docker,$(DOCKER_BOLOS_SDKX),make -C $(DOCKER_APP_SRC)) +.PHONY: clean clean: $(call run_docker,$(DOCKER_BOLOS_SDK),make -C $(DOCKER_APP_SRC) clean) +.PHONY: shell shell: $(call run_docker,$(DOCKER_BOLOS_SDK) -t,bash) -debug: build - $(call run_docker,$(DOCKER_BOLOS_SDK),/home/zondax/speculos/speculos.py --debug --display headless -t $(DOCKER_APP_BIN)) - -emu: build - $(call run_docker,$(DOCKER_BOLOS_SDK),/home/zondax/speculos/speculos.py --ontop --zoom 3 --vnc-port 8001 $(DOCKER_APP_BIN)) - - +.PHONY: load load: ${LEDGER_SRC}/pkg/zxtool.sh load +.PHONY: delete delete: ${LEDGER_SRC}/pkg/zxtool.sh delete +.PHONY: show_info_recovery_mode show_info_recovery_mode: @echo "This command requires a Ledger Nano S in recovery mode. To go into recovery mode, follow:" @echo " 1. Settings -> Device -> Reset all and confirm" @@ -107,25 +115,76 @@ show_info_recovery_mode: @echo "If everything was correct, no PIN needs to be entered." # This target will initialize the device with the integration testing mnemonic +.PHONY: dev_init dev_init: show_info_recovery_mode @echo "Initializing device with test mnemonic! WARNING TAKES 2 MINUTES AND REQUIRES RECOVERY MODE" @python -m ledgerblue.hostOnboard --apdu --id 0 --prefix "" --passphrase "" --pin 5555 --words "equip will roof matter pink blind book anxiety banner elbow sun young" # This target will initialize the device with the secondary integration testing mnemonic (Bob) +.PHONY: dev_init_secondary dev_init_secondary: check_python show_info_recovery_mode @echo "Initializing device with secondary test mnemonic! WARNING TAKES 2 MINUTES AND REQUIRES RECOVERY MODE" @python -m ledgerblue.hostOnboard --apdu --id 0 --prefix "" --passphrase "" --pin 5555 --words "elite vote proof agree february step sibling sand grocery axis false cup" # This target will setup a custom developer certificate +.PHONY: dev_ca dev_ca: check_python @python -m ledgerblue.setupCustomCA --targetId 0x31100004 --public $(SCP_PUBKEY) --name zondax +.PHONY: dev_ca_delete dev_ca_delete: check_python @python -m ledgerblue.resetCustomCA --targetId 0x31100004 # This target will setup a custom developer certificate +.PHONY: dev_ca2 dev_ca2: check_python @python -m ledgerblue.setupCustomCA --targetId 0x33000004 --public $(SCP_PUBKEY) --name zondax +.PHONY: dev_ca_delete2 dev_ca_delete2: check_python @python -m ledgerblue.resetCustomCA --targetId 0x33000004 + +########################## ZEMU Section ############################### + +.PHONY: zemu_install_js_link +ifeq ($(TESTS_ZEMU_JS_PACKAGE),) +zemu_install_js_link: + @echo "No local package defined" +else +zemu_install_js_link: + # First unlink everything + cd $(TESTS_ZEMU_JS_DIR) && yarn unlink || true + cd $(TESTS_ZEMU_DIR) && yarn unlink $(TESTS_ZEMU_JS_PACKAGE) || true + # Now build and link + cd $(TESTS_ZEMU_JS_DIR) && yarn install && yarn build || true + cd $(TESTS_ZEMU_JS_DIR) && yarn link || true + cd $(TESTS_ZEMU_DIR) && yarn link $(TESTS_ZEMU_JS_PACKAGE) || true +endif + +.PHONY: zemu_install +zemu_install: zemu_install_js_link + # and now install everything + cd $(TESTS_ZEMU_DIR) && yarn install + +.PHONY: zemu +zemu: + cd $(TESTS_ZEMU_DIR)/tools && node debug.mjs + +.PHONY: zemu_debug +zemu_debug: + cd $(TESTS_ZEMU_DIR)/tools && node debug.mjs debug + +########################## TEST Section ############################### + +.PHONY: zemu_test +zemu_test: + cd $(TESTS_ZEMU_DIR) && yarn test + +.PHONY: rust_test +rust_test: + cd app/rust && cargo test + +.PHONY: cpp_test +cpp_test: + mkdir -p build && cd build && cmake -DDISABLE_DOCKER_BUILDS=ON -DCMAKE_BUILD_TYPE=Debug .. && make + cd build && GTEST_COLOR=1 ASAN_OPTIONS=detect_leaks=0 ctest -VV diff --git a/deps/ledger-zxlib/include/view_templates.h b/deps/ledger-zxlib/include/view_templates.h index 0b5c6bcd..1ba531cb 100644 --- a/deps/ledger-zxlib/include/view_templates.h +++ b/deps/ledger-zxlib/include/view_templates.h @@ -155,6 +155,7 @@ NULL, /* text */ \ } +// FIXME: Up/Down vs Left/Right (not supported in zemu) #define UI_BACKGROUND_LEFT_RIGHT_ICONS \ UI_FillRectangle(0, 0, 0, UI_SCREEN_WIDTH, UI_SCREEN_HEIGHT, 0x000000, 0xFFFFFF), \ UI_Icon(UIID_ICONLEFT, 0, 0, 7, 7, BAGL_GLYPH_ICON_LEFT), \ diff --git a/integration/snapshots/.gitignore b/integration/snapshots/.gitignore deleted file mode 100644 index 33662f55..00000000 --- a/integration/snapshots/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/* diff --git a/integration/.babelrc b/tests_zemu/.babelrc similarity index 100% rename from integration/.babelrc rename to tests_zemu/.babelrc diff --git a/integration/.gitignore b/tests_zemu/.gitignore similarity index 100% rename from integration/.gitignore rename to tests_zemu/.gitignore diff --git a/integration/jest.config.js b/tests_zemu/jest.config.js similarity index 100% rename from integration/jest.config.js rename to tests_zemu/jest.config.js diff --git a/integration/jest.js b/tests_zemu/jest.js similarity index 100% rename from integration/jest.js rename to tests_zemu/jest.js diff --git a/integration/package.json b/tests_zemu/package.json similarity index 100% rename from integration/package.json rename to tests_zemu/package.json diff --git a/integration/tests/test.js b/tests_zemu/tests/test.js similarity index 100% rename from integration/tests/test.js rename to tests_zemu/tests/test.js diff --git a/integration/yarn.lock b/tests_zemu/yarn.lock similarity index 100% rename from integration/yarn.lock rename to tests_zemu/yarn.lock From 03c7c504cfac35d5ac63292ba7aca58c0a9e2b3a Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sat, 2 May 2020 17:19:15 +0200 Subject: [PATCH 59/78] remove U2F, improve checks --- app/Makefile | 5 +--- app/script.ld | 2 +- app/src/common/app_main.c | 3 +++ app/src/common/parser_common.h | 1 + app/src/common/tx.c | 2 ++ app/src/crypto.c | 35 ++++++++++++++-------------- app/src/parser.c | 12 +++++++--- app/src/tx_parser.c | 17 ++++++++++++++ app/src/tx_validate.c | 14 +++++------ deps/ledger-zxlib/app/common/view.c | 12 +++++++++- deps/ledger-zxlib/include/zxmacros.h | 20 ++++------------ deps/ledger-zxlib/src/zxmacros.c | 28 +++++++--------------- 12 files changed, 83 insertions(+), 68 deletions(-) diff --git a/app/Makefile b/app/Makefile index 8694b25e..74a26297 100755 --- a/app/Makefile +++ b/app/Makefile @@ -74,9 +74,6 @@ DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=7 IO_HID_EP_LENGTH=64 DEFINES += LEDGER_MAJOR_VERSION=$(APPVERSION_M) LEDGER_MINOR_VERSION=$(APPVERSION_N) LEDGER_PATCH_VERSION=$(APPVERSION_P) -DEFINES += HAVE_U2F HAVE_IO_U2F -DEFINES += U2F_PROXY_MAGIC=\"CSM\" -DEFINES += U2F_MAX_MESSAGE_SIZE=264 #257+5+2 DEFINES += USB_SEGMENT_SIZE=64 DEFINES += HAVE_BOLOS_APP_STACK_CANARY DEFINES += NDEBUG @@ -148,7 +145,7 @@ APP_SOURCE_PATH += $(MY_DIR)/../deps/ledger-zxlib/src APP_SOURCE_PATH += $(MY_DIR)/../deps/ledger-zxlib/app/common APP_SOURCE_PATH += $(MY_DIR)/../deps/jsmn/src -SDK_SOURCE_PATH += lib_stusb lib_u2f lib_stusb_impl +SDK_SOURCE_PATH += lib_stusb lib_stusb_impl #SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl SDK_SOURCE_PATH += lib_ux diff --git a/app/script.ld b/app/script.ld index 2de1b378..81025ad3 100644 --- a/app/script.ld +++ b/app/script.ld @@ -30,7 +30,7 @@ MEMORY } PAGE_SIZE = 64; -STACK_SIZE = 848; +STACK_SIZE = 1104; END_STACK = ORIGIN(SRAM) + LENGTH(SRAM); SECTIONS diff --git a/app/src/common/app_main.c b/app/src/common/app_main.c index 20ce4254..85fc7a03 100644 --- a/app/src/common/app_main.c +++ b/app/src/common/app_main.c @@ -292,13 +292,16 @@ void app_main() { tx = 0; rx = io_exchange(CHANNEL_APDU | flags, rx); flags = 0; + CHECK_APP_CANARY() if (rx == 0) THROW(APDU_CODE_EMPTY_BUFFER); handle_generic_apdu(&flags, &tx, rx); + CHECK_APP_CANARY() handleApdu(&flags, &tx, rx); + CHECK_APP_CANARY() } CATCH_OTHER(e); { diff --git a/app/src/common/parser_common.h b/app/src/common/parser_common.h index 958d480c..9039f0b6 100644 --- a/app/src/common/parser_common.h +++ b/app/src/common/parser_common.h @@ -24,6 +24,7 @@ extern "C" { #define CHECK_PARSER_ERR(__CALL) { \ parser_error_t __err = __CALL; \ + CHECK_APP_CANARY() \ if (__err!=parser_ok) return __err;} typedef enum { diff --git a/app/src/common/tx.c b/app/src/common/tx.c index 655292ea..2bcdf1ab 100644 --- a/app/src/common/tx.c +++ b/app/src/common/tx.c @@ -84,6 +84,8 @@ const char *tx_parse() { } err = parser_validate(&ctx_parsed_tx); + CHECK_APP_CANARY() + if (err != parser_ok) { return parser_getErrorDescription(err); } diff --git a/app/src/crypto.c b/app/src/crypto.c index 074f5074..3ac96f52 100644 --- a/app/src/crypto.c +++ b/app/src/crypto.c @@ -42,15 +42,15 @@ void crypto_extractPublicKey(const uint32_t path[HDPATH_LEN_DEFAULT], uint8_t *p { TRY { // Generate keys - SAFE_HEARTBEAT(os_perso_derive_node_bip32(CX_CURVE_256K1, + os_perso_derive_node_bip32(CX_CURVE_256K1, path, HDPATH_LEN_DEFAULT, privateKeyData, - NULL)); + NULL); - SAFE_HEARTBEAT(cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &cx_privateKey)); - SAFE_HEARTBEAT(cx_ecfp_init_public_key(CX_CURVE_256K1, NULL, 0, &cx_publicKey)); - SAFE_HEARTBEAT(cx_ecfp_generate_pair(CX_CURVE_256K1, &cx_publicKey, &cx_privateKey, 1)); + cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &cx_privateKey); + cx_ecfp_init_public_key(CX_CURVE_256K1, NULL, 0, &cx_publicKey); + cx_ecfp_generate_pair(CX_CURVE_256K1, &cx_publicKey, &cx_privateKey, 1); } FINALLY { MEMZERO(&cx_privateKey, sizeof(cx_privateKey)); @@ -78,7 +78,7 @@ uint16_t crypto_sign(uint8_t *signature, uint8_t messageDigest[CX_SHA256_SIZE]; // Hash it - SAFE_HEARTBEAT(cx_hash_sha256(message, messageLen, messageDigest, CX_SHA256_SIZE)); + cx_hash_sha256(message, messageLen, messageDigest, CX_SHA256_SIZE); cx_ecfp_private_key_t cx_privateKey; uint8_t privateKeyData[32]; @@ -90,23 +90,22 @@ uint16_t crypto_sign(uint8_t *signature, TRY { // Generate keys - SAFE_HEARTBEAT(os_perso_derive_node_bip32(CX_CURVE_256K1, + os_perso_derive_node_bip32(CX_CURVE_256K1, hdPath, HDPATH_LEN_DEFAULT, - privateKeyData, NULL)); + privateKeyData, NULL); - SAFE_HEARTBEAT(cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &cx_privateKey)); + cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &cx_privateKey); // Sign - SAFE_HEARTBEAT( - signatureLength = cx_ecdsa_sign(&cx_privateKey, - CX_RND_RFC6979 | CX_LAST, - CX_SHA256, - messageDigest, - CX_SHA256_SIZE, - signature, - signatureMaxlen, - &info)); + signatureLength = cx_ecdsa_sign(&cx_privateKey, + CX_RND_RFC6979 | CX_LAST, + CX_SHA256, + messageDigest, + CX_SHA256_SIZE, + signature, + signatureMaxlen, + &info); } FINALLY { MEMZERO(&cx_privateKey, sizeof(cx_privateKey)); diff --git a/app/src/parser.c b/app/src/parser.c index 9198779e..57887aa3 100644 --- a/app/src/parser.c +++ b/app/src/parser.c @@ -138,15 +138,15 @@ __Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, const int16_t denomLen = parser_tx_obj.json.tokens[amountToken + 4].end - parser_tx_obj.json.tokens[amountToken + 4].start; - if (sizeof(bufferUI) < amountLen + denomLen + 2) { + if (amountLen <= 0) { return parser_unexpected_buffer_end; } - if (amountLen == 0) { + if (denomLen <= 0) { return parser_unexpected_buffer_end; } - if (denomLen == 0) { + if (sizeof(bufferUI) < (size_t) (amountLen + denomLen + 2) ) { return parser_unexpected_buffer_end; } @@ -171,6 +171,8 @@ parser_error_t parser_getItem(const parser_context_t *ctx, uint16_t numItems; CHECK_PARSER_ERR(parser_getNumItems(ctx, &numItems)) + CHECK_APP_CANARY() + if (numItems == 0) { return parser_unexpected_number_items; } @@ -181,6 +183,7 @@ parser_error_t parser_getItem(const parser_context_t *ctx, uint16_t ret_value_token_index = 0; CHECK_PARSER_ERR(tx_display_query(displayIdx, outKey, outKeyLen, &ret_value_token_index)); + CHECK_APP_CANARY() if (parser_isAmount(outKey)) { CHECK_PARSER_ERR(parser_formatAmount( @@ -193,8 +196,10 @@ parser_error_t parser_getItem(const parser_context_t *ctx, outVal, outValLen, pageIdx, pageCount)) } + CHECK_APP_CANARY() CHECK_PARSER_ERR(tx_display_make_friendly()) + CHECK_APP_CANARY() if (*pageCount > 1) { size_t keyLen = strlen(outKey); @@ -203,5 +208,6 @@ parser_error_t parser_getItem(const parser_context_t *ctx, } } + CHECK_APP_CANARY() return parser_ok; } diff --git a/app/src/tx_parser.c b/app/src/tx_parser.c index 0e4a8712..19209028 100644 --- a/app/src/tx_parser.c +++ b/app/src/tx_parser.c @@ -133,23 +133,35 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to const jsmntype_t token_type = parser_tx_obj.json.tokens[root_token_index].type; if (parser_tx_obj.tx == NULL || root_token_index < 0) { + CHECK_APP_CANARY() return parser_no_data; } + CHECK_APP_CANARY() + if (parser_tx_obj.query.max_level <= 0 || parser_tx_obj.query.max_depth <= 0 || token_type == JSMN_STRING || token_type == JSMN_PRIMITIVE) { + CHECK_APP_CANARY() + const bool groupedField = strcmp("msgs/type", parser_tx_obj.query.out_key) == 0; + CHECK_APP_CANARY() + const bool isMainIndex = parser_tx_obj.filter_msg_type_valid_idx != parser_tx_obj.query._item_index_current; + CHECK_APP_CANARY() + const bool skipItem = parser_tx_obj.flags.cache_valid == 1u && parser_tx_obj.flags.msg_type_grouping == 1u && groupedField && isMainIndex; + CHECK_APP_CANARY() + // Early bail out if (!skipItem && parser_tx_obj.query._item_index_current == parser_tx_obj.query.item_index) { *ret_value_token_index = root_token_index; + CHECK_APP_CANARY() return parser_ok; } @@ -158,6 +170,7 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to } parser_tx_obj.query._item_index_current++; + CHECK_APP_CANARY() return parser_query_no_results; } @@ -181,11 +194,14 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to parser_tx_obj.query.max_level--; parser_tx_obj.query.max_depth--; // Traverse the value, extracting subkeys + CHECK_APP_CANARY() err = tx_traverse_find(value_index, ret_value_token_index); + CHECK_APP_CANARY() parser_tx_obj.query.max_level++; parser_tx_obj.query.max_depth++; if (err == parser_ok) { + CHECK_APP_CANARY() return parser_ok; } @@ -207,6 +223,7 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to parser_tx_obj.query.max_depth++; if (err == parser_ok) { + CHECK_APP_CANARY() return parser_ok; } } diff --git a/app/src/tx_validate.c b/app/src/tx_validate.c index 643738fa..48fae7d4 100644 --- a/app/src/tx_validate.c +++ b/app/src/tx_validate.c @@ -41,7 +41,7 @@ int8_t contains_whitespace(parsed_json_t *json) { const int last_element_index = json->tokens[0].end; // Starting at token 1 because token 0 contains full tx - for (int i = 1; i < json->numberOfTokens; i++) { + for (uint32_t i = 1; i < json->numberOfTokens; i++) { if (json->tokens[i].type != JSMN_UNDEFINED) { const int end = json->tokens[i].start; for (int j = start; j < end; j++) { @@ -86,7 +86,7 @@ int8_t is_sorted(int16_t first_index, } int8_t dictionaries_sorted(parsed_json_t *json) { - for (int i = 0; i < json->numberOfTokens; i++) { + for (uint32_t i = 0; i < json->numberOfTokens; i++) { if (json->tokens[i].type == JSMN_OBJECT) { uint16_t count; @@ -135,23 +135,23 @@ parser_error_t tx_validate(parsed_json_t *json) { if (err != parser_ok) return parser_json_missing_chain_id; - err = object_get_value(json,0,"sequence", &token_index); + err = object_get_value(json, 0, "sequence", &token_index); if (err != parser_ok) return parser_json_missing_sequence; - err = object_get_value(json,0,"fee", &token_index); + err = object_get_value(json, 0, "fee", &token_index); if (err != parser_ok) return parser_json_missing_fee; - err = object_get_value(json,0,"msgs", &token_index); + err = object_get_value(json, 0, "msgs", &token_index); if (err != parser_ok) return parser_json_missing_msgs; - err = object_get_value(json,0,"account_number", &token_index); + err = object_get_value(json, 0, "account_number", &token_index); if (err != parser_ok) return parser_json_missing_account_number; - err = object_get_value(json,0,"memo", &token_index); + err = object_get_value(json, 0, "memo", &token_index); if (err != parser_ok) return parser_json_missing_memo; diff --git a/deps/ledger-zxlib/app/common/view.c b/deps/ledger-zxlib/app/common/view.c index 8ecbda88..9ae9554f 100644 --- a/deps/ledger-zxlib/app/common/view.c +++ b/deps/ledger-zxlib/app/common/view.c @@ -99,7 +99,8 @@ void h_paging_decrease() { if (viewdata.itemIdx > 0) { viewdata.itemIdx--; // jump to last page. update will cap this value - viewdata.pageIdx = 255; + viewdata.pageIdx = 0; +// viewdata.pageIdx = 255; } } } @@ -120,6 +121,15 @@ view_error_t h_review_update_data() { viewdata.value, MAX_CHARS_PER_VALUE1_LINE, viewdata.pageIdx, &viewdata.pageCount); +// if (err == tx_no_data && viewdata.pageIdx > viewdata.pageCount) { +// // Retry capping +// viewdata.pageIdx = viewdata.pageCount; +// err = tx_getItem(viewdata.itemIdx, +// viewdata.key, MAX_CHARS_PER_KEY_LINE, +// viewdata.value, MAX_CHARS_PER_VALUE1_LINE, +// viewdata.pageIdx, &viewdata.pageCount); +// } + if (err == tx_no_data) { return view_no_data; } diff --git a/deps/ledger-zxlib/include/zxmacros.h b/deps/ledger-zxlib/include/zxmacros.h index 133ccaf4..df37bb7c 100644 --- a/deps/ledger-zxlib/include/zxmacros.h +++ b/deps/ledger-zxlib/include/zxmacros.h @@ -38,12 +38,9 @@ extern void explicit_bzero(void *__s, size_t __n) __THROW __nonnull ((1)); #if defined(TARGET_NANOX) #define NV_CONST const #define NV_VOL volatile -#define SAFE_HEARTBEAT(X) X; #else #define NV_CONST #define NV_VOL -// Disabling heartbeat (this was a Nano S workaround for U2F) -#define SAFE_HEARTBEAT(X) X; /*io_seproxyhal_io_heartbeat(); X; io_seproxyhal_io_heartbeat();*/ #endif #ifndef PIC @@ -85,15 +82,6 @@ extern void explicit_bzero(void *__s, size_t __n) __THROW __nonnull ((1)); #define MEMCPY_NV nvm_write #define MEMZERO explicit_bzero -void debug_printf(void* buffer); - -#undef LOG -#undef LOGSTACK -#define LOG(str) debug_printf(str) -extern unsigned int app_stack_canary; -void __logstack(); -#define LOGSTACK() __logstack() - #else #define MEMMOVE memmove @@ -111,9 +99,6 @@ void __logstack(); __Z_INLINE void __memzero(void *buffer, size_t s) { memset(buffer, 0, s); } #define MEMZERO __memzero #endif - -#define LOG(str) -#define LOGSTACK() #endif #include @@ -125,6 +110,11 @@ __Z_INLINE void __memzero(void *buffer, size_t s) { memset(buffer, 0, s); } MEMCPY_NV((void*) PIC(DST), (void *) PIC(&nvset_tmp), sizeof(TYPE)); \ } +#define APP_STACK_CANARY_MAGIC 0xDEAD0031 +extern unsigned int app_stack_canary; +void handle_stack_overflow(); +#define CHECK_APP_CANARY() { if (app_stack_canary != APP_STACK_CANARY_MAGIC) handle_stack_overflow(); } + #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #define __SWAP(v) (((v) & 0x000000FFu) << 24u | ((v) & 0x0000FF00u) << 8u | ((v) & 0x00FF0000u) >> 8u | ((v) & 0xFF000000u) >> 24u) #define HtoNL(v) __SWAP( v ) diff --git a/deps/ledger-zxlib/src/zxmacros.c b/deps/ledger-zxlib/src/zxmacros.c index fc98f341..7c6ca13e 100644 --- a/deps/ledger-zxlib/src/zxmacros.c +++ b/deps/ledger-zxlib/src/zxmacros.c @@ -16,24 +16,6 @@ #include "zxmacros.h" #include "utf8.h" -#ifdef LEDGER_SPECIFIC -#include -#include "stdint.h" -void __logstack() -{ - uint8_t st; - uint32_t tmp1 = (uint32_t)&st - (uint32_t)&app_stack_canary; - uint32_t tmp2 = 0x20002800 - (uint32_t)&st; - char buffer[30]; - snprintf(buffer, 40, "%d / %d", tmp1, tmp2); - LOG(buffer); -} -#else - -void __logstack() {} - -#endif - size_t asciify(char *utf8_in_ascii_out) { return asciify_ext(utf8_in_ascii_out, utf8_in_ascii_out); @@ -47,7 +29,7 @@ size_t asciify_ext(const char *utf8_in, char *ascii_only_out) { while (*((char *) p) && utf8valid(p) == 0) { utf8_int32_t tmp_codepoint = 0; p = utf8codepoint(p, &tmp_codepoint); - *q = (tmp_codepoint >= 32 && tmp_codepoint <= 0x7F)? tmp_codepoint : '.'; + *q = (tmp_codepoint >= 32 && tmp_codepoint <= 0x7F) ? tmp_codepoint : '.'; q++; } @@ -55,3 +37,11 @@ size_t asciify_ext(const char *utf8_in, char *ascii_only_out) { *q = 0; return q - ascii_only_out; } + +void handle_stack_overflow() { +#if defined (TARGET_NANOS) || defined(TARGET_NANOX) + io_seproxyhal_se_reset(); +#else + while(1); +#endif +} From 0a71fbb37f1995ba588fcd2eed39f4c14a621664 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sat, 2 May 2020 17:19:22 +0200 Subject: [PATCH 60/78] add zemu tests --- .gdbinit | 11 + .gitignore | 1 + deps/ledger-zxlib/app/common/view.c | 9 - deps/ledger-zxlib/include/zxmacros.h | 13 +- tests_zemu/globalsetup.js | 15 + tests_zemu/jest.config.js | 2 + tests_zemu/package.json | 18 +- tests_zemu/snapshots/show-address/0.png | Bin 0 -> 809 bytes tests_zemu/snapshots/show-address/1.png | Bin 0 -> 833 bytes tests_zemu/snapshots/show-address/2.png | Bin 0 -> 792 bytes tests_zemu/snapshots/show-address/3.png | Bin 0 -> 575 bytes tests_zemu/snapshots/show-address/4.png | Bin 0 -> 303 bytes tests_zemu/snapshots/show-address/5.png | Bin 0 -> 521 bytes tests_zemu/snapshots/show-address/tmp0.png | Bin 0 -> 809 bytes tests_zemu/snapshots/sign-basic/0.png | Bin 0 -> 461 bytes tests_zemu/snapshots/sign-basic/1.png | Bin 0 -> 361 bytes tests_zemu/snapshots/sign-basic/10.png | Bin 0 -> 1040 bytes tests_zemu/snapshots/sign-basic/11.png | Bin 0 -> 567 bytes tests_zemu/snapshots/sign-basic/12.png | Bin 0 -> 964 bytes tests_zemu/snapshots/sign-basic/13.png | Bin 0 -> 687 bytes tests_zemu/snapshots/sign-basic/14.png | Bin 0 -> 644 bytes tests_zemu/snapshots/sign-basic/15.png | Bin 0 -> 713 bytes tests_zemu/snapshots/sign-basic/2.png | Bin 0 -> 383 bytes tests_zemu/snapshots/sign-basic/3.png | Bin 0 -> 380 bytes tests_zemu/snapshots/sign-basic/4.png | Bin 0 -> 355 bytes tests_zemu/snapshots/sign-basic/5.png | Bin 0 -> 518 bytes tests_zemu/snapshots/sign-basic/6.png | Bin 0 -> 1040 bytes tests_zemu/snapshots/sign-basic/7.png | Bin 0 -> 567 bytes tests_zemu/snapshots/sign-basic/8.png | Bin 0 -> 986 bytes tests_zemu/snapshots/sign-basic/9.png | Bin 0 -> 701 bytes tests_zemu/snapshots/signTransaction.png | Bin 0 -> 713 bytes tests_zemu/tests/test.js | 247 ++- tests_zemu/tools/debug.mjs | 110 + tests_zemu/yarn.lock | 2151 +++++++------------- 34 files changed, 1127 insertions(+), 1450 deletions(-) create mode 100644 .gdbinit create mode 100644 tests_zemu/globalsetup.js create mode 100644 tests_zemu/snapshots/show-address/0.png create mode 100644 tests_zemu/snapshots/show-address/1.png create mode 100644 tests_zemu/snapshots/show-address/2.png create mode 100644 tests_zemu/snapshots/show-address/3.png create mode 100644 tests_zemu/snapshots/show-address/4.png create mode 100644 tests_zemu/snapshots/show-address/5.png create mode 100644 tests_zemu/snapshots/show-address/tmp0.png create mode 100644 tests_zemu/snapshots/sign-basic/0.png create mode 100644 tests_zemu/snapshots/sign-basic/1.png create mode 100644 tests_zemu/snapshots/sign-basic/10.png create mode 100644 tests_zemu/snapshots/sign-basic/11.png create mode 100644 tests_zemu/snapshots/sign-basic/12.png create mode 100644 tests_zemu/snapshots/sign-basic/13.png create mode 100644 tests_zemu/snapshots/sign-basic/14.png create mode 100644 tests_zemu/snapshots/sign-basic/15.png create mode 100644 tests_zemu/snapshots/sign-basic/2.png create mode 100644 tests_zemu/snapshots/sign-basic/3.png create mode 100644 tests_zemu/snapshots/sign-basic/4.png create mode 100644 tests_zemu/snapshots/sign-basic/5.png create mode 100644 tests_zemu/snapshots/sign-basic/6.png create mode 100644 tests_zemu/snapshots/sign-basic/7.png create mode 100644 tests_zemu/snapshots/sign-basic/8.png create mode 100644 tests_zemu/snapshots/sign-basic/9.png create mode 100644 tests_zemu/snapshots/signTransaction.png create mode 100644 tests_zemu/tools/debug.mjs diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 00000000..8abe1fbb --- /dev/null +++ b/.gdbinit @@ -0,0 +1,11 @@ +# https://www.jetbrains.com/help/clion/configuring-debugger-options.html#gdbinit-lldbinit +# +# You need to create `$HOME/.gdbinit` with the following content: +# set auto-load local-gdbinit on +# add-auto-load-safe-path / + +set architecture arm +handle SIGILL nostop pass noprint +add-symbol-file app/bin/app.elf 0x40000000 +set backtrace limit 20 +b *0x40000000 diff --git a/.gitignore b/.gitignore index ed1588b1..e445c919 100644 --- a/.gitignore +++ b/.gitignore @@ -86,3 +86,4 @@ cmake-build-fuzz/ app/src/glyphs.c app/src/glyphs.h +/build diff --git a/deps/ledger-zxlib/app/common/view.c b/deps/ledger-zxlib/app/common/view.c index 9ae9554f..8a63c891 100644 --- a/deps/ledger-zxlib/app/common/view.c +++ b/deps/ledger-zxlib/app/common/view.c @@ -121,15 +121,6 @@ view_error_t h_review_update_data() { viewdata.value, MAX_CHARS_PER_VALUE1_LINE, viewdata.pageIdx, &viewdata.pageCount); -// if (err == tx_no_data && viewdata.pageIdx > viewdata.pageCount) { -// // Retry capping -// viewdata.pageIdx = viewdata.pageCount; -// err = tx_getItem(viewdata.itemIdx, -// viewdata.key, MAX_CHARS_PER_KEY_LINE, -// viewdata.value, MAX_CHARS_PER_VALUE1_LINE, -// viewdata.pageIdx, &viewdata.pageCount); -// } - if (err == tx_no_data) { return view_no_data; } diff --git a/deps/ledger-zxlib/include/zxmacros.h b/deps/ledger-zxlib/include/zxmacros.h index df37bb7c..067ec02c 100644 --- a/deps/ledger-zxlib/include/zxmacros.h +++ b/deps/ledger-zxlib/include/zxmacros.h @@ -31,6 +31,8 @@ extern void explicit_bzero(void *__s, size_t __n) __THROW __nonnull ((1)); #endif #define __Z_INLINE inline __attribute__((always_inline)) static +void handle_stack_overflow(); + #if defined(LEDGER_SPECIFIC) #include "bolos_target.h" #endif @@ -61,6 +63,10 @@ extern void explicit_bzero(void *__s, size_t __n) __THROW __nonnull ((1)); #include "os_io_seproxyhal.h" #endif +#define CHECK_APP_CANARY() { if (app_stack_canary != APP_STACK_CANARY_MAGIC) handle_stack_overflow(); } +#define APP_STACK_CANARY_MAGIC 0xDEAD0031 +extern unsigned int app_stack_canary; + #define WAIT_EVENT() io_seproxyhal_spi_recv(G_io_seproxyhal_spi_buffer, sizeof(G_io_seproxyhal_spi_buffer), 0) #define UX_WAIT() \ @@ -84,6 +90,8 @@ extern void explicit_bzero(void *__s, size_t __n) __THROW __nonnull ((1)); #else +#define CHECK_APP_CANARY() {} + #define MEMMOVE memmove #define MEMSET memset #define MEMCPY memcpy @@ -110,11 +118,6 @@ __Z_INLINE void __memzero(void *buffer, size_t s) { memset(buffer, 0, s); } MEMCPY_NV((void*) PIC(DST), (void *) PIC(&nvset_tmp), sizeof(TYPE)); \ } -#define APP_STACK_CANARY_MAGIC 0xDEAD0031 -extern unsigned int app_stack_canary; -void handle_stack_overflow(); -#define CHECK_APP_CANARY() { if (app_stack_canary != APP_STACK_CANARY_MAGIC) handle_stack_overflow(); } - #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #define __SWAP(v) (((v) & 0x000000FFu) << 24u | ((v) & 0x0000FF00u) << 8u | ((v) & 0x00FF0000u) >> 8u | ((v) & 0xFF000000u) >> 24u) #define HtoNL(v) __SWAP( v ) diff --git a/tests_zemu/globalsetup.js b/tests_zemu/globalsetup.js new file mode 100644 index 00000000..ab86d45f --- /dev/null +++ b/tests_zemu/globalsetup.js @@ -0,0 +1,15 @@ +import Zemu from "@zondax/zemu"; + +const catchExit = async () => { + process.on("SIGINT", () => { + Zemu.stopAllEmuContainers(function () { + process.exit(); + }); + }); +}; + +module.exports = async () => { + await catchExit(); + await Zemu.checkAndPullImage(); + await Zemu.stopAllEmuContainers(); +}; diff --git a/tests_zemu/jest.config.js b/tests_zemu/jest.config.js index 53ac6c65..fbceb386 100644 --- a/tests_zemu/jest.config.js +++ b/tests_zemu/jest.config.js @@ -14,6 +14,8 @@ module.exports = { // The directory where Jest should output its coverage files coverageDirectory: "coverage", + globalSetup: "/globalsetup.js", + // A list of paths to directories that Jest should use to search for files in roots: [""], diff --git a/tests_zemu/package.json b/tests_zemu/package.json index c9fb88a5..d74ed3ed 100644 --- a/tests_zemu/package.json +++ b/tests_zemu/package.json @@ -6,7 +6,6 @@ "description": "", "main": "index.js", "scripts": { - "pull": "docker pull zondax/builder-bolos-emu:latest", "test": "jest --detectOpenHandles" }, "repository": { @@ -17,25 +16,26 @@ "zondax" ], "dependencies": { - "@zondax/zemu": "^0.0.7", - "ledger-cosmos-js": "^2.1.7" + "ledger-cosmos-js": "^2", + "@zondax/zemu": "^0.2.4" }, "devDependencies": { "@babel/cli": "^7.8.4", "@babel/core": "^7.9.0", "@babel/node": "^7.8.7", "@babel/plugin-transform-runtime": "^7.9.0", - "@babel/preset-env": "^7.9.0", + "@babel/preset-env": "^7.9.5", "babel-eslint": "^10.1.0", - "babel-jest": "^25.2.6", + "babel-jest": "24", "eslint": "^6.8.0", "eslint-config-airbnb-base": "^14.1.0", "eslint-config-prettier": "^6.10.1", "eslint-plugin-import": "^2.20.2", "eslint-plugin-jest": "^23.8.2", - "eslint-plugin-prettier": "^3.1.2", - "eslint-plugin-vue": "^6.2.2", - "jest": "^25.2.7 ", - "jest-serial-runner": "^1.1.0 " + "eslint-plugin-prettier": "^3.1.3", + "jest": "24", + "jest-serial-runner": "^1.1.0 ", + "secp256k1": "^4.0.1", + "crypto-js": "4.0.0" } } diff --git a/tests_zemu/snapshots/show-address/0.png b/tests_zemu/snapshots/show-address/0.png new file mode 100644 index 0000000000000000000000000000000000000000..c337bb87e8603b8b16059c4a4df8f5b5c337005b GIT binary patch literal 809 zcmXAoeMl2w7{F)N6x*47VD5^XiCSVQ=c$>A+MGJnp_!O5tHE{7IdmoA7UDca7+=on3Nq^Jv6%Rmhxe?wYN#RuoT{~p&T=s{CB*|(0X7iIuFbVk=qk- zS#OR9R!@1!**G#M+HO%5-J#lTB^|bAn?K`qTY|M|rdAX3H>=S3kA_r743{tyR*Mul zB#6H$>;Sq&`W@-EHESS`kg}CZX6YCb*AsL&9b<3SZ~+)M_q>eq>g6h_c++ARh;~~R z5(ZfkA)ex&YPij8L{54KquOYDSHp%sf}bguV4N(N2nGApYGb4E38fG-?G8-g$ehA1 z$GF3sZ2dT~MdlanViXjmpv0&vp>4MLV)(cfN>uUMPn;es5E7!EwpucwQk(O!z(3IF z<4z4%^h!(=-&j~G#!4!APXO>@=fj$B6Q%qfze`PK2@=&{G@V4aO~b;H374(+hP?qy z1vPNeSvnI9#XNNvh@X=J1xM`d&BcfuV;|)uj6edUc9_YXsUK0V4W9ttHVxDV;UGmR z31a3atz3Y>&@diE#Idwgm@(0~cFrvUC$ zDG#6kZ6)O>CETS1WQGlJdejwMvIGmANzk8h2%$|Gj))WX^7OT2R)HK0(WBbFMEI*0 zqj@t{yeNUh)jrU*(OF95O&Z=;L_z-58~}e=C6*Ff`6{({frVFKW(ihU-Jd{OV(h+x zV&=2E~lJGgaUuVZO{5(%Ej{ wz0#uL=DrV&rbYZ(n+ClD7V@moXwq$q>U0^kuYUxlz!MND6{^^tvsZ5Y1Axa|0RR91 literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/show-address/1.png b/tests_zemu/snapshots/show-address/1.png new file mode 100644 index 0000000000000000000000000000000000000000..d45cb1bee5e743fc5959900ad88579846008c268 GIT binary patch literal 833 zcmXw2e@GK?6rV`v)LoW}x>Iy3bt6L8+6MMh-LyV|FwLJK#vBbQ3uHnQhW1YBkNduR_rCA*KJR_rwdHEh;6X$X zkH^Ea&T8_&Ho+s}V{m>wU`pWe{P|g$w1RrS-(wf@4i<;2$GU=EZSVP|c3F=UY;;|d zDWZtu`M!S1!+LA-&p^qsA8qlme_ESrW+EPMXhK8&>RllxolXm1Iy^5!NtIFZ09@@Z zQHTbOixV*lfslxhk6@^(!ZlCa#J4-B3_C5v%O?av1lCNIG_LkkSf4xlFhoYlyY%9t zkO0P$$P_AlD0+C-^m)Lzi`vH}rx_H75uHM(xjXf&?-52&4y{44{3+}kMAnF;O6z?G zxv135%1{I_sl?TW1$dQrHDky&IbKJ0Iuj9{hB4wnC}^g76~10oRkvQ!;;{HJX0(#w z&XKb*tjHsl2gpw|O*O#)F)c%x_H@I-0FVZcGr(OY-ili tx@kcbyOjfeHD4N1XLLC&*H0`DZS$_?>#08?LiHg`(fteC+-xoxSaHQm%kI8C!3)T+U?CdjZeJ_34+3)uK z1hb}gkBTsTz9WbhA4(9bB;JVtP7EFz;O<;g$%%t0gg8~Rfziu0@TQxn?`BnH>l)Cg zl!Me_T*((fRlv-Et`-uQ1M0aO0iLY#cd^#BX&JN~m<7qlnQfw8hlrpa5(kZp9P$R; zV6vQ`o{w;2Xhksdw4;%%t={P6;P*$M%@gSLERWr|QeNZ&L-wQTC3Og{F)7#IE@S`( zSj6}zNvVx(WDT$7*g;P0zjzxvtm4y&pOT2fK&Lm!Y^>e{DS7yV7sqgUmo$gUy literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/show-address/3.png b/tests_zemu/snapshots/show-address/3.png new file mode 100644 index 0000000000000000000000000000000000000000..4cb7de4b78e860e7fbd40eb813e1f0d6af1baba3 GIT binary patch literal 575 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfg%sx;TbZFupw)n0Gru#O2`S zmg@i9@A~@+4C@SZZ+ggR&z!JE=>N-J6}{(+?iDPaeb3`jksQ-0-{_Ld(w;Hr5+C>8 zE3g!0eCi?f?nI5};R%+jdKXXnsmg0I=PY;nr47o)Qq$TLH=F1fxo;{|GdiJQEY)^K zsrgLbnZUgTA-v^Mde!C9eq+a+TgM_#^soWV0utw(94gl{&OF^{qVW0N zv1;BEF??=qZQsrAWjQ=63NSL+v-RUDp!C6$7d~V?p31{Jy*b-jmlG|wY5vVGvSd;aXZ|1Ms}vGWJ*~I(2;G5&1W(yJkm}m0O=pO zM*TT|w57_V#r zMRav4&i4O1L#LOLkBK2*$&s+kT z0`ydk$Fu%PKZRz-t9{T+>W!u53{LtiyEb u=@v7+<=YA^xAvZ1nH%Gm`?x5}o?*7njqrF~&C9@q!QkoY=d#Wzp$PzAS^ZrA literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/show-address/4.png b/tests_zemu/snapshots/show-address/4.png new file mode 100644 index 0000000000000000000000000000000000000000..9794a2c11ed34beb917ada3db1306974c3a22cff GIT binary patch literal 303 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW&!sc)Vxjv*C{Zx1>aNeA*cA7l@! z_|II-_L_TSDZs+%0Bg1qiQCikcWuuYE{T~Ugf(c=3l59UW zw2K`5b=rAW(%)wfm#Mi9eSJ-07}&l9)b+fO6Ls-Stiq z(`Q)tqIXOmML@ZVh7FnNXWh7phLz0p>)iNLGz>CB!-mYN0T~)rGBgY_L&G35G;GMc zn}t6?!%Ak=up@)aMguad24r>`Rx+!G9T{Za(Zba=Jk+q!K*J#o4>fEw(D06K9M$j= znN!KoFf<@@3K<%P24vpJ!qv$;6*LSo$P5in$vh=9eecFmGCxLU zkfC9x;i-n{m>X9h^N`Gr%u_PcF$>d2TbMqYfHHloh3TU$Odstp_w%_!CBXMQ00000 LNkvXXu0mjfZSw8( literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/show-address/tmp0.png b/tests_zemu/snapshots/show-address/tmp0.png new file mode 100644 index 0000000000000000000000000000000000000000..c337bb87e8603b8b16059c4a4df8f5b5c337005b GIT binary patch literal 809 zcmXAoeMl2w7{F)N6x*47VD5^XiCSVQ=c$>A+MGJnp_!O5tHE{7IdmoA7UDca7+=on3Nq^Jv6%Rmhxe?wYN#RuoT{~p&T=s{CB*|(0X7iIuFbVk=qk- zS#OR9R!@1!**G#M+HO%5-J#lTB^|bAn?K`qTY|M|rdAX3H>=S3kA_r743{tyR*Mul zB#6H$>;Sq&`W@-EHESS`kg}CZX6YCb*AsL&9b<3SZ~+)M_q>eq>g6h_c++ARh;~~R z5(ZfkA)ex&YPij8L{54KquOYDSHp%sf}bguV4N(N2nGApYGb4E38fG-?G8-g$ehA1 z$GF3sZ2dT~MdlanViXjmpv0&vp>4MLV)(cfN>uUMPn;es5E7!EwpucwQk(O!z(3IF z<4z4%^h!(=-&j~G#!4!APXO>@=fj$B6Q%qfze`PK2@=&{G@V4aO~b;H374(+hP?qy z1vPNeSvnI9#XNNvh@X=J1xM`d&BcfuV;|)uj6edUc9_YXsUK0V4W9ttHVxDV;UGmR z31a3atz3Y>&@diE#Idwgm@(0~cFrvUC$ zDG#6kZ6)O>CETS1WQGlJdejwMvIGmANzk8h2%$|Gj))WX^7OT2R)HK0(WBbFMEI*0 zqj@t{yeNUh)jrU*(OF95O&Z=;L_z-58~}e=C6*Ff`6{({frVFKW(ihU-Jd{OV(h+x zV&=2E~lJGgaUuVZO{5(%Ej{ wz0#uL=DrV&rbYZ(n+ClD7V@moXwq$q>U0^kuYUxlz!MND6{^^tvsZ5Y1Axa|0RR91 literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-basic/0.png b/tests_zemu/snapshots/sign-basic/0.png new file mode 100644 index 0000000000000000000000000000000000000000..275655482e0c7838fe7ed6a7ce3d169c5fbe4b00 GIT binary patch literal 461 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfdN>ba4!+V0?QpFz>d5fXl(_ z+~5B4+sMnd8L%meuoo#cCzY)}(*8!}HF6Uz5~7OXkT$GcFe*rV}`;?d4CJZ5zhWy{qr z2PqU!$ken`1u{PGzx_j@^M6>^)9-AXCpe~AO5gGAdRl7s&ZSLcNrsWX=SN+j&38H* zHuz+3@W}-s8?TSyK{mRqnjVEqd!I@f>9QJy-`Ek{5F(nE_6)HJ*0c|mvIc?F& zEh4_lmO6k@hSB@H$6A^bEIk{2yc>NcS1N#9;&CwuL|;ykJF!LNgpDgu>9e`K?^KR% z_b8kxav~pHp8tIL0BhEe^LMJHn%9U_*QMl3TUguXE!-Z`gGzqO(m7)=bGu6{1- HoD!M$5FNu6Ka@c# z7BoqOym|J>RVcc{@#qpDXUqARHyX?SN{d?uZ9KB&eC!()Hc@`94o6n4j!2;D8LnW4 z&5;@vpsC!93uUt}^qFP2OV3egn_+HvWKXB!GnVFj@#Zdb!xav!hCp`!UAbMl##;34 zcfDJaxsudG@8?~ZmlV**m9+2f0@>UPePxAOqDOqR#6ZNpxeXWkOo8TYSUs;)OjMK`j1zK5*pbJ6R zWwtnyEVQUuSxB^SC~}pzILL}6n1v^~{)o!5D1)-+o&9m|-M#PoywCIfe4n>rn=6-` zP0iNnbfhDX-2rbeJf?U8#vyZ!MW@3D9c7pB}l=qej( zZop=VSG$iGdcR9cJ_ocj>Saot^izJ7pHCYo@mfW=QLs$J=eB;$fBrjvZmq@h(yPig z)5J2a2&b7ne5PBuO>m--^c~F`o%$WyReqx)9J+}@=g>@xGO&sgRKA4nfY~k4+N0wu zPdzQJ%5*cKo0#;X#+*TkMp#q^yeiDvOkLbxHm)~sw2ZIYDMzTDvjmsUm?jDndKO5l z(ZNcK(kL9hj!B)z#;xZk55Zz2A^DERy2AtaxO4@EBDtxQTv;`rMn4-1a1i5Hi<7pU zt|gTg#^ixleSqMSwUPdQn_fz+tYfgT{_>9vo3e1y3X#7=Ve6~$073D=QrULp*IUq> zM5er~c>rp&qWco=M(%MrD2vmLH2zQNbSt4-m^6n(Y8*J-ff*?PWhFbImNo!z3mEgk zNS}=o*H%ueO-BTC8ww%{e9n-A_r z8pTK51oy6hkWQW$P3FXF+Rm`aE*Elq5WT1zoNUvaHYhE3(eMSxP`}(riO10wNbsuQ zw~{7ZAUMCCv_m3~fo>fep=c(}$W0qK%nE`9<$oAiHQgnhO%T+=q)ChI@|~H@DN|6S zn^cDPBGpQ0aNefBRWqLXdSCaY0_fTUYtBz@C6(L4Vh@nqm>nYs0aki&U0pM9!09hb zJzWIX(~P;%TIG@=rLqEVB(KgEv0-dsfh2Hn8RVB1<$<_Lc1m}XA`PGTjKfgWp^$tzx YR^)zopZBAE1>AIOu~S&fG0Eq0*aoG7s=oPMRsDHZy>|w%G{&X78d={i`{OfL^0dW7cAEs&C$q~$C_EbAjCwm>xC!Q z3*D?17nO}6MNlowGNgg*56@U_T0>1yOxQm)2`o#K>UVR01h?C>-*fx@zTeOHIoH0j zA~FS~$Ye6Iq}X@>zB%})mnz`?XR7>w#_f>o3Y8EB=@Zgb~HI3;PYS7lJwUwm0R7x8qr zdTlvwU)jEOuO4 z|9m-VVI5uhDwM855L=PkJ25t6EyY-KL5d5+cO*s69uD4Sfp8|2rP6y)JGEEexWhzH zsQ~{;-1$4Y0Au{YIR$Fz#0|?fK7&kQ^7NTIJ1Zdj(jaKfpn$|CGLxdgi3c+n6I8`X zsH(M6OdWL5o0FS>-O5*v!VOXq_b@` z+N-);P|c=l+eYeBROsk=C1sY59|O}svKXmLlcIvAK#U8mGZ0i;YjS^Alv7f4vpsBv z;2HFEzzPi literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-basic/13.png b/tests_zemu/snapshots/sign-basic/13.png new file mode 100644 index 0000000000000000000000000000000000000000..00e4c0099cc2934f778d34959041def7cb4112bb GIT binary patch literal 687 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFff&Qx;TbZFupw)n0MQQ!R6p} zmf!!_-}U#E3Ghr?AbZ+O)lzZwk^H?Ak2squY`)xZX1CM?NvR2y!OTYf7Y$BcFgW?f zM8Wy6im{20(fKXk+NAG(aTDTKc4jsLswkiQa~fL;pQ7?;52@i{U z*i!7SG@S9@_=yW>%;w7l=8{tPu6|h7=Ex=~r8ZMPdqzrzL)!AhNtLRMPd`=io{(9^ zI5RzJXHD#3h0nUY>y;M7_+>xz;5Ct1^zgLQyK4@I-I#gHvlleB^|s|_-pE++&_mlj z)8Ux_Z~V1_?QAJ#;Bfjm;SA7HMUczd*i!m76t%Q1OaumC8N{0oZ9v_;Gv~6UWZcLE zIzU2-ZKi#eLt2)@vtTJTNhvdsF_lZEDg*s_rtb`p03ttTqxX|$N=*PFP&7CnRse>! z|3#1jpk2mm=eQoeE46P4M^%s6xRi5wGfX#MF8lUtiONirp4Mv9ylFe;wK@=iYEc6c{1h-5+GWBhU-7G-fy502h3=hmU8 gSH$#|Zv#rjSLW&%UGLwX4NQm(p00i_>zopr0PJHPr2qf` literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-basic/14.png b/tests_zemu/snapshots/sign-basic/14.png new file mode 100644 index 0000000000000000000000000000000000000000..dae18a50f0dcadb5df981183c70f20bbf47bfd71 GIT binary patch literal 644 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfe(0x;TbZFupw)m?s@6;(YLM z+m8RNpPQ9qc;xO(6tM_Xk$$;O=>O}fDt^Zo#T71H9q0L|M26{9XmrU{ZO=JOFL%CeA$#lC%L`_H?b`~iw)9?Jm}|4Tv2AH%+eL@NFB{pU_FZlbpH#WB zP4V+%Q=XZw&vwUFENxTVZ1U#B1%ni~!xJQVozpTSq}q6RK>`^!CPqq4SeQ6blGoa3 zaiV+L@`nrC6rB%Gh?EkWxs*{7NT%oR2|hf*G0jB5SSrtiXQu2-SGJi;6W!S)fyVhU z8}Xl1*nC2v`Ai1LP@@wdPH{NM9&6qcHC~5fdF5j(rt$Ea=opzxO>1ihV*SaHQu8i> zTocP1f2|;#*{I&9jV)ywTZ-MT53(uumL;C(Y4e}FFflU%sHR`lnc2uh;q&U=@`*on zL6*BdyM038v!+qJTJiKV!6_LI&*t^+cTAgD>5*n~!QpUliW@U;`N}X7VBk6cLpo&| zP$qoW2R_~tf2N%Q3V{Or=EQ}Jpny!v1c!fQN*PeuQpTBc*;3wR0d+xCRZitzuVyUO lcILKejVH5__u>7|?5|%G_GmoX+y+ct44$rjF6*2UngDC22r2*o literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-basic/15.png b/tests_zemu/snapshots/sign-basic/15.png new file mode 100644 index 0000000000000000000000000000000000000000..1e430afe4949126c0cf7e2855169f446ec26c4f6 GIT binary patch literal 713 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfdKvUzxavyHU=5CL*{+Pq7ZDhh@)XvY_e5PC|VBXTi2|tT~?EdQpC;phI z$1O~pASq>b@%l5SqmE5rna@4=44gO06K&BM9sDjtJzZ6 zB&FUd8JqqIJFM_oRfY+ad5r9*@fev; ztekO1ZRUSpU|?>W^TQ`?@)`M)K+k-R26{F&C1XM2gq;e`%)I5<5fde)GH$2<*^_yU zOfDFlh?%gkvCZ*WX39FjfH;!?bn3^_;zna32^m$j=;e3 zm6~vKb8ERG(9k<;(y2orU&4T@vg! z=?90V*i6-7gx$MkrQu{K9m@}O=o|*TcrEUWZqllMkn4RT-auo5p8_~Xr$Uq z*EW&KXD)McymXyp|-o9&U<_h;S-gJ^pDISey zrZLUbWi^@&qE(N|`o8l?^ZfWWsKmsgZnERD$p z2Wkcyt@hboSfFnk(7DZvw*u8Z$jB|&VrCH~4R$P&@LTr1q6O_SD=V)7LzThP)z4*} HQ$iB}wU?e? literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-basic/4.png b/tests_zemu/snapshots/sign-basic/4.png new file mode 100644 index 0000000000000000000000000000000000000000..f6fde190611f7ecba69d9a941efe9c97be4fdc4b GIT binary patch literal 355 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfeL)x;TbZFupzLn0MEK$K~L4 z=8yl_pDkJ}&^Fc-Syxx&%h0ZL11pa z+JQL-IJtmUi>Pa8_yq*aS+Kyu(Xp_p=@AR-QJ6%<>Z2LChEdTVrN{n9@0km9>HNq& zGeMMa!o5=`-3}f`m=q^>bP0 Hl+XkKO(%?6 literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-basic/5.png b/tests_zemu/snapshots/sign-basic/5.png new file mode 100644 index 0000000000000000000000000000000000000000..f6fa06307b04002121f668c03b3737f1fc523bc7 GIT binary patch literal 518 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfg9=ba4!+V0?SWrPOA6wzYXa^C>&$^-OQ3dCI#1q#L{K1R-mH@~rQW;Oz%_mdVf&Sajsl5r-G zUir`=ZPH9BHc6m?j9_A>f2Knk(C7?@HlR;I1juHGv@9U1STc8WR>cgkrRz=hG|e@h zEGhM`^UQ@0ZD-JX`yoxF1kFhIUs;)OjMK`j1zK5*pbJ6R zWwtnyEVQUuSxB^SC~}pzILL}6n1v^~{)o!5D1)-+o&9m|-M#PoywCIfe4n>rn=6-` zP0iNnbfhDX-2rbeJf?U8#vyZ!MW@3D9c7pB}l=qej( zZop=VSG$iGdcR9cJ_ocj>Saot^izJ7pHCYo@mfW=QLs$J=eB;$fBrjvZmq@h(yPig z)5J2a2&b7ne5PBuO>m--^c~F`o%$WyReqx)9J+}@=g>@xGO&sgRKA4nfY~k4+N0wu zPdzQJ%5*cKo0#;X#+*TkMp#q^yeiDvOkLbxHm)~sw2ZIYDMzTDvjmsUm?jDndKO5l z(ZNcK(kL9hj!B)z#;xZk55Zz2A^DERy2AtaxO4@EBDtxQTv;`rMn4-1a1i5Hi<7pU zt|gTg#^ixleSqMSwUPdQn_fz+tYfgT{_>9vo3e1y3X#7=Ve6~$073D=QrULp*IUq> zM5er~c>rp&qWco=M(%MrD2vmLH2zQNbSt4-m^6n(Y8*J-ff*?PWhFbImNo!z3mEgk zNS}=o*H%ueO-BTC8ww%{e9n-A_r z8pTK51oy6hkWQW$P3FXF+Rm`aE*Elq5WT1zoNUvaHYhE3(eMSxP`}(riO10wNbsuQ zw~{7ZAUMCCv_m3~fo>fep=c(}$W0qK%nE`9<$oAiHQgnhO%T+=q)ChI@|~H@DN|6S zn^cDPBGpQ0aNefBRWqLXdSCaY0_fTUYtBz@C6(L4Vh@nqm>nYs0aki&U0pM9!09hb zJzWIX(~P;%TIG@=rLqEVB(KgEv0-dsfh2Hn8RVB1<$<_Lc1m}XA`PGTjKfgWp^$tzx YR^)zopZBAE1>AIOu~S&fG0Eq0*aoG7s=oPMRsDHZy>nyJxfSl8;RO>hR_F=R2|-xv4`H5#4ls_S?(;eCe}lYz0@j949&IJRBSDjo2yU(k2Vwfl?UueUO_)2J4hw&i`bW;rWsI(js8WC1a zGm1-hz6+=_4BgXt;U8a}*m^y=aoYI{JHPhr8}x5ug|u*_UdIXxzVy8q6*BcDIa!?d zZf`DMLua#lyu(4+t#n|c8DSVJEkK7Zpb7A#A<~l@ZpAPGSVB0TWLpeo1Zm0<@iWSN zOhdkrf~$!(0V4*pNXwLf@!{mP->~8O0kyQ!a92z6$pB!l6LUBu$=Zw~ubW5K==jq< zYicuFF*Ig^4sCQ@bIMz693(N?354(Ym{Jd}RF#$;lZ{>M4K32$nd$|80c6gkrWhjK9 z&}s1sR^xtOp7UJqEDqFf{5@sLD8kxkYiHtn7l=$x9GHqB2gE;nFe2zVN@ds`Oa$d* z0nIG+Rf6apJZQr=qu2@QJuo;dX(_9$IK_xl=oPMQfc_I}1w`>I3gCw+L$O%;qyfkqrw za0Z(KG-&df{B57wRqii#UO zBQtL?2LoO2%6lTmgvW>6lggMO1tiU+G8QCeSzI~XO)oew^GfrYTqp=-29gekLwSP_D~HW<1bUB6Qfh*wvazW`GtdP< zbZ2Segq_jMM$)_|{)o<8nV98}mT@CvM#_YlQb43OGd=nzFDSZz`q*Yp&-@W|*cBN3 zZa@t{O2rxIvyQ2@KuwDifexCm(-WwB!q1msbwVKZ?O=BoPdx)vpMSaGOsv!dp!Gl3 zP5s{uj4id9()~~}GG)0BH)M0S$3~+_)iW$`uxGj*tDgl-mJFV*elF{r5}E+tcN>fV literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/signTransaction.png b/tests_zemu/snapshots/signTransaction.png new file mode 100644 index 0000000000000000000000000000000000000000..1e430afe4949126c0cf7e2855169f446ec26c4f6 GIT binary patch literal 713 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfdKvUzxavyHU=5CL*{+Pq7ZDhh@)XvY_e5PC|VBXTi2|tT~?EdQpC;phI z$1O~pASq>b@%l5SqmE5rna@4=44gO06K&BM9sDjtJzZ6 zB&FUd8JqqIJFM_oRfY+ad5r9*@fev; ztekO1ZRUSpU|?>W^TQ`?@)`M)K+k-R26{F&C1XM2gq;e`%)I5<5fde)GH$2<*^_yU zOfDFlh?%gkvCZ*WX39FjfH;!?bn3^_;zna32^m$j=;e3 zm6~vKb8ERG(9 { + Zemu.default.stopAllEmuContainers(function () { + process.exit(); + }); + }); + await Zemu.default.checkAndPullImage(); +} + +async function beforeEnd() { + await Zemu.default.stopAllEmuContainers(); +} + +async function debugScenario(sim, app) { + const path = [44, 118, 0, 0, 0]; + let tx = JSON.stringify(example_tx_str); + + // do not wait here.. + const signatureRequest = app.sign(path, tx); + await Zemu.default.sleep(1000); + + // await sim.clickRight(); + // await sim.clickRight(); + // await sim.clickRight(); + // await sim.clickRight(); + // await sim.clickRight(); + // await sim.clickRight(); + // await sim.clickRight(); + // await sim.clickRight(); + // await sim.clickRight(); + + let resp = await signatureRequest; + console.log(resp); +} + +async function main() {m + await beforeStart(); + + if (process.argv.length > 2 && process.argv[2] === "debug") { + SIM_OPTIONS["custom"] = SIM_OPTIONS["custom"] + " --debug"; + } + + const sim = new Zemu.default(APP_PATH); + + try { + await sim.start(SIM_OPTIONS); + const app = new CosmosApp.default(sim.getTransport()); + + //////////// + /// TIP you can use zemu commands here to take the app to the point where you trigger a breakpoint + + await debugScenario(sim, app); + + /// TIP + + } finally { + await sim.close(); + await beforeEnd(); + } +} + +(async () => { + await main(); +})(); diff --git a/tests_zemu/yarn.lock b/tests_zemu/yarn.lock index ae34c9a2..a0d4d98d 100644 --- a/tests_zemu/yarn.lock +++ b/tests_zemu/yarn.lock @@ -25,28 +25,28 @@ dependencies: "@babel/highlight" "^7.8.3" -"@babel/compat-data@^7.8.6", "@babel/compat-data@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.0.tgz#04815556fc90b0c174abd2c0c1bb966faa036a6c" - integrity sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g== +"@babel/compat-data@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.6.tgz#3f604c40e420131affe6f2c8052e9a275ae2049b" + integrity sha512-5QPTrNen2bm7RBc7dsOmcA5hbrS4O2Vhmk5XOL4zWW/zD/hV0iinpefDlkm+tBBy8kDtFaaeEvmAqt+nURAV2g== dependencies: - browserslist "^4.9.1" + browserslist "^4.11.1" invariant "^2.2.4" semver "^5.5.0" -"@babel/core@^7.1.0", "@babel/core@^7.7.5", "@babel/core@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" - integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w== +"@babel/core@^7.1.0", "@babel/core@^7.9.0": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.6.tgz#d9aa1f580abf3b2286ef40b6904d390904c63376" + integrity sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg== dependencies: "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.0" + "@babel/generator" "^7.9.6" "@babel/helper-module-transforms" "^7.9.0" - "@babel/helpers" "^7.9.0" - "@babel/parser" "^7.9.0" + "@babel/helpers" "^7.9.6" + "@babel/parser" "^7.9.6" "@babel/template" "^7.8.6" - "@babel/traverse" "^7.9.0" - "@babel/types" "^7.9.0" + "@babel/traverse" "^7.9.6" + "@babel/types" "^7.9.6" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.1" @@ -56,12 +56,12 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.4.0", "@babel/generator@^7.9.0", "@babel/generator@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.5.tgz#27f0917741acc41e6eaaced6d68f96c3fa9afaf9" - integrity sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ== +"@babel/generator@^7.4.0", "@babel/generator@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.6.tgz#5408c82ac5de98cda0d77d8124e99fa1f2170a43" + integrity sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ== dependencies: - "@babel/types" "^7.9.5" + "@babel/types" "^7.9.6" jsesc "^2.5.1" lodash "^4.17.13" source-map "^0.5.0" @@ -81,13 +81,13 @@ "@babel/helper-explode-assignable-expression" "^7.8.3" "@babel/types" "^7.8.3" -"@babel/helper-compilation-targets@^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz#dac1eea159c0e4bd46e309b5a1b04a66b53c1dde" - integrity sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw== +"@babel/helper-compilation-targets@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.9.6.tgz#1e05b7ccc9d38d2f8b40b458b380a04dcfadd38a" + integrity sha512-x2Nvu0igO0ejXzx09B/1fGBxY9NXQlBW2kZsSxCJft+KHN8t9XWzIvFxtPHnBOAXpVsdxZKZFbRUC8TsNKajMw== dependencies: - "@babel/compat-data" "^7.8.6" - browserslist "^4.9.1" + "@babel/compat-data" "^7.9.6" + browserslist "^4.11.1" invariant "^2.2.4" levenary "^1.1.1" semver "^5.5.0" @@ -199,14 +199,14 @@ "@babel/types" "^7.8.3" "@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8" - integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA== + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.9.6.tgz#03149d7e6a5586ab6764996cd31d6981a17e1444" + integrity sha512-qX+chbxkbArLyCImk3bWV+jB5gTNU/rsze+JlcF6Nf8tVTigPJSI1o1oBow/9Resa1yehUO9lIipsmu9oG4RzA== dependencies: "@babel/helper-member-expression-to-functions" "^7.8.3" "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/traverse" "^7.8.6" - "@babel/types" "^7.8.6" + "@babel/traverse" "^7.9.6" + "@babel/types" "^7.9.6" "@babel/helper-simple-access@^7.8.3": version "7.8.3" @@ -238,14 +238,14 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" -"@babel/helpers@^7.9.0": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f" - integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA== +"@babel/helpers@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.6.tgz#092c774743471d0bb6c7de3ad465ab3d3486d580" + integrity sha512-tI4bUbldloLcHWoRUMAj4g1bF313M/o6fBKhIsb3QnGVPwRm9JsNf/gqMkQ7zjqReABiffPV6RWj7hEglID5Iw== dependencies: "@babel/template" "^7.8.3" - "@babel/traverse" "^7.9.0" - "@babel/types" "^7.9.0" + "@babel/traverse" "^7.9.6" + "@babel/types" "^7.9.6" "@babel/highlight@^7.8.3": version "7.9.0" @@ -270,10 +270,10 @@ resolve "^1.13.1" v8flags "^3.1.1" -"@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0", "@babel/parser@^7.7.5", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0": - version "7.9.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8" - integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA== +"@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0", "@babel/parser@^7.8.6", "@babel/parser@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.6.tgz#3b1bbb30dabe600cd72db58720998376ff653bc7" + integrity sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q== "@babel/plugin-proposal-async-generator-functions@^7.8.3": version "7.8.3" @@ -316,10 +316,10 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-numeric-separator" "^7.8.3" -"@babel/plugin-proposal-object-rest-spread@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.5.tgz#3fd65911306d8746014ec0d0cf78f0e39a149116" - integrity sha512-VP2oXvAf7KCYTthbUHwBlewbl1Iq059f6seJGsxMizaCdgHIeczOr7FBqELhSqfkIl04Fi8okzWzl63UKbQmmg== +"@babel/plugin-proposal-object-rest-spread@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.6.tgz#7a093586fcb18b08266eb1a7177da671ac575b63" + integrity sha512-Ga6/fhGqA9Hj+y6whNpPv8psyaK5xzrQwSPsGPloVkvmH+PqW1ixdnfJ9uIO06OjQNYol3PMnfmJ8vfZtkzF+A== dependencies: "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-object-rest-spread" "^7.8.0" @@ -356,13 +356,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-bigint@^7.0.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - "@babel/plugin-syntax-dynamic-import@^7.8.0": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" @@ -530,34 +523,34 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-modules-amd@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz#19755ee721912cf5bb04c07d50280af3484efef4" - integrity sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q== +"@babel/plugin-transform-modules-amd@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.6.tgz#8539ec42c153d12ea3836e0e3ac30d5aae7b258e" + integrity sha512-zoT0kgC3EixAyIAU+9vfaUVKTv9IxBDSabgHoUCBP6FqEJ+iNiN7ip7NBKcYqbfUDfuC2mFCbM7vbu4qJgOnDw== dependencies: "@babel/helper-module-transforms" "^7.9.0" "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz#e3e72f4cbc9b4a260e30be0ea59bdf5a39748940" - integrity sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g== +"@babel/plugin-transform-modules-commonjs@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.6.tgz#64b7474a4279ee588cacd1906695ca721687c277" + integrity sha512-7H25fSlLcn+iYimmsNe3uK1at79IE6SKW9q0/QeEHTMC9MdOZ+4bA+T1VFB5fgOqBWoqlifXRzYD0JPdmIrgSQ== dependencies: "@babel/helper-module-transforms" "^7.9.0" "@babel/helper-plugin-utils" "^7.8.3" "@babel/helper-simple-access" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-systemjs@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz#e9fd46a296fc91e009b64e07ddaa86d6f0edeb90" - integrity sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ== +"@babel/plugin-transform-modules-systemjs@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.6.tgz#207f1461c78a231d5337a92140e52422510d81a4" + integrity sha512-NW5XQuW3N2tTHim8e1b7qGy7s0kZ2OH3m5octc49K1SdAKGxYxeIx7hiIz05kS1R2R+hOWcsr1eYwcGhrdHsrg== dependencies: "@babel/helper-hoist-variables" "^7.8.3" "@babel/helper-module-transforms" "^7.9.0" "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" + babel-plugin-dynamic-import-node "^2.3.3" "@babel/plugin-transform-modules-umd@^7.9.0": version "7.9.0" @@ -619,9 +612,9 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-transform-runtime@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.0.tgz#45468c0ae74cc13204e1d3b1f4ce6ee83258af0b" - integrity sha512-pUu9VSf3kI1OqbWINQ7MaugnitRss1z533436waNXp+0N3ur3zfut37sXiQMxkuCF4VUjwZucen/quskCh7NHw== + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.6.tgz#3ba804438ad0d880a17bca5eaa0cdf1edeedb2fd" + integrity sha512-qcmiECD0mYOjOIt8YHNsAP1SxPooC/rDmfmiSK9BNY72EitdSc7l44WTEklaWuFtbOEBjNhWWyph/kOImbNJ4w== dependencies: "@babel/helper-module-imports" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" @@ -673,13 +666,13 @@ "@babel/helper-create-regexp-features-plugin" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" -"@babel/preset-env@^7.9.0": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.5.tgz#8ddc76039bc45b774b19e2fc548f6807d8a8919f" - integrity sha512-eWGYeADTlPJH+wq1F0wNfPbVS1w1wtmMJiYk55Td5Yu28AsdR9AsC97sZ0Qq8fHqQuslVSIYSGJMcblr345GfQ== +"@babel/preset-env@^7.9.5": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.6.tgz#df063b276c6455ec6fcfc6e53aacc38da9b0aea6" + integrity sha512-0gQJ9RTzO0heXOhzftog+a/WyOuqMrAIugVYxMYf83gh1CQaQDjMtsOpqOwXyDL/5JcWsrCm8l4ju8QC97O7EQ== dependencies: - "@babel/compat-data" "^7.9.0" - "@babel/helper-compilation-targets" "^7.8.7" + "@babel/compat-data" "^7.9.6" + "@babel/helper-compilation-targets" "^7.9.6" "@babel/helper-module-imports" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-proposal-async-generator-functions" "^7.8.3" @@ -687,7 +680,7 @@ "@babel/plugin-proposal-json-strings" "^7.8.3" "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" "@babel/plugin-proposal-numeric-separator" "^7.8.3" - "@babel/plugin-proposal-object-rest-spread" "^7.9.5" + "@babel/plugin-proposal-object-rest-spread" "^7.9.6" "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" "@babel/plugin-proposal-optional-chaining" "^7.9.0" "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" @@ -714,9 +707,9 @@ "@babel/plugin-transform-function-name" "^7.8.3" "@babel/plugin-transform-literals" "^7.8.3" "@babel/plugin-transform-member-expression-literals" "^7.8.3" - "@babel/plugin-transform-modules-amd" "^7.9.0" - "@babel/plugin-transform-modules-commonjs" "^7.9.0" - "@babel/plugin-transform-modules-systemjs" "^7.9.0" + "@babel/plugin-transform-modules-amd" "^7.9.6" + "@babel/plugin-transform-modules-commonjs" "^7.9.6" + "@babel/plugin-transform-modules-systemjs" "^7.9.6" "@babel/plugin-transform-modules-umd" "^7.9.0" "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" "@babel/plugin-transform-new-target" "^7.8.3" @@ -732,8 +725,8 @@ "@babel/plugin-transform-typeof-symbol" "^7.8.4" "@babel/plugin-transform-unicode-regex" "^7.8.3" "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.9.5" - browserslist "^4.9.1" + "@babel/types" "^7.9.6" + browserslist "^4.11.1" core-js-compat "^3.6.2" invariant "^2.2.2" levenary "^1.1.1" @@ -762,13 +755,13 @@ source-map-support "^0.5.16" "@babel/runtime@^7.7.4", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" - integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f" + integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.4.0", "@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": +"@babel/template@^7.4.0", "@babel/template@^7.8.3", "@babel/template@^7.8.6": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg== @@ -777,35 +770,30 @@ "@babel/parser" "^7.8.6" "@babel/types" "^7.8.6" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.4", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2" - integrity sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ== +"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0", "@babel/traverse@^7.8.3", "@babel/traverse@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.6.tgz#5540d7577697bf619cc57b92aa0f1c231a94f442" + integrity sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg== dependencies: "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.5" + "@babel/generator" "^7.9.6" "@babel/helper-function-name" "^7.9.5" "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/parser" "^7.9.0" - "@babel/types" "^7.9.5" + "@babel/parser" "^7.9.6" + "@babel/types" "^7.9.6" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.13" -"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444" - integrity sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg== +"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5", "@babel/types@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.6.tgz#2c5502b427251e9de1bd2dff95add646d95cc9f7" + integrity sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA== dependencies: "@babel/helper-validator-identifier" "^7.9.5" lodash "^4.17.13" to-fast-properties "^2.0.0" -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== - "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -814,21 +802,6 @@ exec-sh "^0.3.2" minimist "^1.2.0" -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz#10602de5570baea82f8afbfa2630b24e7a8cfe5b" - integrity sha512-ZR0rq/f/E4f4XcgnDvtMWXCUJpi8eO0rssVhmztsZqLIEFA9UUP9zmpE0VxlM+kv/E1ul2I876Fwil2ayptDVg== - dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" - -"@istanbuljs/schema@^0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" - integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== - "@jest/console@^24.7.1", "@jest/console@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.9.0.tgz#79b1bc06fb74a8cfb01cbdedf945584b1b9707f0" @@ -838,49 +811,39 @@ chalk "^2.0.1" slash "^2.0.0" -"@jest/console@^25.2.6": - version "25.2.6" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-25.2.6.tgz#f594847ec8aef3cf27f448abe97e76e491212e97" - integrity sha512-bGp+0PicZVCEhb+ifnW9wpKWONNdkhtJsRE7ap729hiAfTvCN6VhGx0s/l/V/skA2pnyqq+N/7xl9ZWfykDpsg== +"@jest/core@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.9.0.tgz#2ceccd0b93181f9c4850e74f2a9ad43d351369c4" + integrity sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A== dependencies: - "@jest/source-map" "^25.2.6" - chalk "^3.0.0" - jest-util "^25.2.6" - slash "^3.0.0" - -"@jest/core@^25.2.7": - version "25.2.7" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-25.2.7.tgz#58d697687e94ee644273d15e4eed6a20e27187cd" - integrity sha512-Nd6ELJyR+j0zlwhzkfzY70m04hAur0VnMwJXVe4VmmD/SaQ6DEyal++ERQ1sgyKIKKEqRuui6k/R0wHLez4P+g== - dependencies: - "@jest/console" "^25.2.6" - "@jest/reporters" "^25.2.6" - "@jest/test-result" "^25.2.6" - "@jest/transform" "^25.2.6" - "@jest/types" "^25.2.6" - ansi-escapes "^4.2.1" - chalk "^3.0.0" + "@jest/console" "^24.7.1" + "@jest/reporters" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + ansi-escapes "^3.0.0" + chalk "^2.0.1" exit "^0.1.2" - graceful-fs "^4.2.3" - jest-changed-files "^25.2.6" - jest-config "^25.2.7" - jest-haste-map "^25.2.6" - jest-message-util "^25.2.6" - jest-regex-util "^25.2.6" - jest-resolve "^25.2.6" - jest-resolve-dependencies "^25.2.7" - jest-runner "^25.2.7" - jest-runtime "^25.2.7" - jest-snapshot "^25.2.7" - jest-util "^25.2.6" - jest-validate "^25.2.6" - jest-watcher "^25.2.7" - micromatch "^4.0.2" - p-each-series "^2.1.0" - realpath-native "^2.0.0" - rimraf "^3.0.0" - slash "^3.0.0" - strip-ansi "^6.0.0" + graceful-fs "^4.1.15" + jest-changed-files "^24.9.0" + jest-config "^24.9.0" + jest-haste-map "^24.9.0" + jest-message-util "^24.9.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.9.0" + jest-resolve-dependencies "^24.9.0" + jest-runner "^24.9.0" + jest-runtime "^24.9.0" + jest-snapshot "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" + jest-watcher "^24.9.0" + micromatch "^3.1.10" + p-each-series "^1.0.0" + realpath-native "^1.1.0" + rimraf "^2.5.4" + slash "^2.0.0" + strip-ansi "^5.0.0" "@jest/environment@^24.9.0": version "24.9.0" @@ -892,15 +855,6 @@ "@jest/types" "^24.9.0" jest-mock "^24.9.0" -"@jest/environment@^25.2.6": - version "25.2.6" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-25.2.6.tgz#8f7931e79abd81893ce88b7306f0cc4744835000" - integrity sha512-17WIw+wCb9drRNFw1hi8CHah38dXVdOk7ga9exThhGtXlZ9mK8xH4DjSB9uGDGXIWYSHmrxoyS6KJ7ywGr7bzg== - dependencies: - "@jest/fake-timers" "^25.2.6" - "@jest/types" "^25.2.6" - jest-mock "^25.2.6" - "@jest/fake-timers@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.9.0.tgz#ba3e6bf0eecd09a636049896434d306636540c93" @@ -910,47 +864,32 @@ jest-message-util "^24.9.0" jest-mock "^24.9.0" -"@jest/fake-timers@^25.2.6": - version "25.2.6" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.2.6.tgz#239dbde3f56badf7d05bcf888f5d669296077cad" - integrity sha512-A6qtDIA2zg/hVgUJJYzQSHFBIp25vHdSxW/s4XmTJAYxER6eL0NQdQhe4+232uUSviKitubHGXXirt5M7blPiA== - dependencies: - "@jest/types" "^25.2.6" - jest-message-util "^25.2.6" - jest-mock "^25.2.6" - jest-util "^25.2.6" - lolex "^5.0.0" - -"@jest/reporters@^25.2.6": - version "25.2.6" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-25.2.6.tgz#6d87e40fb15adb69e22bb83aa02a4d88b2253b5f" - integrity sha512-DRMyjaxcd6ZKctiXNcuVObnPwB1eUs7xrUVu0J2V0p5/aZJei5UM9GL3s/bmN4hRV8Mt3zXh+/9X2o0Q4ClZIA== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^25.2.6" - "@jest/test-result" "^25.2.6" - "@jest/transform" "^25.2.6" - "@jest/types" "^25.2.6" - chalk "^3.0.0" - collect-v8-coverage "^1.0.0" +"@jest/reporters@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.9.0.tgz#86660eff8e2b9661d042a8e98a028b8d631a5b43" + integrity sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw== + dependencies: + "@jest/environment" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + chalk "^2.0.1" exit "^0.1.2" glob "^7.1.2" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.0" - jest-haste-map "^25.2.6" - jest-resolve "^25.2.6" - jest-util "^25.2.6" - jest-worker "^25.2.6" - slash "^3.0.0" + istanbul-lib-coverage "^2.0.2" + istanbul-lib-instrument "^3.0.1" + istanbul-lib-report "^2.0.4" + istanbul-lib-source-maps "^3.0.1" + istanbul-reports "^2.2.6" + jest-haste-map "^24.9.0" + jest-resolve "^24.9.0" + jest-runtime "^24.9.0" + jest-util "^24.9.0" + jest-worker "^24.6.0" + node-notifier "^5.4.2" + slash "^2.0.0" source-map "^0.6.0" - string-length "^3.1.0" - terminal-link "^2.0.0" - v8-to-istanbul "^4.0.1" - optionalDependencies: - node-notifier "^6.0.0" + string-length "^2.0.0" "@jest/source-map@^24.3.0", "@jest/source-map@^24.9.0": version "24.9.0" @@ -961,15 +900,6 @@ graceful-fs "^4.1.15" source-map "^0.6.0" -"@jest/source-map@^25.2.6": - version "25.2.6" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-25.2.6.tgz#0ef2209514c6d445ebccea1438c55647f22abb4c" - integrity sha512-VuIRZF8M2zxYFGTEhkNSvQkUKafQro4y+mwUxy5ewRqs5N/ynSFUODYp3fy1zCnbCMy1pz3k+u57uCqx8QRSQQ== - dependencies: - callsites "^3.0.0" - graceful-fs "^4.2.3" - source-map "^0.6.0" - "@jest/test-result@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.9.0.tgz#11796e8aa9dbf88ea025757b3152595ad06ba0ca" @@ -979,16 +909,6 @@ "@jest/types" "^24.9.0" "@types/istanbul-lib-coverage" "^2.0.0" -"@jest/test-result@^25.2.6": - version "25.2.6" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-25.2.6.tgz#f6082954955313eb96f6cabf9fb14f8017826916" - integrity sha512-gmGgcF4qz/pkBzyfJuVHo2DA24kIgVQ5Pf/VpW4QbyMLSegi8z+9foSZABfIt5se6k0fFj/3p/vrQXdaOgit0w== - dependencies: - "@jest/console" "^25.2.6" - "@jest/types" "^25.2.6" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - "@jest/test-sequencer@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz#f8f334f35b625a4f2f355f2fe7e6036dad2e6b31" @@ -999,16 +919,6 @@ jest-runner "^24.9.0" jest-runtime "^24.9.0" -"@jest/test-sequencer@^25.2.7": - version "25.2.7" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-25.2.7.tgz#e4331f7b4850e34289b9a5c8ec8a2c03b400da8f" - integrity sha512-s2uYGOXONDSTJQcZJ9A3Zkg3hwe53RlX1HjUNqjUy3HIqwgwCKJbnAKYsORPbhxXi3ARMKA7JNBi9arsTxXoYw== - dependencies: - "@jest/test-result" "^25.2.6" - jest-haste-map "^25.2.6" - jest-runner "^25.2.7" - jest-runtime "^25.2.7" - "@jest/transform@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.9.0.tgz#4ae2768b296553fadab09e9ec119543c90b16c56" @@ -1031,28 +941,6 @@ source-map "^0.6.1" write-file-atomic "2.4.1" -"@jest/transform@^25.2.6": - version "25.2.6" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-25.2.6.tgz#007fd946dedf12d2a9eb5d4154faf1991d5f61ff" - integrity sha512-rZnjCjZf9avPOf9q/w9RUZ9Uc29JmB53uIXNJmNz04QbDMD5cR/VjfikiMKajBsXe2vnFl5sJ4RTt+9HPicauQ== - dependencies: - "@babel/core" "^7.1.0" - "@jest/types" "^25.2.6" - babel-plugin-istanbul "^6.0.0" - chalk "^3.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.2.3" - jest-haste-map "^25.2.6" - jest-regex-util "^25.2.6" - jest-util "^25.2.6" - micromatch "^4.0.2" - pirates "^4.0.1" - realpath-native "^2.0.0" - slash "^3.0.0" - source-map "^0.6.1" - write-file-atomic "^3.0.0" - "@jest/types@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59" @@ -1062,16 +950,6 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" -"@jest/types@^25.2.6": - version "25.2.6" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.2.6.tgz#c12f44af9bed444438091e4b59e7ed05f8659cb6" - integrity sha512-myJTTV37bxK7+3NgKc4Y/DlQ5q92/NOwZsZ+Uch7OXdElxOg61QYc72fPYNAjlvbnJ2YvbXLamIsa9tj48BmyQ== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^15.0.0" - chalk "^3.0.0" - "@ledgerhq/devices@^4.78.0": version "4.78.0" resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-4.78.0.tgz#149b572f0616096e2bd5eb14ce14d0061c432be6" @@ -1081,33 +959,33 @@ "@ledgerhq/logs" "^4.72.0" rxjs "^6.5.3" -"@ledgerhq/devices@^5.12.0": - version "5.12.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-5.12.0.tgz#974c6a418b80b3917a3a7527e80e82886a0b3580" - integrity sha512-6dduN4KkSMtBUmxLUBlTH7RXwnc0+1aiCjZMl2unL8CY9u4hGNzu1eN7y2SnR0V5PQQrDXCbQ+MAKmFJa0eR6w== +"@ledgerhq/devices@^5.13.1": + version "5.13.1" + resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-5.13.1.tgz#1d65fcbcc470874968e2e74f7e87e3f350c4b332" + integrity sha512-E3zgmA51+esMkczM9xLddVz0myXTauJaT5g4bbwGxxeHQyHyvaJNyOhMmcuxZN/aa/lfRmxYGthcN2JBUPju5A== dependencies: - "@ledgerhq/errors" "^5.12.0" - "@ledgerhq/logs" "^5.11.0" - rxjs "^6.5.4" + "@ledgerhq/errors" "^5.13.1" + "@ledgerhq/logs" "^5.13.1" + rxjs "^6.5.5" "@ledgerhq/errors@^4.78.0": version "4.78.0" resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-4.78.0.tgz#23daf3af54d03b1bda3e616002b555da1bdb705a" integrity sha512-FX6zHZeiNtegBvXabK6M5dJ+8OV8kQGGaGtuXDeK/Ss5EmG4Ltxc6Lnhe8hiHpm9pCHtktOsnUVL7IFBdHhYUg== -"@ledgerhq/errors@^5.12.0": - version "5.12.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-5.12.0.tgz#2fdfb08ee54c8bb73e0074835c24243cedf4583f" - integrity sha512-jF5Km9Zh9Swt9tsNoFtB4tfcb7PmK/tubx+Z9Kt8ivJ9pF8+zthlTNr6sL1hBsfkjln+gYqLIHSUWcitlMN/Kw== +"@ledgerhq/errors@^5.13.1": + version "5.13.1" + resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-5.13.1.tgz#1df73a8084609888fabcefbdb9edfd4c11ab3a22" + integrity sha512-IuEw9a70K3C3AZV4yVGk75HlwmKmJaR6EjMIxBAupiCw0G6rBP0d62MA1Vx4dg082LKKNXKafWcDstLG4ySpBA== -"@ledgerhq/hw-transport-http@^5.12.0": - version "5.12.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-http/-/hw-transport-http-5.12.0.tgz#19e8250e2d72a32c59bbfb9b85892b01ebbbca7c" - integrity sha512-SciMHeztVNjXwjwxI7JX4jdFmmZgTmGEHMRKqJXSA/ZUXp3mfeLy9EkghpO7YFS3x/s4BlbN7u/fn3FAOVKnFQ== +"@ledgerhq/hw-transport-http@^5.13.1": + version "5.13.1" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-http/-/hw-transport-http-5.13.1.tgz#f0220bc58b3905ba10b8355984cf082a66d637f5" + integrity sha512-mdsF2ZRAwEGZV/rsNnZeT6+T1bISu3jgYbxI91H9MzBdnaC1Q7UwN+b/d+MWFFHd3QpPX46k0EaBVjjcyUaNlw== dependencies: - "@ledgerhq/errors" "^5.12.0" - "@ledgerhq/hw-transport" "^5.12.0" - "@ledgerhq/logs" "^5.11.0" + "@ledgerhq/errors" "^5.13.1" + "@ledgerhq/hw-transport" "^5.13.1" + "@ledgerhq/logs" "^5.13.1" axios "^0.19.0" ws "6" @@ -1120,13 +998,13 @@ "@ledgerhq/errors" "^4.78.0" events "^3.0.0" -"@ledgerhq/hw-transport@^5.12.0": - version "5.12.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-5.12.0.tgz#1fc6ca7e96851e4ffbb550c2980dc676abf4ed5e" - integrity sha512-TsGLDY4anie4f3nMPPC4defJAVfuakkTOKntKcPhnGBGFO4HhJGlpZ5LWx50RhYGP7c86QpwPhmxsnx8sLIJjQ== +"@ledgerhq/hw-transport@^5.13.1": + version "5.13.1" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-5.13.1.tgz#b0fb8208b0fb383f3984c1af26a9b9e7a3ad3d75" + integrity sha512-TQJJY10ZoToYjaGK+u9wud0W2dK+6TBA753FGlHgptydSaRmep0uc4A2TxOLPexdVlbHXmkMa1skd3w4ZrKKdA== dependencies: - "@ledgerhq/devices" "^5.12.0" - "@ledgerhq/errors" "^5.12.0" + "@ledgerhq/devices" "^5.13.1" + "@ledgerhq/errors" "^5.13.1" events "^3.1.0" "@ledgerhq/logs@^4.72.0": @@ -1134,17 +1012,10 @@ resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-4.72.0.tgz#43df23af013ad1135407e5cf33ca6e4c4c7708d5" integrity sha512-o+TYF8vBcyySRsb2kqBDv/KMeme8a2nwWoG+lAWzbDmWfb2/MrVWYCVYDYvjXdSoI/Cujqy1i0gIDrkdxa9chA== -"@ledgerhq/logs@^5.11.0": - version "5.11.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.11.0.tgz#9ad2aefceeef48cf9d77972f67e63ba478dd04cc" - integrity sha512-NiFDdxLU/z1VGQy0/cbpv7UScMDQ/rU8SznqILSHYTnhK2xvvNFTUkd1W2mpmf9E/hzXFI0UAOieLQ44qovX3w== - -"@sinonjs/commons@^1.7.0": - version "1.7.1" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.1.tgz#da5fd19a5f71177a53778073978873964f49acf1" - integrity sha512-Debi3Baff1Qu1Unc3mjJ96MgpbwTn43S1+9yJ0llWygPwDNu2aaWBD6yc9y/Z8XDRNhx7U+u2UDg2OGQXkclUQ== - dependencies: - type-detect "4.0.8" +"@ledgerhq/logs@^5.13.1": + version "5.13.1" + resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.13.1.tgz#80a13031c9e0a9b874b96b6636f3f6df603ec1b2" + integrity sha512-ag2wX5VcAqPMKooCn/S6kKblVlsn74ixtagpwR+6GdFqYa/bspH8mWw+zwcdzSwa/wbOQRuY75zH52qBonfXBA== "@types/babel__core@^7.1.0": version "7.1.7" @@ -1173,9 +1044,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.0.10" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.10.tgz#d9a99f017317d9b3d1abc2ced45d3bca68df0daf" - integrity sha512-74fNdUGrWsgIB/V9kTO5FGHPWYY6Eqn+3Z7L6Hc4e/BxjYV7puvBqp5HwsVYYfLm6iURYBNCx4Ut37OF9yitCw== + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.11.tgz#1ae3010e8bf8851d324878b42acec71986486d18" + integrity sha512-ddHK5icION5U6q11+tV2f9Mo6CZVuT8GJKld2q9LqHSZbvLbH34Kcu2yFGckZut453+eQU6btIA3RihmnRgI+Q== dependencies: "@babel/types" "^7.3.0" @@ -1184,7 +1055,7 @@ resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg== @@ -1209,11 +1080,6 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA== -"@types/prettier@^1.19.0": - version "1.19.1" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f" - integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ== - "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -1231,27 +1097,20 @@ dependencies: "@types/yargs-parser" "*" -"@types/yargs@^15.0.0": - version "15.0.4" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.4.tgz#7e5d0f8ca25e9d5849f2ea443cf7c402decd8299" - integrity sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg== - dependencies: - "@types/yargs-parser" "*" - "@typescript-eslint/experimental-utils@^2.5.0": - version "2.27.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.27.0.tgz#801a952c10b58e486c9a0b36cf21e2aab1e9e01a" - integrity sha512-vOsYzjwJlY6E0NJRXPTeCGqjv5OHgRU1kzxHKWJVPjDYGbPgLudBXjIlc+OD1hDBZ4l1DLbOc5VjofKahsu9Jw== + version "2.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.30.0.tgz#9845e868c01f3aed66472c561d4b6bac44809dd0" + integrity sha512-L3/tS9t+hAHksy8xuorhOzhdefN0ERPDWmR9CclsIGOUqGKy6tqc/P+SoXeJRye5gazkuPO0cK9MQRnolykzkA== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.27.0" + "@typescript-eslint/typescript-estree" "2.30.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/typescript-estree@2.27.0": - version "2.27.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.27.0.tgz#a288e54605412da8b81f1660b56c8b2e42966ce8" - integrity sha512-t2miCCJIb/FU8yArjAvxllxbTiyNqaXJag7UOpB5DVoM3+xnjeOngtqlJkLRnMtzaRcJhe3CIR9RmL40omubhg== +"@typescript-eslint/typescript-estree@2.30.0": + version "2.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.30.0.tgz#1b8e848b55144270255ffbfe4c63291f8f766615" + integrity sha512-nI5WOechrA0qAhnr+DzqwmqHsx7Ulr/+0H7bWCcClDhhWkSyZR5BmTvnBEyONwJCTWHfc5PAQExX24VD26IAVw== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" @@ -1261,17 +1120,18 @@ semver "^6.3.0" tsutils "^3.17.1" -"@zondax/zemu@^0.0.7": - version "0.0.7" - resolved "https://registry.yarnpkg.com/@zondax/zemu/-/zemu-0.0.7.tgz#ac58f8d1a65ba378989cedd97b9b88acf384c4e5" - integrity sha512-Mk86LHEYQ4mu4TEMFfDvGub4e10bPVqYZGet4/P5cB0rQA3Z6N/vMNTptc1WkVFiHV6yQ+WDejLKjFWZpqMZXA== +"@zondax/zemu@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@zondax/zemu/-/zemu-0.2.4.tgz#117f7d44ded3c764b918922769fd7f299d1c98ae" + integrity sha512-7E2gOJRLaMbm2CLVltu4DB+9F3CyfHSXezIr4oRZGAyUwonaNpGX6GdanUmzTRNyQ97pdk+CHu62F6gD4q4pvw== dependencies: "@babel/runtime" "^7.9.2" - "@ledgerhq/hw-transport" "^5.12.0" - "@ledgerhq/hw-transport-http" "^5.12.0" + "@ledgerhq/hw-transport" "^5.13.1" + "@ledgerhq/hw-transport-http" "^5.13.1" dockerode "^3.2.0" path "^0.12.7" - pngjs "^3.4.0" + pngjs "^5.0.0" + randomstring "^1.1.5" rfb2 "^0.2.2" sleep "^6.1.0" @@ -1280,7 +1140,12 @@ abab@^2.0.0: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg== -acorn-globals@^4.1.0, acorn-globals@^4.3.2: +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +acorn-globals@^4.1.0: version "4.3.4" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== @@ -1308,21 +1173,26 @@ acorn@^6.0.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== -acorn@^7.1.0, acorn@^7.1.1: +acorn@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg== ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5: - version "6.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" - integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== + version "6.12.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" + integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ansi-escapes@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + ansi-escapes@^4.2.1: version "4.3.1" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" @@ -1330,6 +1200,16 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.11.0" +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + ansi-regex@^4.0.0, ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" @@ -1347,7 +1227,7 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -ansi-styles@^4.0.0, ansi-styles@^4.1.0: +ansi-styles@^4.1.0: version "4.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== @@ -1363,13 +1243,18 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -anymatch@^3.0.3: - version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" + delegates "^1.0.0" + readable-stream "^2.0.6" argparse@^1.0.7: version "1.0.10" @@ -1407,6 +1292,11 @@ array-includes@^3.0.3: es-abstract "^1.17.0" is-string "^1.0.5" +array-uniq@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.2.tgz#5fcc373920775723cfd64d65c64bef53bf9eba6d" + integrity sha1-X8w3OSB3VyPP1k1lxkvvU7+eum0= + array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" @@ -1491,7 +1381,7 @@ babel-eslint@^10.1.0: eslint-visitor-keys "^1.0.0" resolve "^1.12.0" -babel-jest@^24.9.0: +babel-jest@24, babel-jest@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.9.0.tgz#3fc327cb8467b89d14d7bc70e315104a783ccd54" integrity sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw== @@ -1504,23 +1394,10 @@ babel-jest@^24.9.0: chalk "^2.4.2" slash "^2.0.0" -babel-jest@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-25.2.6.tgz#fe67ff4d0db3626ca8082da8881dd5e84e07ae75" - integrity sha512-MDJOAlwtIeIQiGshyX0d2PxTbV73xZMpNji40ivVTPQOm59OdRR9nYCkffqI7ugtsK4JR98HgNKbDbuVf4k5QQ== - dependencies: - "@jest/transform" "^25.2.6" - "@jest/types" "^25.2.6" - "@types/babel__core" "^7.1.0" - babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^25.2.6" - chalk "^3.0.0" - slash "^3.0.0" - -babel-plugin-dynamic-import-node@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" - integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ== +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== dependencies: object.assign "^4.1.0" @@ -1534,17 +1411,6 @@ babel-plugin-istanbul@^5.1.0: istanbul-lib-instrument "^3.3.0" test-exclude "^5.2.3" -babel-plugin-istanbul@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765" - integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^4.0.0" - test-exclude "^6.0.0" - babel-plugin-jest-hoist@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz#4f837091eb407e01447c8843cbec546d0002d756" @@ -1552,13 +1418,6 @@ babel-plugin-jest-hoist@^24.9.0: dependencies: "@types/babel__traverse" "^7.0.6" -babel-plugin-jest-hoist@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.2.6.tgz#2af07632b8ac7aad7d414c1e58425d5fc8e84909" - integrity sha512-qE2xjMathybYxjiGFJg0mLFrz0qNp83aNZycWDY/SuHiZNq+vQfRQtuINqyXyue1ELd8Rd+1OhFSLjms8msMbw== - dependencies: - "@types/babel__traverse" "^7.0.6" - babel-preset-jest@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz#192b521e2217fb1d1f67cf73f70c336650ad3cdc" @@ -1567,15 +1426,6 @@ babel-preset-jest@^24.9.0: "@babel/plugin-syntax-object-rest-spread" "^7.0.0" babel-plugin-jest-hoist "^24.9.0" -babel-preset-jest@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-25.2.6.tgz#5d3f7c99e2a8508d61775c9d68506d143b7f71b5" - integrity sha512-Xh2eEAwaLY9+SyMt/xmGZDnXTW/7pSaBPG0EMo7EuhvosFKVWYB6CqwYD31DaEQuoTL090oDZ0FEqygffGRaSQ== - dependencies: - "@babel/plugin-syntax-bigint" "^7.0.0" - "@babel/plugin-syntax-object-rest-spread" "^7.0.0" - babel-plugin-jest-hoist "^25.2.6" - balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -1607,9 +1457,9 @@ bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2: tweetnacl "^0.14.3" bech32@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.3.tgz#bd47a8986bbb3eec34a56a097a84b8d3e9a2dfcd" - integrity sha512-yuVFUvrNcoJi0sv5phmqc6P+Fl1HjRDRNOOkHY2X/3LBy2bIGNSFx4fZ95HMaXHupuS7cZR15AsvtmCIF4UEyg== + version "1.1.4" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" + integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== binary-extensions@^1.0.0: version "1.13.1" @@ -1632,6 +1482,11 @@ bl@^4.0.1: inherits "^2.0.4" readable-stream "^3.4.0" +bn.js@^4.4.0: + version "4.11.8" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" + integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1656,12 +1511,10 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" +brorand@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= browser-process-hrtime@^1.0.0: version "1.0.0" @@ -1675,13 +1528,13 @@ browser-resolve@^1.11.3: dependencies: resolve "1.1.7" -browserslist@^4.8.3, browserslist@^4.9.1: - version "4.11.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.11.1.tgz#92f855ee88d6e050e7e7311d987992014f1a1f1b" - integrity sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g== +browserslist@^4.11.1, browserslist@^4.8.5: + version "4.12.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.0.tgz#06c6d5715a1ede6c51fc39ff67fd647f740b656d" + integrity sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg== dependencies: - caniuse-lite "^1.0.30001038" - electron-to-chromium "^1.3.390" + caniuse-lite "^1.0.30001043" + electron-to-chromium "^1.3.413" node-releases "^1.1.53" pkg-up "^2.0.0" @@ -1698,9 +1551,9 @@ buffer-from@^1.0.0: integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== buffer@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.5.0.tgz#9c3caa3d623c33dd1c7ef584b89b88bf9c9bc1ce" - integrity sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww== + version "5.6.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" + integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" @@ -1730,10 +1583,10 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -caniuse-lite@^1.0.30001038: - version "1.0.30001039" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001039.tgz#b3814a1c38ffeb23567f8323500c09526a577bbe" - integrity sha512-SezbWCTT34eyFoWHgx8UWso7YtvtM7oosmFoXbCkdC6qJzRfBTeTgE9REtKtiuKXuMwWTZEvdnFNGAyVMorv8Q== +caniuse-lite@^1.0.30001043: + version "1.0.30001048" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001048.tgz#4bb4f1bc2eb304e5e1154da80b93dee3f1cf447e" + integrity sha512-g1iSHKVxornw0K8LG9LLdf+Fxnv7T1Z+mMsf0/YYLclQX4Cd522Ap0Lrw6NFqHgezit78dtyWxzlV2Xfc7vgRg== capture-exit@^2.0.0: version "2.0.0" @@ -1816,9 +1669,9 @@ cli-cursor@^3.1.0: restore-cursor "^3.1.0" cli-width@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" - integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" + integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== cliui@^5.0.0: version "5.0.0" @@ -1829,24 +1682,15 @@ cliui@^5.0.0: strip-ansi "^5.2.0" wrap-ansi "^5.1.0" -cliui@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" - integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^6.2.0" - co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= -collect-v8-coverage@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" - integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= collection-visit@^1.0.0: version "1.0.0" @@ -1922,12 +1766,17 @@ confusing-browser-globals@^1.0.9: resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz#72bc13b483c0276801681871d4898516f8f54fdd" integrity sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw== +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + contains-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= -convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: +convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== @@ -1940,17 +1789,17 @@ copy-descriptor@^0.1.0: integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= core-js-compat@^3.6.2: - version "3.6.4" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.4.tgz#938476569ebb6cda80d339bcf199fae4f16fff17" - integrity sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA== + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c" + integrity sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng== dependencies: - browserslist "^4.8.3" + browserslist "^4.8.5" semver "7.0.0" core-js@^3.2.1: - version "3.6.4" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647" - integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw== + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" + integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -1968,25 +1817,16 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0: - version "7.0.2" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.2.tgz#d0d7dcfa74e89115c7619f4f721a94e1fdb716d6" - integrity sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" +crypto-js@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.0.0.tgz#2904ab2677a9d042856a2ea2ef80de92e4a36dcc" + integrity sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg== -cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0", cssom@~0.3.6: +cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": version "0.3.8" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== -cssom@^0.4.1: - version "0.4.4" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" - integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== - cssstyle@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1" @@ -1994,13 +1834,6 @@ cssstyle@^1.0.0: dependencies: cssom "0.3.x" -cssstyle@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.2.0.tgz#e4c44debccd6b7911ed617a4395e5754bba59992" - integrity sha512-sEb3XFPx3jNnCAMtqrXPDeSgQr+jojtCeNf8cvMNMh1cG970+lljssvQDzPq6lmmJu2Vhqood/gtEomBiHOGnA== - dependencies: - cssom "~0.3.6" - dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -2008,7 +1841,7 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -data-urls@^1.0.0, data-urls@^1.1.0: +data-urls@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== @@ -2031,6 +1864,13 @@ debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: dependencies: ms "2.0.0" +debug@^3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" @@ -2048,16 +1888,16 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -deepmerge@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" - integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== - define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -2092,30 +1932,30 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + detect-newline@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - diff-sequences@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== -diff-sequences@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" - integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg== - docker-modem@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-2.1.2.tgz#7c346bfa7b3e4f6d5832eb80f13d603862639519" - integrity sha512-fwlfnsK9WV+m+qc/NZCiGt+oYAMjmCUeir0a/l3oHb0yc8FhRAucdwT4htKD3aLtV+1w2syQePH9pQFxsq1GFA== + version "2.1.3" + resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-2.1.3.tgz#15432225f63db02eb5de4bb9a621b7293e5f264d" + integrity sha512-cwaRptBmYZwu/FyhGcqBm2MzXA77W2/E6eVkpOZVDk6PkI9Bjj84xPrXiHMA+OWjzNy+DFjgKh8Q+1hMR7/OHg== dependencies: debug "^4.1.1" readable-stream "^3.5.0" @@ -2161,10 +2001,23 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -electron-to-chromium@^1.3.390: - version "1.3.398" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.398.tgz#4c01e29091bf39e578ac3f66c1f157d92fa5725d" - integrity sha512-BJjxuWLKFbM5axH3vES7HKMQgAknq9PZHBkMK/rEXUQG9i1Iw5R+6hGkm6GtsQSANjSUrh/a6m32nzCNDNo/+w== +electron-to-chromium@^1.3.413: + version "1.3.427" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.427.tgz#ea43d02908a8c71f47ebb46e09de5a3cf8236f04" + integrity sha512-/rG5G7Opcw68/Yrb4qYkz07h3bESVRJjUl4X/FrKLXzoUJleKm6D7K7rTTz8V5LUWnd+BbTOyxJX2XprRqHD8A== + +elliptic@^6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" + integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== + dependencies: + bn.js "^4.4.0" + brorand "^1.0.1" + hash.js "^1.0.0" + hmac-drbg "^1.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.0" emoji-regex@^7.0.1: version "7.0.3" @@ -2221,7 +2074,7 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@^1.11.1, escodegen@^1.9.1: +escodegen@^1.9.1: version "1.14.1" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.1.tgz#ba01d0c8278b5e95a9a45350142026659027a457" integrity sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ== @@ -2243,9 +2096,9 @@ eslint-config-airbnb-base@^14.1.0: object.entries "^1.1.1" eslint-config-prettier@^6.10.1: - version "6.10.1" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.10.1.tgz#129ef9ec575d5ddc0e269667bf09defcd898642a" - integrity sha512-svTy6zh1ecQojvpbJSgH3aei/Rt7C6i090l5f2WQ4aB05lYHeZIR1qL4wZyyILTbtmnbHP5Yn8MrsOJMGa8RkQ== + version "6.11.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz#f6d2238c1290d01c859a8b5c1f7d352a0b0da8b1" + integrity sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA== dependencies: get-stdin "^6.0.0" @@ -2290,22 +2143,13 @@ eslint-plugin-jest@^23.8.2: dependencies: "@typescript-eslint/experimental-utils" "^2.5.0" -eslint-plugin-prettier@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz#432e5a667666ab84ce72f945c72f77d996a5c9ba" - integrity sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA== +eslint-plugin-prettier@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.3.tgz#ae116a0fc0e598fdae48743a4430903de5b4e6ca" + integrity sha512-+HG5jmu/dN3ZV3T6eCD7a4BlAySdN7mLIbJYo0z1cFQuI+r2DiTJEFeF68ots93PsnrMxbzIZ2S/ieX+mkrBeQ== dependencies: prettier-linter-helpers "^1.0.0" -eslint-plugin-vue@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-6.2.2.tgz#27fecd9a3a24789b0f111ecdd540a9e56198e0fe" - integrity sha512-Nhc+oVAHm0uz/PkJAWscwIT4ijTrK5fqNqz9QB1D35SbbuMG1uB6Yr5AJpvPSWg+WOw7nYNswerYh0kOk64gqQ== - dependencies: - natural-compare "^1.4.0" - semver "^5.6.0" - vue-eslint-parser "^7.0.0" - eslint-scope@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" @@ -2391,11 +2235,11 @@ esprima@^4.0.0, esprima@^4.0.1: integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.2.0.tgz#a010a519c0288f2530b3404124bfb5f02e9797fe" - integrity sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q== + version "1.3.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" + integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== dependencies: - estraverse "^5.0.0" + estraverse "^5.1.0" esrecurse@^4.1.0: version "4.2.1" @@ -2409,10 +2253,10 @@ estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -estraverse@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.0.0.tgz#ac81750b482c11cca26e4b07e83ed8f75fbcdc22" - integrity sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A== +estraverse@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642" + integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== esutils@^2.0.2: version "2.0.3" @@ -2442,22 +2286,6 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -execa@^3.2.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-3.4.0.tgz#c08ed4550ef65d858fac269ffc8572446f37eb89" - integrity sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g== - dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - p-finally "^2.0.0" - signal-exit "^3.0.2" - strip-final-newline "^2.0.0" - exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -2488,18 +2316,6 @@ expect@^24.9.0: jest-message-util "^24.9.0" jest-regex-util "^24.9.0" -expect@^25.2.7: - version "25.2.7" - resolved "https://registry.yarnpkg.com/expect/-/expect-25.2.7.tgz#509b79f47502835f4071ff3ecc401f2eaecca709" - integrity sha512-yA+U2Ph0MkMsJ9N8q5hs9WgWI6oJYfecdXta6LkP/alY/jZZL1MHlJ2wbLh60Ucqf3G+51ytbqV3mlGfmxkpNw== - dependencies: - "@jest/types" "^25.2.6" - ansi-styles "^4.0.0" - jest-get-type "^25.2.6" - jest-matcher-utils "^25.2.7" - jest-message-util "^25.2.6" - jest-regex-util "^25.2.6" - extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -2609,13 +2425,6 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - find-cache-dir@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" @@ -2639,14 +2448,6 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" -find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - flat-cache@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" @@ -2699,6 +2500,13 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== +fs-minipass@^1.2.5: + version "1.2.7" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" + integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== + dependencies: + minipass "^2.6.0" + fs-readdir-recursive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" @@ -2717,11 +2525,6 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" -fsevents@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" - integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== - function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -2732,6 +2535,20 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + gensync@^1.0.0-beta.1: version "1.0.0-beta.1" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" @@ -2754,13 +2571,6 @@ get-stream@^4.0.0: dependencies: pump "^3.0.0" -get-stream@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" - integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== - dependencies: - pump "^3.0.0" - get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -2788,7 +2598,7 @@ glob-parent@^5.0.0: dependencies: is-glob "^4.0.1" -glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -2812,10 +2622,10 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" - integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== growly@^1.3.0: version "1.3.0" @@ -2850,6 +2660,11 @@ has-symbols@^1.0.0, has-symbols@^1.0.1: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -2889,12 +2704,30 @@ has@^1.0.3: function-bind "^1.1.1" hash-base@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" - integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hmac-drbg@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" homedir-polyfill@^1.0.1: version "1.0.3" @@ -2929,12 +2762,7 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -human-signals@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" - integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== - -iconv-lite@0.4.24, iconv-lite@^0.4.24: +iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -2946,6 +2774,13 @@ ieee754@^1.1.4: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== +ignore-walk@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" + integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== + dependencies: + minimatch "^3.0.4" + ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -2959,13 +2794,13 @@ import-fresh@^3.0.0: parent-module "^1.0.0" resolve-from "^4.0.0" -import-local@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" - integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== +import-local@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" + integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" + pkg-dir "^3.0.0" + resolve-cwd "^2.0.0" imurmurhash@^0.1.4: version "0.1.4" @@ -2990,6 +2825,11 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + inquirer@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.1.0.tgz#1298a01859883e17c7264b82870ae1034f92dd29" @@ -3016,11 +2856,6 @@ invariant@^2.2.2, invariant@^2.2.4: dependencies: loose-envify "^1.0.0" -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= - is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -3118,6 +2953,13 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" @@ -3154,11 +2996,6 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -3166,11 +3003,6 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= - is-regex@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" @@ -3183,11 +3015,6 @@ is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= -is-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== - is-string@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" @@ -3200,7 +3027,7 @@ is-symbol@^1.0.2: dependencies: has-symbols "^1.0.1" -is-typedarray@^1.0.0, is-typedarray@~1.0.0: +is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= @@ -3210,10 +3037,10 @@ is-windows@^1.0.2: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -is-wsl@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d" - integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog== +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" @@ -3242,17 +3069,12 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -istanbul-lib-coverage@^2.0.5: +istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49" integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== -istanbul-lib-coverage@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" - integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== - -istanbul-lib-instrument@^3.3.0: +istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630" integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA== @@ -3265,72 +3087,60 @@ istanbul-lib-instrument@^3.3.0: istanbul-lib-coverage "^2.0.5" semver "^6.0.0" -istanbul-lib-instrument@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz#61f13ac2c96cfefb076fe7131156cc05907874e6" - integrity sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg== - dependencies: - "@babel/core" "^7.7.5" - "@babel/parser" "^7.7.5" - "@babel/template" "^7.7.4" - "@babel/traverse" "^7.7.4" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.0.0" - semver "^6.3.0" - -istanbul-lib-report@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" - integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== +istanbul-lib-report@^2.0.4: + version "2.0.8" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33" + integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ== dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^3.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9" - integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg== + istanbul-lib-coverage "^2.0.5" + make-dir "^2.1.0" + supports-color "^6.1.0" + +istanbul-lib-source-maps@^3.0.1: + version "3.0.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8" + integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw== dependencies: debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" + istanbul-lib-coverage "^2.0.5" + make-dir "^2.1.0" + rimraf "^2.6.3" source-map "^0.6.1" -istanbul-reports@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b" - integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw== +istanbul-reports@^2.2.6: + version "2.2.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.7.tgz#5d939f6237d7b48393cc0959eab40cd4fd056931" + integrity sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg== dependencies: html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" -jest-changed-files@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-25.2.6.tgz#7d569cd6b265b1a84db3914db345d9c452f26b71" - integrity sha512-F7l2m5n55jFnJj4ItB9XbAlgO+6umgvz/mdK76BfTd2NGkvGf9x96hUXP/15a1K0k14QtVOoutwpRKl360msvg== +jest-changed-files@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039" + integrity sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg== dependencies: - "@jest/types" "^25.2.6" - execa "^3.2.0" - throat "^5.0.0" + "@jest/types" "^24.9.0" + execa "^1.0.0" + throat "^4.0.0" -jest-cli@^25.2.7: - version "25.2.7" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-25.2.7.tgz#515b61fee402c397ffa8d570532f7b039c3159f4" - integrity sha512-OOAZwY4Jkd3r5WhVM5L3JeLNFaylvHUczMLxQDVLrrVyb1Cy+DNJ6MVsb5TLh6iBklB42m5TOP+IbOgKGGOtMw== +jest-cli@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.9.0.tgz#ad2de62d07472d419c6abc301fc432b98b10d2af" + integrity sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg== dependencies: - "@jest/core" "^25.2.7" - "@jest/test-result" "^25.2.6" - "@jest/types" "^25.2.6" - chalk "^3.0.0" + "@jest/core" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + chalk "^2.0.1" exit "^0.1.2" - import-local "^3.0.2" + import-local "^2.0.0" is-ci "^2.0.0" - jest-config "^25.2.7" - jest-util "^25.2.6" - jest-validate "^25.2.6" + jest-config "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" prompts "^2.0.1" - realpath-native "^2.0.0" - yargs "^15.3.1" + realpath-native "^1.1.0" + yargs "^13.3.0" jest-config@^24.9.0: version "24.9.0" @@ -3355,30 +3165,6 @@ jest-config@^24.9.0: pretty-format "^24.9.0" realpath-native "^1.1.0" -jest-config@^25.2.7: - version "25.2.7" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-25.2.7.tgz#a14e5b96575987ce913dd9fc20ac8cd4b35a8c29" - integrity sha512-rIdPPXR6XUxi+7xO4CbmXXkE6YWprvlKc4kg1SrkCL2YV5m/8MkHstq9gBZJ19Qoa3iz/GP+0sTG/PcIwkFojg== - dependencies: - "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^25.2.7" - "@jest/types" "^25.2.6" - babel-jest "^25.2.6" - chalk "^3.0.0" - deepmerge "^4.2.2" - glob "^7.1.1" - jest-environment-jsdom "^25.2.6" - jest-environment-node "^25.2.6" - jest-get-type "^25.2.6" - jest-jasmine2 "^25.2.7" - jest-regex-util "^25.2.6" - jest-resolve "^25.2.6" - jest-util "^25.2.6" - jest-validate "^25.2.6" - micromatch "^4.0.2" - pretty-format "^25.2.6" - realpath-native "^2.0.0" - jest-diff@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da" @@ -3389,16 +3175,6 @@ jest-diff@^24.9.0: jest-get-type "^24.9.0" pretty-format "^24.9.0" -jest-diff@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.2.6.tgz#a6d70a9ab74507715ea1092ac513d1ab81c1b5e7" - integrity sha512-KuadXImtRghTFga+/adnNrv9s61HudRMR7gVSbP35UKZdn4IK2/0N0PpGZIqtmllK9aUyye54I3nu28OYSnqOg== - dependencies: - chalk "^3.0.0" - diff-sequences "^25.2.6" - jest-get-type "^25.2.6" - pretty-format "^25.2.6" - jest-docblock@^24.3.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2" @@ -3406,13 +3182,6 @@ jest-docblock@^24.3.0: dependencies: detect-newline "^2.1.0" -jest-docblock@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-25.2.6.tgz#4b09f1e7b7d6b3f39242ef3647ac7106770f722b" - integrity sha512-VAYrljEq0upq0oERfIaaNf28gC6p9gORndhHstCYF8NWGNQJnzoaU//S475IxfWMk4UjjVmS9rJKLe5Jjjbixw== - dependencies: - detect-newline "^3.0.0" - jest-each@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.9.0.tgz#eb2da602e2a610898dbc5f1f6df3ba86b55f8b05" @@ -3424,17 +3193,6 @@ jest-each@^24.9.0: jest-util "^24.9.0" pretty-format "^24.9.0" -jest-each@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-25.2.6.tgz#026f6dea2ccc443c35cea793265620aab1b278b6" - integrity sha512-OgQ01VINaRD6idWJOhCYwUc5EcgHBiFlJuw+ON2VgYr7HLtMFyCcuo+3mmBvuLUH4QudREZN7cDCZviknzsaJQ== - dependencies: - "@jest/types" "^25.2.6" - chalk "^3.0.0" - jest-get-type "^25.2.6" - jest-util "^25.2.6" - pretty-format "^25.2.6" - jest-environment-jsdom@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz#4b0806c7fc94f95edb369a69cc2778eec2b7375b" @@ -3447,18 +3205,6 @@ jest-environment-jsdom@^24.9.0: jest-util "^24.9.0" jsdom "^11.5.1" -jest-environment-jsdom@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-25.2.6.tgz#b7ae41c6035905b8e58d63a8f63cf8eaa00af279" - integrity sha512-/o7MZIhGmLGIEG5j7r5B5Az0umWLCHU+F5crwfbm0BzC4ybHTJZOQTFQWhohBg+kbTCNOuftMcqHlVkVduJCQQ== - dependencies: - "@jest/environment" "^25.2.6" - "@jest/fake-timers" "^25.2.6" - "@jest/types" "^25.2.6" - jest-mock "^25.2.6" - jest-util "^25.2.6" - jsdom "^15.2.1" - jest-environment-node@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.9.0.tgz#333d2d2796f9687f2aeebf0742b519f33c1cbfd3" @@ -3470,28 +3216,11 @@ jest-environment-node@^24.9.0: jest-mock "^24.9.0" jest-util "^24.9.0" -jest-environment-node@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-25.2.6.tgz#ad4398432867113f474d94fe37b071ed04b30f3d" - integrity sha512-D1Ihj14fxZiMHGeTtU/LunhzSI+UeBvlr/rcXMTNyRMUMSz2PEhuqGbB78brBY6Dk3FhJDk7Ta+8reVaGjLWhA== - dependencies: - "@jest/environment" "^25.2.6" - "@jest/fake-timers" "^25.2.6" - "@jest/types" "^25.2.6" - jest-mock "^25.2.6" - jest-util "^25.2.6" - semver "^6.3.0" - jest-get-type@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q== -jest-get-type@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" - integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig== - jest-haste-map@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d" @@ -3511,25 +3240,6 @@ jest-haste-map@^24.9.0: optionalDependencies: fsevents "^1.2.7" -jest-haste-map@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-25.2.6.tgz#4aa6bcfa15310afccdb9ca77af58a98add8cedb8" - integrity sha512-nom0+fnY8jwzelSDQnrqaKAcDZczYQvMEwcBjeL3PQ4MlcsqeB7dmrsAniUw/9eLkngT5DE6FhnenypilQFsgA== - dependencies: - "@jest/types" "^25.2.6" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.3" - jest-serializer "^25.2.6" - jest-util "^25.2.6" - jest-worker "^25.2.6" - micromatch "^4.0.2" - sane "^4.0.3" - walker "^1.0.7" - which "^2.0.2" - optionalDependencies: - fsevents "^2.1.2" - jest-jasmine2@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz#1f7b1bd3242c1774e62acabb3646d96afc3be6a0" @@ -3552,29 +3262,6 @@ jest-jasmine2@^24.9.0: pretty-format "^24.9.0" throat "^4.0.0" -jest-jasmine2@^25.2.7: - version "25.2.7" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-25.2.7.tgz#55ff87f8f462ef0e2f16fd19430b8be8bcebef0e" - integrity sha512-HeQxEbonp8fUvik9jF0lkU9ab1u5TQdIb7YSU9Fj7SxWtqHNDGyCpF6ZZ3r/5yuertxi+R95Ba9eA91GMQ38eA== - dependencies: - "@babel/traverse" "^7.1.0" - "@jest/environment" "^25.2.6" - "@jest/source-map" "^25.2.6" - "@jest/test-result" "^25.2.6" - "@jest/types" "^25.2.6" - chalk "^3.0.0" - co "^4.6.0" - expect "^25.2.7" - is-generator-fn "^2.0.0" - jest-each "^25.2.6" - jest-matcher-utils "^25.2.7" - jest-message-util "^25.2.6" - jest-runtime "^25.2.7" - jest-snapshot "^25.2.7" - jest-util "^25.2.6" - pretty-format "^25.2.6" - throat "^5.0.0" - jest-leak-detector@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz#b665dea7c77100c5c4f7dfcb153b65cf07dcf96a" @@ -3583,14 +3270,6 @@ jest-leak-detector@^24.9.0: jest-get-type "^24.9.0" pretty-format "^24.9.0" -jest-leak-detector@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-25.2.6.tgz#68fbaf651142292b03e30641f33e15af9b8c62b1" - integrity sha512-n+aJUM+j/x1kIaPVxzerMqhAUuqTU1PL5kup46rXh+l9SP8H6LqECT/qD1GrnylE1L463/0StSPkH4fUpkuEjA== - dependencies: - jest-get-type "^25.2.6" - pretty-format "^25.2.6" - jest-matcher-utils@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073" @@ -3601,16 +3280,6 @@ jest-matcher-utils@^24.9.0: jest-get-type "^24.9.0" pretty-format "^24.9.0" -jest-matcher-utils@^25.2.7: - version "25.2.7" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.2.7.tgz#53fad3c11fc42e92e374306df543026712c957a3" - integrity sha512-jNYmKQPRyPO3ny0KY1I4f0XW4XnpJ3Nx5ovT4ik0TYDOYzuXJW40axqOyS61l/voWbVT9y9nZ1THL1DlpaBVpA== - dependencies: - chalk "^3.0.0" - jest-diff "^25.2.6" - jest-get-type "^25.2.6" - pretty-format "^25.2.6" - jest-message-util@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.9.0.tgz#527f54a1e380f5e202a8d1149b0ec872f43119e3" @@ -3625,19 +3294,6 @@ jest-message-util@^24.9.0: slash "^2.0.0" stack-utils "^1.0.1" -jest-message-util@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.2.6.tgz#9d5523bebec8cd9cdef75f0f3069d6ec9a2252df" - integrity sha512-Hgg5HbOssSqOuj+xU1mi7m3Ti2nwSQJQf/kxEkrz2r2rp2ZLO1pMeKkz2WiDUWgSR+APstqz0uMFcE5yc0qdcg== - dependencies: - "@babel/code-frame" "^7.0.0" - "@jest/types" "^25.2.6" - "@types/stack-utils" "^1.0.1" - chalk "^3.0.0" - micromatch "^4.0.2" - slash "^3.0.0" - stack-utils "^1.0.1" - jest-mock@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.9.0.tgz#c22835541ee379b908673ad51087a2185c13f1c6" @@ -3645,13 +3301,6 @@ jest-mock@^24.9.0: dependencies: "@jest/types" "^24.9.0" -jest-mock@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.2.6.tgz#8df66eaa55a713d0f2a7dfb4f14507289d24dfa3" - integrity sha512-vc4nibavi2RGPdj/MyZy/azuDjZhpYZLvpfgq1fxkhbyTpKVdG7CgmRVKJ7zgLpY5kuMjTzDYA6QnRwhsCU+tA== - dependencies: - "@jest/types" "^25.2.6" - jest-pnp-resolver@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a" @@ -3662,19 +3311,14 @@ jest-regex-util@^24.3.0, jest-regex-util@^24.9.0: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.9.0.tgz#c13fb3380bde22bf6575432c493ea8fe37965636" integrity sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA== -jest-regex-util@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-25.2.6.tgz#d847d38ba15d2118d3b06390056028d0f2fd3964" - integrity sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw== - -jest-resolve-dependencies@^25.2.7: - version "25.2.7" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-25.2.7.tgz#9ca4c62d67cce031a27fa5d5705b4b5b5c029d23" - integrity sha512-IrnMzCAh11Xd2gAOJL+ThEW6QO8DyqNdvNkQcaCticDrOAr9wtKT7yT6QBFFjqKFgjjvaVKDs59WdgUhgYnHnQ== +jest-resolve-dependencies@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz#ad055198959c4cfba8a4f066c673a3f0786507ab" + integrity sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g== dependencies: - "@jest/types" "^25.2.6" - jest-regex-util "^25.2.6" - jest-snapshot "^25.2.7" + "@jest/types" "^24.9.0" + jest-regex-util "^24.3.0" + jest-snapshot "^24.9.0" jest-resolve@^24.9.0: version "24.9.0" @@ -3687,18 +3331,6 @@ jest-resolve@^24.9.0: jest-pnp-resolver "^1.2.1" realpath-native "^1.1.0" -jest-resolve@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-25.2.6.tgz#84694ead5da13c2890ac04d4a78699ba937f3896" - integrity sha512-7O61GVdcAXkLz/vNGKdF+00A80/fKEAA47AEXVNcZwj75vEjPfZbXDaWFmAQCyXj4oo9y9dC9D+CLA11t8ieGw== - dependencies: - "@jest/types" "^25.2.6" - browser-resolve "^1.11.3" - chalk "^3.0.0" - jest-pnp-resolver "^1.2.1" - realpath-native "^2.0.0" - resolve "^1.15.1" - jest-runner@^24.8.0, jest-runner@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.9.0.tgz#574fafdbd54455c2b34b4bdf4365a23857fcdf42" @@ -3724,31 +3356,6 @@ jest-runner@^24.8.0, jest-runner@^24.9.0: source-map-support "^0.5.6" throat "^4.0.0" -jest-runner@^25.2.7: - version "25.2.7" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-25.2.7.tgz#3676c01dc0104caa8a0ebb8507df382c88f2a1e2" - integrity sha512-RFEr71nMrtNwcpoHzie5+fe1w3JQCGMyT2xzNwKe3f88+bK+frM2o1v24gEcPxQ2QqB3COMCe2+1EkElP+qqqQ== - dependencies: - "@jest/console" "^25.2.6" - "@jest/environment" "^25.2.6" - "@jest/test-result" "^25.2.6" - "@jest/types" "^25.2.6" - chalk "^3.0.0" - exit "^0.1.2" - graceful-fs "^4.2.3" - jest-config "^25.2.7" - jest-docblock "^25.2.6" - jest-haste-map "^25.2.6" - jest-jasmine2 "^25.2.7" - jest-leak-detector "^25.2.6" - jest-message-util "^25.2.6" - jest-resolve "^25.2.6" - jest-runtime "^25.2.7" - jest-util "^25.2.6" - jest-worker "^25.2.6" - source-map-support "^0.5.6" - throat "^5.0.0" - jest-runtime@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.9.0.tgz#9f14583af6a4f7314a6a9d9f0226e1a781c8e4ac" @@ -3778,37 +3385,6 @@ jest-runtime@^24.9.0: strip-bom "^3.0.0" yargs "^13.3.0" -jest-runtime@^25.2.7: - version "25.2.7" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-25.2.7.tgz#cb10e695d036671a83aec3a3803163c354043ac9" - integrity sha512-Gw3X8KxTTFylu2T/iDSNKRUQXQiPIYUY0b66GwVYa7W8wySkUljKhibQHSq0VhmCAN7vRBEQjlVQ+NFGNmQeBw== - dependencies: - "@jest/console" "^25.2.6" - "@jest/environment" "^25.2.6" - "@jest/source-map" "^25.2.6" - "@jest/test-result" "^25.2.6" - "@jest/transform" "^25.2.6" - "@jest/types" "^25.2.6" - "@types/yargs" "^15.0.0" - chalk "^3.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.3" - jest-config "^25.2.7" - jest-haste-map "^25.2.6" - jest-message-util "^25.2.6" - jest-mock "^25.2.6" - jest-regex-util "^25.2.6" - jest-resolve "^25.2.6" - jest-snapshot "^25.2.7" - jest-util "^25.2.6" - jest-validate "^25.2.6" - realpath-native "^2.0.0" - slash "^3.0.0" - strip-bom "^4.0.0" - yargs "^15.3.1" - "jest-serial-runner@^1.1.0 ": version "1.1.0" resolved "https://registry.yarnpkg.com/jest-serial-runner/-/jest-serial-runner-1.1.0.tgz#867fcd3ce0284afdf742a7306a9cbfd998631aaf" @@ -3821,11 +3397,6 @@ jest-serializer@^24.9.0: resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.9.0.tgz#e6d7d7ef96d31e8b9079a714754c5d5c58288e73" integrity sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ== -jest-serializer@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-25.2.6.tgz#3bb4cc14fe0d8358489dbbefbb8a4e708ce039b7" - integrity sha512-RMVCfZsezQS2Ww4kB5HJTMaMJ0asmC0BHlnobQC6yEtxiFKIxohFA4QSXSabKwSggaNkqxn6Z2VwdFCjhUWuiQ== - jest-snapshot@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.9.0.tgz#ec8e9ca4f2ec0c5c87ae8f925cf97497b0e951ba" @@ -3845,26 +3416,6 @@ jest-snapshot@^24.9.0: pretty-format "^24.9.0" semver "^6.2.0" -jest-snapshot@^25.2.7: - version "25.2.7" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-25.2.7.tgz#7eeafeef4dcbda1c47c8503d2bf5212b6430aac6" - integrity sha512-Rm8k7xpGM4tzmYhB6IeRjsOMkXaU8/FOz5XlU6oYwhy53mq6txVNqIKqN1VSiexzpC80oWVxVDfUDt71M6XPOA== - dependencies: - "@babel/types" "^7.0.0" - "@jest/types" "^25.2.6" - "@types/prettier" "^1.19.0" - chalk "^3.0.0" - expect "^25.2.7" - jest-diff "^25.2.6" - jest-get-type "^25.2.6" - jest-matcher-utils "^25.2.7" - jest-message-util "^25.2.6" - jest-resolve "^25.2.6" - make-dir "^3.0.0" - natural-compare "^1.4.0" - pretty-format "^25.2.6" - semver "^6.3.0" - jest-util@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.9.0.tgz#7396814e48536d2e85a37de3e4c431d7cb140162" @@ -3883,16 +3434,6 @@ jest-util@^24.9.0: slash "^2.0.0" source-map "^0.6.0" -jest-util@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.2.6.tgz#3c1c95cdfd653126728b0ed861a86610e30d569c" - integrity sha512-gpXy0H5ymuQ0x2qgl1zzHg7LYHZYUmDEq6F7lhHA8M0eIwDB2WteOcCnQsohl9c/vBKZ3JF2r4EseipCZz3s4Q== - dependencies: - "@jest/types" "^25.2.6" - chalk "^3.0.0" - is-ci "^2.0.0" - make-dir "^3.0.0" - jest-validate@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.9.0.tgz#0775c55360d173cd854e40180756d4ff52def8ab" @@ -3905,29 +3446,18 @@ jest-validate@^24.9.0: leven "^3.1.0" pretty-format "^24.9.0" -jest-validate@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-25.2.6.tgz#ab3631fb97e242c42b09ca53127abe0b12e9125e" - integrity sha512-a4GN7hYbqQ3Rt9iHsNLFqQz7HDV7KiRPCwPgo5nqtTIWNZw7gnT8KchG+Riwh+UTSn8REjFCodGp50KX/fRNgQ== - dependencies: - "@jest/types" "^25.2.6" - camelcase "^5.3.1" - chalk "^3.0.0" - jest-get-type "^25.2.6" - leven "^3.1.0" - pretty-format "^25.2.6" - -jest-watcher@^25.2.7: - version "25.2.7" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-25.2.7.tgz#01db4332d34d14c03c9ef22255125a3b07f997bc" - integrity sha512-RdHuW+f49tahWtluTnUdZ2iPliebleROI2L/J5phYrUS6DPC9RB3SuUtqYyYhGZJsbvRSuLMIlY/cICJ+PIecw== +jest-watcher@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.9.0.tgz#4b56e5d1ceff005f5b88e528dc9afc8dd4ed2b3b" + integrity sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw== dependencies: - "@jest/test-result" "^25.2.6" - "@jest/types" "^25.2.6" - ansi-escapes "^4.2.1" - chalk "^3.0.0" - jest-util "^25.2.6" - string-length "^3.1.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/yargs" "^13.0.0" + ansi-escapes "^3.0.0" + chalk "^2.0.1" + jest-util "^24.9.0" + string-length "^2.0.0" jest-worker@^24.6.0, jest-worker@^24.9.0: version "24.9.0" @@ -3937,22 +3467,13 @@ jest-worker@^24.6.0, jest-worker@^24.9.0: merge-stream "^2.0.0" supports-color "^6.1.0" -jest-worker@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.2.6.tgz#d1292625326794ce187c38f51109faced3846c58" - integrity sha512-FJn9XDUSxcOR4cwDzRfL1z56rUofNTFs539FGASpd50RHdb6EVkhxQqktodW2mI49l+W3H+tFJDotCHUQF6dmA== - dependencies: - merge-stream "^2.0.0" - supports-color "^7.0.0" - -"jest@^25.2.7 ": - version "25.2.7" - resolved "https://registry.yarnpkg.com/jest/-/jest-25.2.7.tgz#3929a5f35cdd496f7756876a206b99a94e1e09ae" - integrity sha512-XV1n/CE2McCikl4tfpCY950RytHYvxdo/wvtgmn/qwA8z1s16fuvgFL/KoPrrmkqJTaPMUlLVE58pwiaTX5TdA== +jest@24: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-24.9.0.tgz#987d290c05a08b52c56188c1002e368edb007171" + integrity sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw== dependencies: - "@jest/core" "^25.2.7" - import-local "^3.0.2" - jest-cli "^25.2.7" + import-local "^2.0.0" + jest-cli "^24.9.0" "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" @@ -4004,38 +3525,6 @@ jsdom@^11.5.1: ws "^5.2.0" xml-name-validator "^3.0.0" -jsdom@^15.2.1: - version "15.2.1" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-15.2.1.tgz#d2feb1aef7183f86be521b8c6833ff5296d07ec5" - integrity sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g== - dependencies: - abab "^2.0.0" - acorn "^7.1.0" - acorn-globals "^4.3.2" - array-equal "^1.0.0" - cssom "^0.4.1" - cssstyle "^2.0.0" - data-urls "^1.1.0" - domexception "^1.0.1" - escodegen "^1.11.1" - html-encoding-sniffer "^1.0.2" - nwsapi "^2.2.0" - parse5 "5.1.0" - pn "^1.1.0" - request "^2.88.0" - request-promise-native "^1.0.7" - saxes "^3.1.9" - symbol-tree "^3.2.2" - tough-cookie "^3.0.1" - w3c-hr-time "^1.0.1" - w3c-xmlserializer "^1.1.2" - webidl-conversions "^4.0.2" - whatwg-encoding "^1.0.5" - whatwg-mimetype "^2.3.0" - whatwg-url "^7.0.0" - ws "^7.0.0" - xml-name-validator "^3.0.0" - jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -4117,7 +3606,7 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -ledger-cosmos-js@^2.1.7: +ledger-cosmos-js@^2: version "2.1.7" resolved "https://registry.yarnpkg.com/ledger-cosmos-js/-/ledger-cosmos-js-2.1.7.tgz#362d1139ac2504ccb56029abda053b2c5b290e8d" integrity sha512-RyaP+6lRhllQTgk5X14PiZtsUE8bYP7mOiFkOMAzPQvUDdy8IZr17mDaH9whqHtE0wZd95YPN89VAhbfV+Re8w== @@ -4188,13 +3677,6 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" @@ -4205,13 +3687,6 @@ lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== -lolex@^5.0.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367" - integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A== - dependencies: - "@sinonjs/commons" "^1.7.0" - loose-envify@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -4227,13 +3702,6 @@ make-dir@^2.0.0, make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" -make-dir@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.2.tgz#04a1acbf22221e1d6ef43559f43e05a90dbb4392" - integrity sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w== - dependencies: - semver "^6.0.0" - makeerror@1.0.x: version "1.0.11" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" @@ -4277,31 +3745,33 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== - dependencies: - braces "^3.0.1" - picomatch "^2.0.5" - -mime-db@1.43.0: - version "1.43.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" - integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== +mime-db@1.44.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.26" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" - integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== dependencies: - mime-db "1.43.0" + mime-db "1.44.0" mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= + minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -4314,6 +3784,21 @@ minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" + integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.2.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" + integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== + dependencies: + minipass "^2.9.0" + mixin-deep@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" @@ -4327,7 +3812,7 @@ mkdirp-classic@^0.5.2: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz#54c441ce4c96cd7790e10b41a87aa51068ecab2b" integrity sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g== -mkdirp@^0.5.1: +mkdirp@^0.5.0, mkdirp@^0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -4350,9 +3835,9 @@ mute-stream@0.0.8: integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== nan@^2.12.1, nan@^2.13.2: - version "2.14.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" - integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + version "2.14.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" + integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== nanomatch@^1.2.9: version "1.2.13" @@ -4376,11 +3861,25 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +needle@^2.2.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.1.tgz#14af48732463d7475696f937626b1b993247a56a" + integrity sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g== + dependencies: + debug "^3.2.6" + iconv-lite "^0.4.4" + sax "^1.2.4" + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +node-addon-api@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.0.tgz#f9afb8d777a91525244b01775ea0ddbe1125483b" + integrity sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA== + node-environment-flags@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" @@ -4389,6 +3888,11 @@ node-environment-flags@^1.0.5: object.getownpropertydescriptors "^2.0.3" semver "^5.7.0" +node-gyp-build@^4.2.0: + version "4.2.2" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.2.tgz#3f44b65adaafd42fb6c3d81afd630e45c847eb66" + integrity sha512-Lqh7mrByWCM8Cf9UPqpeoVBBo5Ugx+RKu885GAzmLBVYjeywScxHXPGLa4JfYNZmcNGwzR0Glu5/9GaQZMFqyA== + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -4399,22 +3903,46 @@ node-modules-regexp@^1.0.0: resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= -node-notifier@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-6.0.0.tgz#cea319e06baa16deec8ce5cd7f133c4a46b68e12" - integrity sha512-SVfQ/wMw+DesunOm5cKqr6yDcvUTDl/yc97ybGHMrteNEY6oekXpNpS3lZwgLlwz0FLgHoiW28ZpmBHUDg37cw== +node-notifier@^5.4.2: + version "5.4.3" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.3.tgz#cb72daf94c93904098e28b9c590fd866e464bd50" + integrity sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q== dependencies: growly "^1.3.0" - is-wsl "^2.1.1" - semver "^6.3.0" + is-wsl "^1.1.0" + semver "^5.5.0" shellwords "^0.1.1" - which "^1.3.1" + which "^1.3.0" + +node-pre-gyp@*: + version "0.14.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83" + integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4.4.2" node-releases@^1.1.53: version "1.1.53" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4" integrity sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ== +nopt@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" + integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== + dependencies: + abbrev "1" + osenv "^0.1.4" + normalize-package-data@^2.3.2: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -4437,6 +3965,27 @@ normalize-path@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +npm-bundled@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" + integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== + dependencies: + npm-normalize-package-bin "^1.0.1" + +npm-normalize-package-bin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" + integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== + +npm-packlist@^1.1.6: + version "1.4.8" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" + integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + npm-normalize-package-bin "^1.0.1" + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -4444,14 +3993,22 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npm-run-path@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== dependencies: - path-key "^3.0.0" + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" -nwsapi@^2.0.7, nwsapi@^2.2.0: +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +nwsapi@^2.0.7: version "2.2.0" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== @@ -4461,6 +4018,11 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + object-copy@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" @@ -4558,26 +4120,36 @@ optionator@^0.8.1, optionator@^0.8.3: type-check "~0.3.2" word-wrap "~1.2.3" -os-tmpdir@~1.0.2: +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + +os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -p-each-series@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48" - integrity sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ== +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +p-each-series@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71" + integrity sha1-kw89Et0fUOdDRFeiLNbwSsatf3E= + dependencies: + p-reduce "^1.0.0" p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= -p-finally@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" - integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== - p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -4585,7 +4157,7 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" -p-limit@^2.0.0, p-limit@^2.2.0: +p-limit@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== @@ -4606,12 +4178,10 @@ p-locate@^3.0.0: dependencies: p-limit "^2.0.0" -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" +p-reduce@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" + integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo= p-try@^1.0.0: version "1.0.0" @@ -4655,11 +4225,6 @@ parse5@4.0.0: resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== -parse5@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" - integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== - pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" @@ -4675,11 +4240,6 @@ path-exists@^3.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -4690,11 +4250,6 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -4727,11 +4282,6 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.4, picomatch@^2.0.5: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== - pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -4768,13 +4318,6 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - pkg-up@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" @@ -4787,10 +4330,10 @@ pn@^1.1.0: resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== -pngjs@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" - integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== +pngjs@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" + integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== posix-character-classes@^0.1.0: version "0.1.1" @@ -4819,16 +4362,6 @@ pretty-format@^24.9.0: ansi-styles "^3.2.0" react-is "^16.8.4" -pretty-format@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.2.6.tgz#542a1c418d019bbf1cca2e3620443bc1323cb8d7" - integrity sha512-DEiWxLBaCHneffrIT4B+TpMvkV9RNvvJrd3lY9ew1CEQobDzEXmYT1mg0hJhljZty7kCc10z13ohOFAE8jrUDg== - dependencies: - "@jest/types" "^25.2.6" - ansi-regex "^5.0.0" - ansi-styles "^4.0.0" - react-is "^16.12.0" - private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -4880,7 +4413,24 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -react-is@^16.12.0, react-is@^16.8.4: +randomstring@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/randomstring/-/randomstring-1.1.5.tgz#6df0628f75cbd5932930d9fe3ab4e956a18518c3" + integrity sha1-bfBij3XL1ZMpMNn+OrTpVqGFGMM= + dependencies: + array-uniq "1.0.2" + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-is@^16.8.4: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -4919,7 +4469,7 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" -readable-stream@^2.0.2: +readable-stream@^2.0.2, readable-stream@^2.0.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -4932,7 +4482,7 @@ readable-stream@^2.0.2: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0: +readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -4957,11 +4507,6 @@ realpath-native@^1.1.0: dependencies: util.promisify "^1.0.0" -realpath-native@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-2.0.0.tgz#7377ac429b6e1fd599dc38d08ed942d0d7beb866" - integrity sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q== - regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -5046,7 +4591,7 @@ request-promise-core@1.1.3: dependencies: lodash "^4.17.15" -request-promise-native@^1.0.5, request-promise-native@^1.0.7: +request-promise-native@^1.0.5: version "1.0.8" resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== @@ -5055,7 +4600,7 @@ request-promise-native@^1.0.5, request-promise-native@^1.0.7: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.87.0, request@^2.88.0: +request@^2.87.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -5091,23 +4636,23 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== +resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= dependencies: - resolve-from "^5.0.0" + resolve-from "^3.0.0" + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + integrity sha1-six699nWiBvItuZTM17rywoYh0g= resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -5118,10 +4663,10 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.8.1: - version "1.15.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" - integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== +resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.3.2, resolve@^1.8.1: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== dependencies: path-parse "^1.0.6" @@ -5150,10 +4695,10 @@ rimraf@2.6.3: dependencies: glob "^7.1.3" -rimraf@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== +rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: glob "^7.1.3" @@ -5171,20 +4716,18 @@ rsvp@^4.8.4: integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== run-async@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8" - integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg== - dependencies: - is-promise "^2.1.0" + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== -rxjs@^6.5.3, rxjs@^6.5.4: +rxjs@^6.5.3, rxjs@^6.5.5: version "6.5.5" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== dependencies: tslib "^1.9.0" -safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== @@ -5226,14 +4769,16 @@ sax@^1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -saxes@^3.1.9: - version "3.1.11" - resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b" - integrity sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g== +secp256k1@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.1.tgz#b9570ca26ace9e74c3171512bba253da9c0b6d60" + integrity sha512-iGRjbGAKfXMqhtdkkuNxsgJQfJO8Oo78Rm7DAvsG3XKngq+nJIOGqrCSXcQqIVsmCj0wFanE5uTKFxV3T9j2wg== dependencies: - xmlchars "^2.1.1" + elliptic "^6.5.2" + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" -"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0: +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -5248,7 +4793,7 @@ semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -set-blocking@^2.0.0: +set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= @@ -5270,23 +4815,11 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" @@ -5307,11 +4840,6 @@ slash@^2.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - sleep@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/sleep/-/sleep-6.1.0.tgz#5507b520556a82ffb983d39123c5459470fa2a9e" @@ -5370,9 +4898,9 @@ source-map-resolve@^0.5.0: urix "^0.1.0" source-map-support@^0.5.16, source-map-support@^0.5.6: - version "0.5.16" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" - integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -5392,11 +4920,6 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - spdx-correct@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" @@ -5406,9 +4929,9 @@ spdx-correct@^3.0.0: spdx-license-ids "^3.0.0" spdx-exceptions@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" - integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== spdx-expression-parse@^3.0.0: version "3.0.0" @@ -5494,13 +5017,30 @@ streamsearch@~0.1.2: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= -string-length@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-3.1.0.tgz#107ef8c23456e187a8abd4a61162ff4ac6e25837" - integrity sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA== +string-length@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" + integrity sha1-1A27aGo6zpYMHP/KVivyxF+DY+0= dependencies: astral-regex "^1.0.0" - strip-ansi "^5.2.0" + strip-ansi "^4.0.0" + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" string-width@^3.0.0, string-width@^3.1.0: version "3.1.0" @@ -5511,7 +5051,7 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.1.0, string-width@^4.2.0: +string-width@^4.1.0: version "4.2.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== @@ -5521,9 +5061,9 @@ string-width@^4.1.0, string-width@^4.2.0: strip-ansi "^6.0.0" string.prototype.trimend@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz#ee497fd29768646d84be2c9b819e292439614373" - integrity sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA== + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== dependencies: define-properties "^1.1.3" es-abstract "^1.17.5" @@ -5547,9 +5087,9 @@ string.prototype.trimright@^2.1.1: string.prototype.trimend "^1.0.0" string.prototype.trimstart@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz#afe596a7ce9de905496919406c9734845f01a2f2" - integrity sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w== + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== dependencies: define-properties "^1.1.3" es-abstract "^1.17.5" @@ -5568,6 +5108,20 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" @@ -5587,26 +5141,21 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - strip-json-comments@^3.0.1: version "3.1.0" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -5621,21 +5170,13 @@ supports-color@^6.1.0: dependencies: has-flag "^3.0.0" -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== dependencies: has-flag "^4.0.0" -supports-hyperlinks@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz#f663df252af5f37c5d49bbd7eeefa9e0b9e59e47" - integrity sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA== - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" - symbol-tree@^3.2.2: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -5672,13 +5213,18 @@ tar-stream@^2.0.0: inherits "^2.0.3" readable-stream "^3.1.1" -terminal-link@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" - integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== +tar@^4.4.2: + version "4.4.13" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" + integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== dependencies: - ansi-escapes "^4.2.1" - supports-hyperlinks "^2.0.0" + chownr "^1.1.1" + fs-minipass "^1.2.5" + minipass "^2.8.6" + minizlib "^1.2.1" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.3" test-exclude@^5.2.3: version "5.2.3" @@ -5690,15 +5236,6 @@ test-exclude@^5.2.3: read-pkg-up "^4.0.0" require-main-filename "^2.0.0" -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -5709,11 +5246,6 @@ throat@^4.0.0: resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo= -throat@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" - integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== - through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -5751,13 +5283,6 @@ to-regex-range@^2.1.0: is-number "^3.0.0" repeat-string "^1.6.1" -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - to-regex@^3.0.1, to-regex@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" @@ -5776,15 +5301,6 @@ tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -tough-cookie@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" - integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== - dependencies: - ip-regex "^2.1.0" - psl "^1.1.28" - punycode "^2.1.1" - tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -5823,11 +5339,6 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - type-fest@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" @@ -5838,13 +5349,6 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -5945,15 +5449,6 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== -v8-to-istanbul@^4.0.1: - version "4.1.3" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-4.1.3.tgz#22fe35709a64955f49a08a7c7c959f6520ad6f20" - integrity sha512-sAjOC+Kki6aJVbUOXJbcR0MnbfjvBzwKZazEJymA2IX49uoOdEdk+4fBq5cXgYgiyKtAyrrJNtBZdOeDIF+Fng== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" - source-map "^0.7.3" - v8flags@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.3.tgz#fc9dc23521ca20c5433f81cc4eb9b3033bb105d8" @@ -5978,18 +5473,6 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -vue-eslint-parser@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-7.0.0.tgz#a4ed2669f87179dedd06afdd8736acbb3a3864d6" - integrity sha512-yR0dLxsTT7JfD2YQo9BhnQ6bUTLsZouuzt9SKRP7XNaZJV459gvlsJo4vT2nhZ/2dH9j3c53bIx9dnqU2prM9g== - dependencies: - debug "^4.1.1" - eslint-scope "^5.0.0" - eslint-visitor-keys "^1.1.0" - espree "^6.1.2" - esquery "^1.0.1" - lodash "^4.17.15" - w3c-hr-time@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" @@ -5997,15 +5480,6 @@ w3c-hr-time@^1.0.1: dependencies: browser-process-hrtime "^1.0.0" -w3c-xmlserializer@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794" - integrity sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg== - dependencies: - domexception "^1.0.1" - webidl-conversions "^4.0.2" - xml-name-validator "^3.0.0" - walker@^1.0.7, walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" @@ -6018,14 +5492,14 @@ webidl-conversions@^4.0.2: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== -whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: +whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== dependencies: iconv-lite "0.4.24" -whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: +whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== @@ -6053,19 +5527,19 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@^1.2.9, which@^1.3.1: +which@^1.2.9, which@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" -which@^2.0.1, which@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== dependencies: - isexe "^2.0.0" + string-width "^1.0.2 || 2" word-wrap@~1.2.3: version "1.2.3" @@ -6081,15 +5555,6 @@ wrap-ansi@^5.1.0: string-width "^3.0.0" strip-ansi "^5.0.0" -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -6104,16 +5569,6 @@ write-file-atomic@2.4.1: imurmurhash "^0.1.4" signal-exit "^3.0.2" -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - write@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" @@ -6135,26 +5590,21 @@ ws@^5.2.0: dependencies: async-limiter "~1.0.0" -ws@^7.0.0: - version "7.2.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46" - integrity sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ== - xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== -xmlchars@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" - integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== - y18n@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== +yallist@^3.0.0, yallist@^3.0.3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + yargs-parser@^13.1.2: version "13.1.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" @@ -6163,14 +5613,6 @@ yargs-parser@^13.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^18.1.1: - version "18.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.2.tgz#2f482bea2136dbde0861683abea7756d30b504f1" - integrity sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - yargs@^13.3.0: version "13.3.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" @@ -6186,20 +5628,3 @@ yargs@^13.3.0: which-module "^2.0.0" y18n "^4.0.0" yargs-parser "^13.1.2" - -yargs@^15.3.1: - version "15.3.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b" - integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA== - dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.1" From cb3bb5385fe5dd393f88283551614fd1128fea4d Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sat, 2 May 2020 18:14:45 +0200 Subject: [PATCH 61/78] Update readme --- README.md | 215 +++++++++++++++++++++++++---------- app/Makefile | 4 +- docs/img/clion_debugging.png | Bin 0 -> 139227 bytes 3 files changed, 160 insertions(+), 59 deletions(-) create mode 100644 docs/img/clion_debugging.png diff --git a/README.md b/README.md index a40e55b9..b1de3492 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,7 @@ This repository contains: - Ledger Nano S/X Cosmos app - Specs / Documentation - C++ unit tests - -## Installing - -### Cosmos app +- Zemu tests The Cosmos app is already available in [Ledger Live](https://www.ledger.com/pages/ledger-live). Our preferred and recommended hardware wallet! @@ -27,68 +24,190 @@ The Cosmos app is already available in [Ledger Live](https://www.ledger.com/page ![](docs/img/cosmos_app3.png) -# Building +## ATTENTION -**Please only use a TEST DEVICE!** +Please: -**We strongly recommend using Linux as your development environment.** +- **Do not use in production** +- **Do not use a Ledger device with funds for development purposes.** +- **Have a separate and marked device that is used ONLY for development and testing** -## Get source -Apart from cloning, be sure you get all the submodules: -``` -git submodule update --init --recursive -``` +Tip: -## Dependencies +- In releases, you will find a precompiled test app. If you are just curious, you can run `zxtool.sh` and avoid building. -#### Ledger Nano S +## Preconditions -- This project requires Ledger firmware 1.6 +- Be sure you checkout submodules too: -- The current repository keeps track of Ledger's SDK but it is possible to override it by changing the git submodule. + ``` + git submodule update --init --recursive + ``` -#### Docker CE +- Install Docker CE + - Instructions can be found here: https://docs.docker.com/install/ -- Please install docker CE. The instructions can be found here: https://docs.docker.com/install/ - -#### Ubuntu Dependencies -- Install the following packages: +- We only officially support Ubuntu. Install the following packages: ``` sudo apt update && apt-get -y install build-essential git wget cmake \ libssl-dev libgmp-dev autoconf libtool ``` -#### Other dependencies +- Install `node > v13.0`. We typically recommend using `n` -- You need Python 3. In most cases, `make deps` will be able to install all additional dependencies: +- You will need python 3 and then run + - `make deps` - ```bash - make deps - ``` +- This project requires Ledger firmware 1.6 + - The current repository keeps track of Ledger's SDK but it is possible to override it by changing the git submodule. + +*Warning*: Some IDEs may not use the same python interpreter or virtual enviroment as the one you used when running `pip`. +If you see conan is not found, check that you installed the package in the same interpreter as the one that launches `cmake`. + +## How to build ? + +> We like clion or vscode but let's have some reproducible command line steps +> + +- Building the app itself + + If you installed the what is described above, just run: + ```bash + make + ``` + +## Running tests + +- Running rust tests (x64) -- You also need to install [Conan](https://conan.io/) + If you installed the what is described above, just run: + ```bash + make rust_test + ``` + +- Running C/C++ tests (x64) + + If you installed the what is described above, just run: + ```bash + make cpp_test + ``` + +- Running device emulation+integration tests!! ```bash - pip install conan - ``` + Use Zemu! Explained below! + ``` -*Warning*: Some IDEs may not use the same python interpreter or virtual enviroment as the one you used when running `pip`. -If you see conan is not found, check that you installed the package in the same interpreter as the one that launches `cmake`. +## How to test with Zemu? + +> What is Zemu?? Great you asked!! +> As part of this project, we are making public a beta version of our internal testing+emulation framework for Ledger apps. +> +> Npm Package here: https://www.npmjs.com/package/@zondax/zemu +> +> Repo here: https://github.com/Zondax/zemu + +Let's go! First install everything: +> At this moment, if you change the app you will need to run `make` before running the test again. + +```bash +make zemu_install +``` + +Then you can run JS tests: + +```bash +make zemu_test +``` + +To run a single specific test: + +> At the moment, the recommendation is to run from the IDE. Remember to run `make` if you change the app. + +## How to debug a ledger app? + +You can use vscode or clion to debug the app. We recommend using CLion but we provide a vscode (unsupported) configuration too. -# Prepare your development device +### Preconditions - **Please do not use a Ledger device with funds for development purposes.** +If you are using CLion, you need to a configuration file in your home directory: `$HOME/.gdbinit` with the following content: - **Have a second device that is used ONLY for development and testing** +``` +set auto-load local-gdbinit on +add-auto-load-safe-path / +``` + +### Warnings + +There are a few things to take into account when enabling Ledger App debugging: + +- Once you enable the local .gdbinit that is located in your project workspace. You **will break** local Rust debugging in your host. The reason is that debugging unit tests will use the same `.gdbinit` configuration that sets the environment to ARM. We are looking at some possible fixes. For now, if you want to debug unit tests instead of the ledger app, you need to comment out the lines in `.gdbinit` + +### Debugging + +1. Build your app + + ```bash + make + ``` + +2. Define your debug scenario + + Open `tests/zemu/tools/debug.mjs` and look for the line: + + ```bash + /// TIP you can use zemu commands here to take the app ... + ``` + + You can adjust this code to get the emulator to trigger a breakpoint in your app: + - send clicks + - send APDUs, etc + +3. Launch the emulator in debug mode + + > If you didnt install Zemu yet (previous section), then run `make zemu_install` + + ```bash + make zemu_debug + ``` + + The emulator will launch and immediately stop. You should see a black window + +4. Configure Clion debugger + + Your configuration should look similar to this: + + ![image](docs/img/clion_debugging.png) + + Check that the path mappings are correct + +5. Start CLion debugger + + You will hit a breakpoint in main. + Add breakpoints in other places and continue. + + Enjoy :) + +## Using a real device + +### How to prepare your DEVELOPMENT! device: + +> You can use an emulated device for development. This is only required if you are using a physical device +> +> **Please do not use a Ledger device with funds for development purposes.** +>> +> **Have a separate and marked device that is used ONLY for development and testing** There are a few additional steps that increase reproducibility and simplify development: **1 - Ensure your device works in your OS** -- In Linux hosts it might be necessary to adjust udev rules, etc. Refer to Ledger documentation: https://support.ledger.com/hc/en-us/articles/115005165269-Fix-connection-issues +- In Linux hosts it might be necessary to adjust udev rules, etc. + + Refer to Ledger documentation: https://support.ledger.com/hc/en-us/articles/115005165269-Fix-connection-issues **2 - Set a test mnemonic** -All our tests expect the device to be configured with a known test mnemonic. +Many of our integration tests expect the device to be configured with a known test mnemonic. - Plug your device while pressing the right button @@ -116,13 +235,12 @@ All our tests expect the device to be configured with a known test mnemonic. - Run `make dev_ca`. The device will receive a development certificate to avoid constant manual confirmations. -# Building the Ledger App +### Loading into your development device The Makefile will build the firmware in a docker container and leave the binary in the correct directory. - Build - The following command will build the app firmware inside a container and load to your device: ``` make # Builds the app ``` @@ -133,24 +251,7 @@ The Makefile will build the firmware in a docker container and leave the binary make load # Builds and loads the app to the device ``` -# Development (building C++ Code / Tests) - -This is useful when you want to make changes to libraries, run unit tests, etc. It will build all common libraries and unit tests. - -## Building unit tests -While we recommend you configure your preferred development environment, the minimum steps are as follows: - - ``` - mkdir build - cd build - cmake .. && make - ``` - **Run unit tests** - ``` - export GTEST_COLOR=1 && ctest -VV - ``` - -## Specifications +## APDU Specifications - [APDU Protocol](docs/APDUSPEC.md) - - [Transaction format](docs/TXSPEC.md) +- [Transaction format](docs/TXSPEC.md) diff --git a/app/Makefile b/app/Makefile index 74a26297..eb55ee42 100755 --- a/app/Makefile +++ b/app/Makefile @@ -27,8 +27,8 @@ include $(BOLOS_SDK)/Makefile.defines # Main app configuration APPNAME = "Cosmos" APPVERSION_M=2 -APPVERSION_N=11 -APPVERSION_P=1 +APPVERSION_N=12 +APPVERSION_P=0 APPPATH = "44'/118'" APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path $(APPPATH) diff --git a/docs/img/clion_debugging.png b/docs/img/clion_debugging.png new file mode 100644 index 0000000000000000000000000000000000000000..d6e8848c8c4b1f9468a03f255b7a424c8aecfda0 GIT binary patch literal 139227 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 Date: Sat, 2 May 2020 20:22:22 +0200 Subject: [PATCH 62/78] improving hdpath UI --- app/src/coin.h | 21 ++++--- app/src/view_custom.c | 37 +++++++++++ deps/ledger-zxlib/app/common/view.c | 62 ++----------------- deps/ledger-zxlib/app/common/view_internal.h | 6 ++ deps/ledger-zxlib/include/view_templates.h | 1 - deps/ledger-zxlib/include/zxversion.h | 20 ++++++ tests_zemu/snapshots/show-address-huge/0.png | Bin 0 -> 830 bytes tests_zemu/snapshots/show-address-huge/1.png | Bin 0 -> 860 bytes tests_zemu/snapshots/show-address-huge/2.png | Bin 0 -> 781 bytes tests_zemu/snapshots/show-address-huge/3.png | Bin 0 -> 724 bytes tests_zemu/snapshots/show-address-huge/4.png | Bin 0 -> 766 bytes tests_zemu/snapshots/show-address-huge/5.png | Bin 0 -> 461 bytes tests_zemu/snapshots/show-address-huge/6.png | Bin 0 -> 303 bytes tests_zemu/snapshots/show-address-huge/7.png | Bin 0 -> 521 bytes tests_zemu/snapshots/show-address/tmp0.png | Bin 809 -> 0 bytes tests_zemu/tests/test.js | 45 ++++++++++++++ 16 files changed, 125 insertions(+), 67 deletions(-) create mode 100644 app/src/view_custom.c create mode 100644 deps/ledger-zxlib/include/zxversion.h create mode 100644 tests_zemu/snapshots/show-address-huge/0.png create mode 100644 tests_zemu/snapshots/show-address-huge/1.png create mode 100644 tests_zemu/snapshots/show-address-huge/2.png create mode 100644 tests_zemu/snapshots/show-address-huge/3.png create mode 100644 tests_zemu/snapshots/show-address-huge/4.png create mode 100644 tests_zemu/snapshots/show-address-huge/5.png create mode 100644 tests_zemu/snapshots/show-address-huge/6.png create mode 100644 tests_zemu/snapshots/show-address-huge/7.png delete mode 100644 tests_zemu/snapshots/show-address/tmp0.png diff --git a/app/src/coin.h b/app/src/coin.h index f5e1a23b..5a88df1b 100644 --- a/app/src/coin.h +++ b/app/src/coin.h @@ -22,16 +22,24 @@ extern "C" { #include #include -#define HDPATH_0_DEFAULT (0x80000000 | 0x2c) -#define HDPATH_1_DEFAULT (0x80000000 | 0x76) -#define HDPATH_2_DEFAULT (0x80000000 | 0) -#define HDPATH_3_DEFAULT (0) -#define HDPATH_4_DEFAULT (0) +#define HDPATH_0_DEFAULT (0x80000000u | 0x2cu) +#define HDPATH_1_DEFAULT (0x80000000u | 0x76u) +#define HDPATH_2_DEFAULT (0x80000000u | 0u) +#define HDPATH_3_DEFAULT (0u) +#define HDPATH_4_DEFAULT (0u) + +#define HDPATH_LEN_DEFAULT 5u typedef enum { addr_secp256k1 = 0, } address_kind_e; +#define SECP256K1_PK_LEN 33u + +#define VIEW_ADDRESS_OFFSET_SECP256K1 SECP256K1_PK_LEN +#define VIEW_ADDRESS_ITEM_COUNT 2 +#define VIEW_ADDRESS_LAST_PAGE_DEFAULT 0 + #define MENU_MAIN_APP_LINE1 "Cosmos" #ifdef TESTING_ENABLED #define MENU_MAIN_APP_LINE2 "Cosmos TEST!" @@ -40,9 +48,6 @@ typedef enum { #endif #define APPVERSION_LINE2 "" -#define VIEW_ADDRESS_ITEM_COUNT 2 -#define VIEW_ADDRESS_BUFFER_OFFSET (PK_LEN) - #ifdef __cplusplus } #endif diff --git a/app/src/view_custom.c b/app/src/view_custom.c new file mode 100644 index 00000000..cf6fea2c --- /dev/null +++ b/app/src/view_custom.c @@ -0,0 +1,37 @@ +/******************************************************************************* +* (c) 2018, 2019 Zondax GmbH +* (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#include "view.h" +#include "coin.h" +#include "crypto.h" +#include "view_internal.h" +#include + +#include +#include + +view_error_t view_printAddr() { + snprintf(viewdata.addr, MAX_CHARS_ADDR, "%s", (char *) (G_io_apdu_buffer + VIEW_ADDRESS_OFFSET_SECP256K1)); + splitValueField(); + return view_no_error; +} + +view_error_t view_printPath() { + bip32_to_str(viewdata.addr, MAX_CHARS_ADDR, hdPath, HDPATH_LEN_DEFAULT); + splitValueField(); + return view_no_error; +} diff --git a/deps/ledger-zxlib/app/common/view.c b/deps/ledger-zxlib/app/common/view.c index 8a63c891..18243adf 100644 --- a/deps/ledger-zxlib/app/common/view.c +++ b/deps/ledger-zxlib/app/common/view.c @@ -99,13 +99,12 @@ void h_paging_decrease() { if (viewdata.itemIdx > 0) { viewdata.itemIdx--; // jump to last page. update will cap this value - viewdata.pageIdx = 0; -// viewdata.pageIdx = 255; + viewdata.pageIdx = VIEW_ADDRESS_LAST_PAGE_DEFAULT; } } } -__Z_INLINE void h_paging_set_page_count(uint8_t pageCount) { +void h_paging_set_page_count(uint8_t pageCount) { viewdata.pageCount = pageCount; if (viewdata.pageIdx > viewdata.pageCount) { viewdata.pageIdx = viewdata.pageCount - 1; @@ -138,68 +137,15 @@ view_error_t h_review_update_data() { return view_no_error; } -__Z_INLINE view_error_t printAddr() { -#if !defined(HAVE_UX_FLOW) - if (viewdata.addrKind != addr_secp256k1 && - viewdata.addrKind != addr_sapling) { - return view_error_detected; - } - - char *p = NULL; - switch (viewdata.addrKind) { - case addr_secp256k1: { - h_paging_set_page_count(1); - snprintf(viewdata.key, MAX_CHARS_PER_KEY_LINE, "unshielded"); - p = (char *) (G_io_apdu_buffer + ADDR_OFFSET_SECP256K1); - p += MAX_CHARS_PER_VALUE1_LINE * viewdata.pageIdx; - snprintf(viewdata.value, MAX_CHARS_PER_VALUE1_LINE, "%s", (char *) (G_io_apdu_buffer + ADDR_OFFSET_SECP256K1)); - break; - } - - case addr_sapling: { - h_paging_set_page_count(3); - - snprintf(viewdata.key, MAX_CHARS_PER_KEY_LINE, "shielded [%d/%d]", viewdata.pageIdx + 1, viewdata.pageCount); - p = (char *) (G_io_apdu_buffer + ADDR_OFFSET_SAPLING); - p += MAX_CHARS_PER_VALUE1_LINE * viewdata.pageIdx; - snprintf(viewdata.value, MAX_CHARS_PER_VALUE1_LINE, "%s", p); - break; - } - - default: - return view_error_detected; - } -#else - snprintf(viewdata.addr, MAX_CHARS_ADDR, "%s", (char *) (G_io_apdu_buffer + VIEW_ADDRESS_BUFFER_OFFSET)); -#endif - splitValueField(); - return view_no_error; -} - -__Z_INLINE view_error_t printPath() { -#if !defined(HAVE_UX_FLOW) - h_paging_set_page_count(2); - snprintf(viewdata.key, MAX_CHARS_PER_KEY_LINE, "path [%d/%d]", viewdata.pageIdx + 1, viewdata.pageCount); - snprintf(viewdata.value, MAX_CHARS_PER_VALUE1_LINE, "SOME_PATH %d", viewdata.pageIdx + 1); -#else - bip32_to_str(viewdata.addr, MAX_CHARS_ADDR, hdPath, HDPATH_LEN_DEFAULT); -#endif - splitValueField(); - return view_no_error; -} - view_error_t h_addr_update_item(uint8_t idx) { MEMZERO(viewdata.value, MAX_CHARS_PER_VALUE1_LINE); switch (idx) { - case 0: return printAddr(); - case 1: return printPath(); + case 0: return view_printAddr(); + case 1: return view_printPath(); default: return view_error_detected; } - - splitValueField(); - return view_no_error; } void io_seproxyhal_display(const bagl_element_t *element) { diff --git a/deps/ledger-zxlib/app/common/view_internal.h b/deps/ledger-zxlib/app/common/view_internal.h index 35f5811d..e5c39ad7 100644 --- a/deps/ledger-zxlib/app/common/view_internal.h +++ b/deps/ledger-zxlib/app/common/view_internal.h @@ -105,6 +105,12 @@ void h_paging_increase(); void h_paging_decrease(); +void h_paging_set_page_count(uint8_t pageCount); + view_error_t h_review_update_data(); view_error_t h_addr_update_item(uint8_t idx); + +view_error_t view_printAddr(); + +view_error_t view_printPath(); diff --git a/deps/ledger-zxlib/include/view_templates.h b/deps/ledger-zxlib/include/view_templates.h index 1ba531cb..0b5c6bcd 100644 --- a/deps/ledger-zxlib/include/view_templates.h +++ b/deps/ledger-zxlib/include/view_templates.h @@ -155,7 +155,6 @@ NULL, /* text */ \ } -// FIXME: Up/Down vs Left/Right (not supported in zemu) #define UI_BACKGROUND_LEFT_RIGHT_ICONS \ UI_FillRectangle(0, 0, 0, UI_SCREEN_WIDTH, UI_SCREEN_HEIGHT, 0x000000, 0xFFFFFF), \ UI_Icon(UIID_ICONLEFT, 0, 0, 7, 7, BAGL_GLYPH_ICON_LEFT), \ diff --git a/deps/ledger-zxlib/include/zxversion.h b/deps/ledger-zxlib/include/zxversion.h new file mode 100644 index 00000000..f7a36f8c --- /dev/null +++ b/deps/ledger-zxlib/include/zxversion.h @@ -0,0 +1,20 @@ +/******************************************************************************* +* (c) 2018 Zondax GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +#pragma once + +#define ZXLIB_MAJOR 1 +#define ZXLIB_MINOR 0 +#define ZXLIB_PATCH 0 diff --git a/tests_zemu/snapshots/show-address-huge/0.png b/tests_zemu/snapshots/show-address-huge/0.png new file mode 100644 index 0000000000000000000000000000000000000000..a2751624bd8cd30d12511e7de4bf7583beae596b GIT binary patch literal 830 zcmXAoe@GKy7{_O`t@O?;Bi$7_H4ADuJ8zPp=9*L2{9U5KD36YLo0f{AlA+VlfoV7` z%we;cWPzl@q7*cd>yI;rEX_(2!x;l3rThUCgnDkl-TS=o-ur#N&*#gvL@0&E_?tVpPjo5YVgK3XLy8)E#3EFZk0~)JPrYZ409tpF{w*LHNcgVp933#*489(f*T(sxjjO@ zSB*YR(;Mpi<+?eZMXPg`FPtluqKl|5My)q|<=U7*EFLFh{8Mfm&Co+@;LuOP2@LDo zxl9PE0{?nmyT;wgM3{Mc@P$wUWVkjF6$H-d3qS)v+DT3thgrrlJS=t0n#HEUJpz$X zX{?g9K!9LD7roULG7K|Usy%hW9Wz zQ4WJQnCI@fLLk=3sg^HPOy^hz-&c=^PD23WT2B@vN-U*{tgX;A(!og`oD6S8Al)n~ z`R6^YkG~S)V0a{BEeHx)&&jB*(-I8O(4#N?coQ+kB5KH(;@QBKBkhS3uSf`2<@r_` yh7a57?$GkYi5$>~$1mDUo7*Fh#p{rqL+jz}X7QUJ821bQ3xP(RojP=>xcMI<4PNp9 literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/show-address-huge/1.png b/tests_zemu/snapshots/show-address-huge/1.png new file mode 100644 index 0000000000000000000000000000000000000000..bf520f458450d66dbd6de46a25a570c49ca95dd6 GIT binary patch literal 860 zcmXX_Ye-XJ7@kd5*bbsIb0?9_DeH%59Ot!_o71J4W}2vUu|vbnOKR8;Me$0Xh>B-k zl7s!2h8o_%NTpfGF*wE%b6Kc~u^>xK+wzABLcJ&ZalYr{{oeC_&-*;@*O;1e1#`pQ z1Ofq;m=Ko+SOcHzlnB-}|B^s~K&VKJ)7~s|*_pkOb}P{hvMPHGg84r zaa(x?^X2EF1RO~X*x20b&QcAf#Sm1Ho< z|B+gSgl-_!N3w+my%1w7J0&?iwdfV46-f<<>xVdAWOIx~KMcW|#6*&i$egz}aoq+g ziqdXQvcBxmf@?>G$%p7Z=9a|hbD?N54^WcP^R$|gPzp`;^Not>3Xjyg`I=389g^?a zA>2@)+3b^qgvs_w9&-b2hhBur8emaF)P4?@)kY)6sw+zgmHMLd8bS@lqb{^jSrw>b zn23)x+?Z=e7fwwNlI(gs==unhXoagcmdAm6IPuF?!zC$$&C`Q+yNFhwTE-7ojEqutmz}i0P$qGZ<8w0S)CvQlL1bQHKw(lf-1C(+>j$ zn$Z&fCrC!OaQ>lC33}(HhcKl*wsGnz=&5IFxp)i?bW zY<<|?iz4QmML1QS4y`CK*7|L31P9X`awt1c0Nz@mQQ6)WzPmFw!k0Jm-cnMS2H2N( za{l$e!Em{g8!#Wj8m37c=0aMubFe5!A zjE;^!rR&kYXX@)%m>ak?W&o7E!%*d868D%jNlrYZgcI)j3XA$EOX*kjf)7`3D~rd^ TevbYPuSSp*jXzL1)dN{O@Kl}+(h zY8-@X#vBNWF!QBMkTW>r!1;nnM6~`GNtB~WriAs(Klto7JY8I}>t{+5}$0;7g_`2dw zq)v5JJ(s-=e?u;A-7QX@@o1Cd8}~0A?YB;S9gci_u|1rhwROx@uT{2965_xpA#QRS zn0pU-xQ^EO@@Udwkh^kyzH>np{KXHmq)zUku-%!O_R|` z47D<03zm)zKNXvyrw0fSEU%8j(iC+lA;ex7q!#v6G(IjDQUzQ@?VCnTQJ9rAwf0;3 zL>4wERxhy#k$f(QdK#RaMA{P=nS`i~&}sUe!;f=x5)VifFB&J<5d%|)&cCu`SC`^s z8A`SduyMA%3!u-4n$j>kW&SCE&b5?aAY=6=(?f{ehozS_Z5|o}M=~_11WAQdc0^G{ zjndZ)X=YyojuwQOYxe88n5%@DJ5RNU)pUNyJ2ww6tSseX%V9$bQPchx+|+u={YGh*;Um+9<4LB?Uhy<6_B~jaJqLb3JAj+#lr7 zi8Cl+$3Fby;>L zZbl?SgLs|ZydYq^qKAi?W>(Ft&S+B^*Zk+GULnRLVKLP1ZGQwhNR%vz{x@Yw0P+m1 z(SHMaZDF7CX)?XNg1@chQN1i2N&+{75Ci#eyfW0943V;GH(?6|TYxa03C5$OC4mG? z4pwi~D1kZb&dN#!iM!x;5RmRW;EGLJ)~0_gW-h_sI^RzE@kCesx6eCcvCrmT{^eu{ V)Z@L_@F-k!POi34Gk&@1{y#S>Qa%6x literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/show-address-huge/3.png b/tests_zemu/snapshots/show-address-huge/3.png new file mode 100644 index 0000000000000000000000000000000000000000..f345cffa1fe6b817bf7280478954b4be530700f6 GIT binary patch literal 724 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfh&bba4!+V0?Qpu;_M}h|57^ zjkEv6ZRCZot$i_7ae~oV4Kv+E(SKHpPqNr6_O*#S_KTCy^6QEVVz#)7@11burD@Ke z1>$q!x*9(9Nb#k}T{1|iTg5nYZkyuf%LXYKHzr0(O{jE}KJWKzy2*tP6WW^F{7>>o zrc99JwdOtlqIZAb;l48&5mIeuE^RpRC*-g%@BM?v?t8FFrnnuR5ScRJ=ER*V6Hn+E zow&2M5oqLzp0@PQ$2MnMWc*kR)C1JIfN^H}_hXwU-^`RSTKw?Kj^4}3Df^mx_xA#& z9y+9%TqrP~dGW)_w$)M@3xEdkIzPL~p&@Uo@VN-6e|naLQN0hdk$i5=m4-9klYZVm z@h8y8q()`Io@|F_UwVPc@*c-B8!gZGYilc)exH(IQCrM=V$ba#6M1--=L7vI&j~WD zczc^-aVXHiAb%L7OoMoSTAS?5rC>YLOmvLcfmVZEzz$-jEl+e$%e*o1X9&pl7-gUd z{-$?SiWN4S#9S@7U;qvvBX*<3i9j#UZJRB%ZjvM0OdthxF;K)696-xc)>S@^GfYZQf`MYZ7}I+Tl#RN z>7Pk$7dM#PS(<2~!@K{sbpIrf$CWml@XVZ@Ql||h-hny0!QowA{n$VIhmeuUg$*ZU zfKJ^2RQ7*O;)y>(l2REFz`&B7>CU0CeEQbHs!ZwS)3+h0{|q`m4bAMf8E^tqD}$%2 KpUXO@geCy}-$HNz literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/show-address-huge/4.png b/tests_zemu/snapshots/show-address-huge/4.png new file mode 100644 index 0000000000000000000000000000000000000000..159079fd581378f28e79eed49c7e6b8116a52361 GIT binary patch literal 766 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfbkUba4!+V0?QpFi1I4#QET2 zt}XvrUpqhEVYuhTqOQ!@3p1PNYW};*?P+5x_O*#S_KTB{f4{TC&WvuoD#gy_$2WZJ zj{EQ>fMrVcW17hulRJyW_swpToq2JC3D3->i4%Y7&isGngK$obxzPy)W1w24<}-a~b^{sz zFF72Z5Sik3cyHyyd8RTZH7nZ`Hv=^R^*S&ceSgW9m@Hh<@xX*}b< z;p5yBJ#12ZDQ?WX;gcnKy$=IT$*{QUz|8BMmMLNM-OZtLHRH_oO9m-+v4R>M8{}3(2su1M)|pOCR$!KNSR=HwZMx_GR5zt3{byfGdR#dVRs|5BK)wXWXgof z@Wa3$07^ta!;%N6+H{ZivoqY=C;faZm3M7}^5=DJ`JT@<=fPp||kn!haZM9kVm zP^^@tOpxSdHv+or{DzN_Qf`MYeF!(bvn0_(hc`S^0vO2CGeD$i+z%ZiZ(yhj%$$7^ z=q@b~XJ^+LU|{cS0g>xI9@?6cu^@5cPEVj6K>xO}0fXpb!Ky@H)clVID(>Xr1qO{b zFcm2|GxK(zNf!v1CwJ}GW#`)!RTba4!+V0?QpFi+Z$!TF%O z;-3GFKM(4)+&X(p^O73xtB6^G_Fok`b9G-eHuKqfBt^G36lHXSS%)7?mAf%bX@SkG zHftmKO9m(RtY)0K@}bK!zr!ldhda-NG8^@0d^nNv&x@H?{!)QbGmla5;n^u_k|`5r zO8H62UF5#+3siQc;HtsN7}J=OHBpC+C2Gu#PAC|g`WS^9otSgtPuO9V;!6&PRX(p{ zlTw?h4K)5Dk6h&8xolDyKZ=Y_`0$4FPCnBKGPwN1A?3-InKM$BwY@E7yM6qGO|$qu zVM(csipe~@CO$^>E^Ta5Ad7(t%uVNZ}aNeA*cA7l@! z_|II-_L_TSDZs+%0Bg1qiQCikcWuuYE{T~Ugf(c=3l59UW zw2K`5b=rAW(%)wfm#Mi9eSJ-07}&l9)b+fO6Ls-Stiq z(`Q)tqIXOmML@ZVh7FnNXWh7phLz0p>)iNLGz>CB!-mYN0T~)rGBgY_L&G35G;GMc zn}t6?!%Ak=up@)aMguad24r>`Rx+!G9T{Za(Zba=Jk+q!K*J#o4>fEw(D06K9M$j= znN!KoFf<@@3K<%P24vpJ!qv$;6*LSo$P5in$vh=9eecFmGCxLU zkfC9x;i-n{m>X9h^N`Gr%u_PcF$>d2TbMqYfHHloh3TU$Odstp_w%_!CBXMQ00000 LNkvXXu0mjfZSw8( literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/show-address/tmp0.png b/tests_zemu/snapshots/show-address/tmp0.png deleted file mode 100644 index c337bb87e8603b8b16059c4a4df8f5b5c337005b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 809 zcmXAoeMl2w7{F)N6x*47VD5^XiCSVQ=c$>A+MGJnp_!O5tHE{7IdmoA7UDca7+=on3Nq^Jv6%Rmhxe?wYN#RuoT{~p&T=s{CB*|(0X7iIuFbVk=qk- zS#OR9R!@1!**G#M+HO%5-J#lTB^|bAn?K`qTY|M|rdAX3H>=S3kA_r743{tyR*Mul zB#6H$>;Sq&`W@-EHESS`kg}CZX6YCb*AsL&9b<3SZ~+)M_q>eq>g6h_c++ARh;~~R z5(ZfkA)ex&YPij8L{54KquOYDSHp%sf}bguV4N(N2nGApYGb4E38fG-?G8-g$ehA1 z$GF3sZ2dT~MdlanViXjmpv0&vp>4MLV)(cfN>uUMPn;es5E7!EwpucwQk(O!z(3IF z<4z4%^h!(=-&j~G#!4!APXO>@=fj$B6Q%qfze`PK2@=&{G@V4aO~b;H374(+hP?qy z1vPNeSvnI9#XNNvh@X=J1xM`d&BcfuV;|)uj6edUc9_YXsUK0V4W9ttHVxDV;UGmR z31a3atz3Y>&@diE#Idwgm@(0~cFrvUC$ zDG#6kZ6)O>CETS1WQGlJdejwMvIGmANzk8h2%$|Gj))WX^7OT2R)HK0(WBbFMEI*0 zqj@t{yeNUh)jrU*(OF95O&Z=;L_z-58~}e=C6*Ff`6{({frVFKW(ihU-Jd{OV(h+x zV&=2E~lJGgaUuVZO{5(%Ej{ wz0#uL=DrV&rbYZ(n+ClD7V@moXwq$q>U0^kuYUxlz!MND6{^^tvsZ5Y1Axa|0RR91 diff --git a/tests_zemu/tests/test.js b/tests_zemu/tests/test.js index 25e544ed..9ddead38 100644 --- a/tests_zemu/tests/test.js +++ b/tests_zemu/tests/test.js @@ -188,6 +188,51 @@ describe('Basic checks', function () { } }); + it('show address - HUGE', async function () { + const snapshotPrefixGolden = "snapshots/show-address-huge/"; + const snapshotPrefixTmp = "snapshots-tmp/show-address-huge/"; + let snapshotCount = 0; + + const sim = new Zemu(APP_PATH); + try { + await sim.start(sim_options); + const app = new CosmosApp(sim.getTransport()); + + // Derivation path. First 3 items are automatically hardened! + const path = [44, 118, 2147483647, 0, 4294967295]; + const respRequest = app.showAddressAndPubKey(path, "cosmos"); + + // We need to wait until the app responds to the APDU + await Zemu.sleep(2000); + + // Now navigate the address / path + await sim.snapshot(`${snapshotPrefixTmp}${snapshotCount++}.png`); + await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); + await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); + await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); + await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); + await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); + await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); + await sim.clickBoth(`${snapshotPrefixTmp}${snapshotCount++}.png`); + + const resp = await respRequest; + console.log(resp); + + compareSnapshots(snapshotPrefixTmp, snapshotPrefixGolden, snapshotCount); + + expect(resp.return_code).toEqual(0x9000); + expect(resp.error_message).toEqual("No errors"); + + expect(resp).toHaveProperty("bech32_address"); + expect(resp).toHaveProperty("compressed_pk"); + + expect(resp.bech32_address).toEqual("cosmos1ex7gkwwmq4vcgdwcalaq3t20pgwr37u6ntkqzh"); + expect(resp.compressed_pk.length).toEqual(33); + } finally { + await sim.close(); + } + }); + it('sign basic', async function () { const snapshotPrefixGolden = "snapshots/sign-basic/"; const snapshotPrefixTmp = "snapshots-tmp/sign-basic/"; From 940cd957c0a80e007b58abf2635d0925b8959466 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sun, 3 May 2020 11:02:54 +0200 Subject: [PATCH 63/78] clean up + improved zemu tests --- .circleci/config.yml | 98 ++++++++++++++++++++++++++++----------- .gitignore | 1 + CMakeLists.txt | 1 + app/Makefile | 2 +- app/script.ld | 2 +- app/src/coin.h | 14 ++---- app/src/common/actions.c | 4 +- app/src/common/app_main.c | 2 +- app/src/crypto.c | 14 +++--- app/src/crypto.h | 2 - app/src/parser_impl.h | 2 +- docs/APDUSPEC.md | 62 ++++++++++++------------- 12 files changed, 122 insertions(+), 82 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 532e514d..cd76c457 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,7 +13,7 @@ jobs: build_ledger: docker: - - image: zondax/ledger-docker-bolos:v1.0 + - image: zondax/builder-bolos:latest environment: - BOLOS_SDK=/home/zondax/project/deps/nanos-secure-sdk - BOLOS_ENV=/opt/bolos @@ -28,36 +28,78 @@ jobs: cd /home/zondax/project make -# build_package: -# docker: -# - image: zondax/ledger-docker-bolos:v1.0 -# environment: -# - BOLOS_SDK=/home/zondax/project/deps/nanos-secure-sdk -# - BOLOS_ENV=/opt/bolos -# steps: -# - checkout -# - run: git submodule update --init --recursive -# - run: -# name: Build -# command: | -# source /home/zondax/.cargo/env -# cd /home/zondax/project -# make -# - store_artifacts: -# path: /home/zondax/project/app/pkg/zxtool.sh -# - run: /home/zondax/go/bin/ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete $(/home/zondax/project/app/pkg/zxtool.sh version) /home/zondax/project/app/pkg/zxtool.sh + test_zemu: + machine: + image: ubuntu-1604:201903-01 + working_directory: ~/repo + environment: + BASH_ENV: "/opt/circleci/.nvm/nvm.sh" + steps: + - checkout + - run: git submodule update --init --recursive + - run: + name: Build Ledger app + command: | + make + - run: + name: Install node + yarn + command: | + nvm install 13.12.0 + nvm use 13.12.0 + npm install -g yarn + - run: + name: Build js app + command: | + nvm use 13.12.0 + cd js && yarn install && yarn build + - run: + name: Build/Install build js deps + command: | + nvm use 13.12.0 + make zemu_install + - run: + name: Workaround/Pull docker + command: | + docker pull zondax/builder-zemu + - run: + name: Run zemu tests + command: | + nvm use 13.12.0 + make zemu_test + + build_package: + docker: + - image: zondax/builder-bolos:latest + environment: + - BOLOS_SDK=/home/zondax/project/deps/nanos-secure-sdk + - BOLOS_ENV=/opt/bolos + steps: + - checkout + - run: git submodule update --init --recursive + - run: + name: Build + command: | + source /home/zondax/.cargo/env + cd /home/zondax/project + make + - store_artifacts: + path: /home/zondax/project/app/pkg/zxtool.sh + - run: /home/zondax/go/bin/ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete $(/home/zondax/project/app/pkg/zxtool.sh version) /home/zondax/project/app/pkg/zxtool.sh workflows: version: 2 - build_all: + + default: jobs: - build - build_ledger -# - build_package: -# requires: -# - build -# - build_ledger -# filters: -# branches: -# only: -# - master + - test_zemu + - build_package: + requires: + - build + - build_ledger + - test_zemu + filters: + branches: + only: + - master diff --git a/.gitignore b/.gitignore index e445c919..b2ba5111 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,7 @@ cmake-build-fuzz/ \deps/* !\deps/nanos-secure-sdk +\deps/nano2-sdk !\deps/ledger-zxlib !\deps/tinycbor !\deps/BLAKE diff --git a/CMakeLists.txt b/CMakeLists.txt index 16ab114b..2c474d24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ target_include_directories(app_lib PUBLIC deps/ledger-zxlib/include deps/jsmn/src app/src + app/src/common ) ############################################################## diff --git a/app/Makefile b/app/Makefile index eb55ee42..51e2856b 100755 --- a/app/Makefile +++ b/app/Makefile @@ -82,7 +82,7 @@ WEBUSB_URL = www.ledgerwallet.com DEFINES += HAVE_WEBUSB WEBUSB_URL_SIZE_B=$(shell echo -n $(WEBUSB_URL) | wc -c) WEBUSB_URL=$(shell echo -n $(WEBUSB_URL) | sed -e "s/./\\\'\0\\\',/g") ifeq ($(TARGET_NAME),TARGET_NANOX) -DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=300 +DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=300 DEFINES += HAVE_GLO096 DEFINES += HAVE_BAGL BAGL_WIDTH=128 BAGL_HEIGHT=64 diff --git a/app/script.ld b/app/script.ld index 81025ad3..0f71a9c4 100644 --- a/app/script.ld +++ b/app/script.ld @@ -1,7 +1,7 @@ /******************************************************************************* * Ledger Blue - Secure firmware +* (c) 2019 Zondax GmbH * (c) 2016, 2017 Ledger -* (c) 2018 Zonda GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/app/src/coin.h b/app/src/coin.h index 5a88df1b..a24ccd80 100644 --- a/app/src/coin.h +++ b/app/src/coin.h @@ -22,30 +22,26 @@ extern "C" { #include #include +#define HDPATH_LEN_DEFAULT 5 + #define HDPATH_0_DEFAULT (0x80000000u | 0x2cu) #define HDPATH_1_DEFAULT (0x80000000u | 0x76u) #define HDPATH_2_DEFAULT (0x80000000u | 0u) #define HDPATH_3_DEFAULT (0u) #define HDPATH_4_DEFAULT (0u) -#define HDPATH_LEN_DEFAULT 5u +#define PK_LEN_SECP256K1 33u typedef enum { addr_secp256k1 = 0, } address_kind_e; -#define SECP256K1_PK_LEN 33u - -#define VIEW_ADDRESS_OFFSET_SECP256K1 SECP256K1_PK_LEN -#define VIEW_ADDRESS_ITEM_COUNT 2 +#define VIEW_ADDRESS_OFFSET_SECP256K1 PK_LEN_SECP256K1 +#define VIEW_ADDRESS_ITEM_COUNT 2 #define VIEW_ADDRESS_LAST_PAGE_DEFAULT 0 #define MENU_MAIN_APP_LINE1 "Cosmos" -#ifdef TESTING_ENABLED -#define MENU_MAIN_APP_LINE2 "Cosmos TEST!" -#else #define MENU_MAIN_APP_LINE2 "App" -#endif #define APPVERSION_LINE2 "" #ifdef __cplusplus diff --git a/app/src/common/actions.c b/app/src/common/actions.c index 0a19eeba..3e682ee1 100644 --- a/app/src/common/actions.c +++ b/app/src/common/actions.c @@ -16,13 +16,15 @@ ********************************************************************************/ #include "actions.h" -#include "../crypto.h" +#include "crypto.h" #include "tx.h" #include "apdu_codes.h" #include +#include "coin.h" uint8_t app_sign() { uint8_t *signature = G_io_apdu_buffer; + const uint8_t *message = tx_get_buffer(); const uint16_t messageLength = tx_get_buffer_length(); diff --git a/app/src/common/app_main.c b/app/src/common/app_main.c index 85fc7a03..469b7355 100644 --- a/app/src/common/app_main.c +++ b/app/src/common/app_main.c @@ -24,7 +24,7 @@ #include "view.h" #include "actions.h" #include "tx.h" -#include "../crypto.h" +#include "crypto.h" #include "coin.h" #include "zxmacros.h" diff --git a/app/src/crypto.c b/app/src/crypto.c index 3ac96f52..5a4460cd 100644 --- a/app/src/crypto.c +++ b/app/src/crypto.c @@ -34,7 +34,7 @@ void crypto_extractPublicKey(const uint32_t path[HDPATH_LEN_DEFAULT], uint8_t *p cx_ecfp_private_key_t cx_privateKey; uint8_t privateKeyData[32]; - if (pubKeyLen < PK_LEN) { + if (pubKeyLen < PK_LEN_SECP256K1) { return; } @@ -68,7 +68,7 @@ void crypto_extractPublicKey(const uint32_t path[HDPATH_LEN_DEFAULT], uint8_t *p pubKey[31] |= 0x80; } ////////////////////// - MEMCPY(pubKey, cx_publicKey.W, PK_LEN); + MEMCPY(pubKey, cx_publicKey.W, PK_LEN_SECP256K1); } uint16_t crypto_sign(uint8_t *signature, @@ -170,7 +170,7 @@ void crypto_set_hrp(char *p) { } uint16_t crypto_fillAddress(uint8_t *buffer, uint16_t buffer_len) { - if (buffer_len < PK_LEN + 50) { + if (buffer_len < PK_LEN_SECP256K1 + 50) { return 0; } @@ -179,13 +179,13 @@ uint16_t crypto_fillAddress(uint8_t *buffer, uint16_t buffer_len) { // Hash it uint8_t hashed1_pk[CX_SHA256_SIZE]; - cx_hash_sha256(buffer, PK_LEN, hashed1_pk, CX_SHA256_SIZE); + cx_hash_sha256(buffer, PK_LEN_SECP256K1, hashed1_pk, CX_SHA256_SIZE); uint8_t hashed2_pk[CX_RIPEMD160_SIZE]; ripemd160_32(hashed2_pk, hashed1_pk); - char *addr = (char *) (buffer + PK_LEN); - bech32EncodeFromBytes(addr, buffer_len - PK_LEN, bech32_hrp, hashed2_pk, CX_RIPEMD160_SIZE); + char *addr = (char *) (buffer + PK_LEN_SECP256K1); + bech32EncodeFromBytes(addr, buffer_len - PK_LEN_SECP256K1, bech32_hrp, hashed2_pk, CX_RIPEMD160_SIZE); - return PK_LEN + strlen(addr); + return PK_LEN_SECP256K1 + strlen(addr); } diff --git a/app/src/crypto.h b/app/src/crypto.h index acbf9666..5c61f8cb 100644 --- a/app/src/crypto.h +++ b/app/src/crypto.h @@ -23,9 +23,7 @@ extern "C" { #endif -#define HDPATH_LEN_DEFAULT 5u #define MAX_BECH32_HRP_LEN 83u -#define PK_LEN 33u extern uint32_t hdPath[HDPATH_LEN_DEFAULT]; extern char *hrp; diff --git a/app/src/parser_impl.h b/app/src/parser_impl.h index 43a9d675..032df72d 100644 --- a/app/src/parser_impl.h +++ b/app/src/parser_impl.h @@ -15,7 +15,7 @@ ********************************************************************************/ #pragma once -#include "common/parser_common.h" +#include "parser_common.h" #include "json/json_parser.h" #include "parser_txdef.h" diff --git a/docs/APDUSPEC.md b/docs/APDUSPEC.md index 6a4fbb1b..4a33b5a8 100644 --- a/docs/APDUSPEC.md +++ b/docs/APDUSPEC.md @@ -63,6 +63,36 @@ The general structure of commands and responses is as follows: -------------- +### INS_GET_ADDR_SECP256K1 + +#### Command + +| Field | Type | Content | Expected | +| ---------- | -------------- | ------------------------------ | -------------- | +| CLA | byte (1) | Application Identifier | 0x55 | +| INS | byte (1) | Instruction ID | 0x04 | +| P1 | byte (1) | Display address/path on device | 0x00 No | +| | | | 0x01 Yes | +| P2 | byte (1) | Parameter 2 | ignored | +| L | byte (1) | Bytes in payload | (depends) | +| HRP_LEN | byte(1) | Bech32 HRP Length | 1<=HRP_LEN<=83 | +| HRP | byte (HRP_LEN) | Bech32 HRP | | +| Path[0] | byte (4) | Derivation Path Data | 44 | +| Path[1] | byte (4) | Derivation Path Data | 118 | +| Path[2] | byte (4) | Derivation Path Data | ? | +| Path[3] | byte (4) | Derivation Path Data | ? | +| Path[4] | byte (4) | Derivation Path Data | ? | + +First three items in the derivation path will be hardened automatically hardened + +#### Response + +| Field | Type | Content | Note | +| ------- | --------- | --------------------- | ------------------------ | +| PK | byte (33) | Compressed Public Key | | +| ADDR | byte (65) | Bech 32 addr | | +| SW1-SW2 | byte (2) | Return code | see list of return codes | + ### SIGN_SECP256K1 #### Command @@ -79,7 +109,7 @@ The general structure of commands and responses is as follows: The first packet/chunk includes only the derivation path -All other packets/chunks should contain message to sign +All other packets/chunks should contain message to sign *First Packet* @@ -105,33 +135,3 @@ All other packets/chunks should contain message to sign | SW1-SW2 | byte (2) | Return code | see list of return codes | -------------- - -### INS_GET_ADDR_SECP256K1 - -#### Command - -| Field | Type | Content | Expected | -| ---------- | -------------- | ------------------------------ | -------------- | -| CLA | byte (1) | Application Identifier | 0x55 | -| INS | byte (1) | Instruction ID | 0x04 | -| P1 | byte (1) | Display address/path on device | 0x00 No | -| | | | 0x01 Yes | -| P2 | byte (1) | Parameter 2 | ignored | -| L | byte (1) | Bytes in payload | (depends) | -| HRP_LEN | byte(1) | Bech32 HRP Length | 1<=HRP_LEN<=83 | -| HRP | byte (HRP_LEN) | Bech32 HRP | | -| Path[0] | byte (4) | Derivation Path Data | 44 | -| Path[1] | byte (4) | Derivation Path Data | 118 | -| Path[2] | byte (4) | Derivation Path Data | ? | -| Path[3] | byte (4) | Derivation Path Data | ? | -| Path[4] | byte (4) | Derivation Path Data | ? | - -First three items in the derivation path will be hardened automatically hardened - -#### Response - -| Field | Type | Content | Note | -| ------- | --------- | --------------------- | ------------------------ | -| PK | byte (33) | Compressed Public Key | | -| ADDR | byte (65) | Bech 32 addr | | -| SW1-SW2 | byte (2) | Return code | see list of return codes | From 65a21e94ee1dd6819d7668aa0b22a467f069dcc7 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sun, 3 May 2020 11:11:25 +0200 Subject: [PATCH 64/78] set zemu link default --- .circleci/config.yml | 5 ----- Makefile | 2 +- deps/ledger-zxlib/{cmake => }/dockerized_build.mk | 1 + deps/ledger-zxlib/include/zxversion.h | 2 +- 4 files changed, 3 insertions(+), 7 deletions(-) rename deps/ledger-zxlib/{cmake => }/dockerized_build.mk (99%) diff --git a/.circleci/config.yml b/.circleci/config.yml index cd76c457..e022664d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -47,11 +47,6 @@ jobs: nvm install 13.12.0 nvm use 13.12.0 npm install -g yarn - - run: - name: Build js app - command: | - nvm use 13.12.0 - cd js && yarn install && yarn build - run: name: Build/Install build js deps command: | diff --git a/Makefile b/Makefile index a3fcae5f..2a433b73 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ # BOLOS_SDK NOT DEFINED We use a containerized build approach ifeq ($(BOLOS_SDK),) -include $(CURDIR)/deps/ledger-zxlib/cmake/dockerized_build.mk +include $(CURDIR)/deps/ledger-zxlib/dockerized_build.mk else default: $(MAKE) -C app diff --git a/deps/ledger-zxlib/cmake/dockerized_build.mk b/deps/ledger-zxlib/dockerized_build.mk similarity index 99% rename from deps/ledger-zxlib/cmake/dockerized_build.mk rename to deps/ledger-zxlib/dockerized_build.mk index 0b1047ac..793e194b 100644 --- a/deps/ledger-zxlib/cmake/dockerized_build.mk +++ b/deps/ledger-zxlib/dockerized_build.mk @@ -17,6 +17,7 @@ .PHONY: all deps build clean load delete check_python show_info_recovery_mode TESTS_ZEMU_DIR ?= $(CURDIR)/tests_zemu +TESTS_ZEMU_JS_PACKAGE ?= LEDGER_SRC=$(CURDIR)/app DOCKER_APP_SRC=/project diff --git a/deps/ledger-zxlib/include/zxversion.h b/deps/ledger-zxlib/include/zxversion.h index f7a36f8c..8c23084f 100644 --- a/deps/ledger-zxlib/include/zxversion.h +++ b/deps/ledger-zxlib/include/zxversion.h @@ -17,4 +17,4 @@ #define ZXLIB_MAJOR 1 #define ZXLIB_MINOR 0 -#define ZXLIB_PATCH 0 +#define ZXLIB_PATCH 1 From b64568056c70ea5e6aeaedca6ac27ec6f3338f04 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sun, 3 May 2020 11:31:00 +0200 Subject: [PATCH 65/78] adding gitignore/gitkeep --- app/src/parser.c | 2 +- tests_zemu/snapshots-tmp/.gitignore | 1 + .../snapshots-tmp/show-address-huge/.gitkeep | 1 + .../snapshots-tmp/show-address/.gitkeep | 1 + tests_zemu/snapshots-tmp/sign-basic/.gitkeep | 1 + tests_zemu/tools/debug.mjs | 2 +- tests_zemu/yarn.lock | 280 +----------------- 7 files changed, 13 insertions(+), 275 deletions(-) create mode 100644 tests_zemu/snapshots-tmp/.gitignore create mode 100644 tests_zemu/snapshots-tmp/show-address-huge/.gitkeep create mode 100644 tests_zemu/snapshots-tmp/show-address/.gitkeep create mode 100644 tests_zemu/snapshots-tmp/sign-basic/.gitkeep diff --git a/app/src/parser.c b/app/src/parser.c index 57887aa3..89bd26a8 100644 --- a/app/src/parser.c +++ b/app/src/parser.c @@ -41,7 +41,7 @@ parser_error_t parser_validate(const parser_context_t *ctx) { char tmpVal[40]; for (uint8_t idx = 0; idx < numItems; idx++) { - uint8_t pageCount; + uint8_t pageCount = 0; CHECK_PARSER_ERR(parser_getItem(ctx, idx, tmpKey, sizeof(tmpKey), tmpVal, sizeof(tmpVal), 0, &pageCount)) } diff --git a/tests_zemu/snapshots-tmp/.gitignore b/tests_zemu/snapshots-tmp/.gitignore new file mode 100644 index 00000000..e33609d2 --- /dev/null +++ b/tests_zemu/snapshots-tmp/.gitignore @@ -0,0 +1 @@ +*.png diff --git a/tests_zemu/snapshots-tmp/show-address-huge/.gitkeep b/tests_zemu/snapshots-tmp/show-address-huge/.gitkeep new file mode 100644 index 00000000..33662f55 --- /dev/null +++ b/tests_zemu/snapshots-tmp/show-address-huge/.gitkeep @@ -0,0 +1 @@ +/* diff --git a/tests_zemu/snapshots-tmp/show-address/.gitkeep b/tests_zemu/snapshots-tmp/show-address/.gitkeep new file mode 100644 index 00000000..33662f55 --- /dev/null +++ b/tests_zemu/snapshots-tmp/show-address/.gitkeep @@ -0,0 +1 @@ +/* diff --git a/tests_zemu/snapshots-tmp/sign-basic/.gitkeep b/tests_zemu/snapshots-tmp/sign-basic/.gitkeep new file mode 100644 index 00000000..33662f55 --- /dev/null +++ b/tests_zemu/snapshots-tmp/sign-basic/.gitkeep @@ -0,0 +1 @@ +/* diff --git a/tests_zemu/tools/debug.mjs b/tests_zemu/tools/debug.mjs index 21f6578e..9de4488a 100644 --- a/tests_zemu/tools/debug.mjs +++ b/tests_zemu/tools/debug.mjs @@ -79,7 +79,7 @@ async function debugScenario(sim, app) { console.log(resp); } -async function main() {m +async function main() { await beforeStart(); if (process.argv.length > 2 && process.argv[2] === "debug") { diff --git a/tests_zemu/yarn.lock b/tests_zemu/yarn.lock index a0d4d98d..f4d055af 100644 --- a/tests_zemu/yarn.lock +++ b/tests_zemu/yarn.lock @@ -1140,11 +1140,6 @@ abab@^2.0.0: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg== -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - acorn-globals@^4.1.0: version "4.3.4" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" @@ -1200,11 +1195,6 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.11.0" -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - ansi-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" @@ -1243,19 +1233,6 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -1687,11 +1664,6 @@ co@^4.6.0: resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -1766,11 +1738,6 @@ confusing-browser-globals@^1.0.9: resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz#72bc13b483c0276801681871d4898516f8f54fdd" integrity sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw== -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - contains-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" @@ -1864,13 +1831,6 @@ debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: dependencies: ms "2.0.0" -debug@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" @@ -1888,11 +1848,6 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -1932,16 +1887,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - detect-newline@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" @@ -2500,13 +2445,6 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-minipass@^1.2.5: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" - integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== - dependencies: - minipass "^2.6.0" - fs-readdir-recursive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" @@ -2535,20 +2473,6 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - gensync@^1.0.0-beta.1: version "1.0.0-beta.1" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" @@ -2660,11 +2584,6 @@ has-symbols@^1.0.0, has-symbols@^1.0.1: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -2762,7 +2681,7 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: +iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -2774,13 +2693,6 @@ ieee754@^1.1.4: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== -ignore-walk@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" - integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== - dependencies: - minimatch "^3.0.4" - ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -2825,11 +2737,6 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== - inquirer@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.1.0.tgz#1298a01859883e17c7264b82870ae1034f92dd29" @@ -2953,13 +2860,6 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" @@ -3784,21 +3684,6 @@ minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" - integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minizlib@^1.2.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" - integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== - dependencies: - minipass "^2.9.0" - mixin-deep@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" @@ -3812,7 +3697,7 @@ mkdirp-classic@^0.5.2: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz#54c441ce4c96cd7790e10b41a87aa51068ecab2b" integrity sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g== -mkdirp@^0.5.0, mkdirp@^0.5.1: +mkdirp@^0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -3861,15 +3746,6 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -needle@^2.2.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.1.tgz#14af48732463d7475696f937626b1b993247a56a" - integrity sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -3914,35 +3790,11 @@ node-notifier@^5.4.2: shellwords "^0.1.1" which "^1.3.0" -node-pre-gyp@*: - version "0.14.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83" - integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4.4.2" - node-releases@^1.1.53: version "1.1.53" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4" integrity sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ== -nopt@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" - integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== - dependencies: - abbrev "1" - osenv "^0.1.4" - normalize-package-data@^2.3.2: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -3965,27 +3817,6 @@ normalize-path@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -npm-bundled@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" - integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== - dependencies: - npm-normalize-package-bin "^1.0.1" - -npm-normalize-package-bin@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" - integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== - -npm-packlist@^1.1.6: - version "1.4.8" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" - integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - npm-normalize-package-bin "^1.0.1" - npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -3993,21 +3824,6 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - nwsapi@^2.0.7: version "2.2.0" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" @@ -4018,11 +3834,6 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - object-copy@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" @@ -4120,24 +3931,11 @@ optionator@^0.8.1, optionator@^0.8.3: type-check "~0.3.2" word-wrap "~1.2.3" -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: +os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - p-each-series@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71" @@ -4420,16 +4218,6 @@ randomstring@^1.1.5: dependencies: array-uniq "1.0.2" -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - react-is@^16.8.4: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -4469,7 +4257,7 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" -readable-stream@^2.0.2, readable-stream@^2.0.6: +readable-stream@^2.0.2: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -4695,7 +4483,7 @@ rimraf@2.6.3: dependencies: glob "^7.1.3" -rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3: +rimraf@^2.5.4, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -4778,7 +4566,7 @@ secp256k1@^4.0.1: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0: +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -4793,7 +4581,7 @@ semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -set-blocking@^2.0.0, set-blocking@~2.0.0: +set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= @@ -5025,23 +4813,6 @@ string-length@^2.0.0: astral-regex "^1.0.0" strip-ansi "^4.0.0" -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - string-width@^3.0.0, string-width@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" @@ -5108,13 +4879,6 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" @@ -5151,11 +4915,6 @@ strip-json-comments@^3.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -5213,19 +4972,6 @@ tar-stream@^2.0.0: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^4.4.2: - version "4.4.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" - integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.8.6" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" - test-exclude@^5.2.3: version "5.2.3" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" @@ -5534,13 +5280,6 @@ which@^1.2.9, which@^1.3.0: dependencies: isexe "^2.0.0" -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" @@ -5600,11 +5339,6 @@ y18n@^4.0.0: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== -yallist@^3.0.0, yallist@^3.0.3: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - yargs-parser@^13.1.2: version "13.1.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" From 3a31913d147dcb48a144a78a0f3bc38d77ad96d8 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sun, 3 May 2020 15:31:25 +0200 Subject: [PATCH 66/78] add latest zxlib --- README.md | 22 +++++++++++++++++++++- deps/ledger-zxlib/dockerized_build.mk | 12 ++++++++---- deps/ledger-zxlib/include/zxversion.h | 2 +- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b1de3492..78b7d8ed 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![CircleCI](https://circleci.com/gh/Zondax/ledger-cosmos.svg?style=shield)](https://circleci.com/gh/Zondax/ledger-cosmos) [![CodeFactor](https://www.codefactor.io/repository/github/zondax/ledger-cosmos/badge)](https://www.codefactor.io/repository/github/zondax/ledger-cosmos) -This repository contains: +This project contains the Cosmos app for Ledger Nano S and X. - Ledger Nano S/X Cosmos app - Specs / Documentation @@ -36,6 +36,26 @@ Tip: - In releases, you will find a precompiled test app. If you are just curious, you can run `zxtool.sh` and avoid building. +## Download and install a prerelease + +*Once the app is approved by Ledger, it will be available in their app store (Ledger Live). +You can get builds generated by CircleCI from the release tab. THESE ARE UNVETTED DEVELOPMENT RELEASES* + +Download a release from here (https://github.com/Zondax/ledger-cosmos/releases). You only need `zxtool.sh` + +If the file is not executable, run +```sh +chmod +x ./zxtool.sh +``` + +then run: + +```sh +./zxtool.sh load +``` + +# Development + ## Preconditions - Be sure you checkout submodules too: diff --git a/deps/ledger-zxlib/dockerized_build.mk b/deps/ledger-zxlib/dockerized_build.mk index 793e194b..24bc057c 100644 --- a/deps/ledger-zxlib/dockerized_build.mk +++ b/deps/ledger-zxlib/dockerized_build.mk @@ -16,8 +16,9 @@ .PHONY: all deps build clean load delete check_python show_info_recovery_mode -TESTS_ZEMU_DIR ?= $(CURDIR)/tests_zemu -TESTS_ZEMU_JS_PACKAGE ?= +TESTS_ZEMU_DIR?=$(CURDIR)/tests_zemu +TESTS_ZEMU_JS_PACKAGE?= +TESTS_ZEMU_JS_DIR?= LEDGER_SRC=$(CURDIR)/app DOCKER_APP_SRC=/project @@ -31,7 +32,10 @@ SCP_PRIVKEY=ff701d781f43ce106f72dc26a46b6a83e053b5d07bb3d4ceab79c91ca822a66b INTERACTIVE:=$(shell [ -t 0 ] && echo 1) USERID:=$(shell id -u) -$(info USERID: $(USERID)) +$(info USERID : $(USERID)) +$(info TESTS_ZEMU_DIR : $(TESTS_ZEMU_DIR)) +$(info TESTS_ZEMU_JS_DIR : $(TESTS_ZEMU_JS_DIR)) +$(info TESTS_ZEMU_JS_PACKAGE : $(TESTS_ZEMU_JS_PACKAGE)) ifeq ($(USERID),1001) # TODO: Use podman inside circleci machines? @@ -148,7 +152,7 @@ dev_ca_delete2: check_python ########################## ZEMU Section ############################### .PHONY: zemu_install_js_link -ifeq ($(TESTS_ZEMU_JS_PACKAGE),) +ifeq ($(TESTS_ZEMU_JS_DIR),) zemu_install_js_link: @echo "No local package defined" else diff --git a/deps/ledger-zxlib/include/zxversion.h b/deps/ledger-zxlib/include/zxversion.h index 8c23084f..a1b97cdb 100644 --- a/deps/ledger-zxlib/include/zxversion.h +++ b/deps/ledger-zxlib/include/zxversion.h @@ -17,4 +17,4 @@ #define ZXLIB_MAJOR 1 #define ZXLIB_MINOR 0 -#define ZXLIB_PATCH 1 +#define ZXLIB_PATCH 2 From c29214f32a653a10ee8ce3f9caec99340b36f5f8 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sun, 3 May 2020 16:04:39 +0200 Subject: [PATCH 67/78] disable autopublishing --- .circleci/config.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e022664d..339941aa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -89,12 +89,12 @@ workflows: - build - build_ledger - test_zemu - - build_package: - requires: - - build - - build_ledger - - test_zemu - filters: - branches: - only: - - master +# - build_package: +# requires: +# - build +# - build_ledger +# - test_zemu +# filters: +# branches: +# only: +# - master From 4083d58d1db51dce5478b9dde74a727ddbe1e567 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Mon, 4 May 2020 13:39:47 +0200 Subject: [PATCH 68/78] Fix X compilation issue --- deps/ledger-zxlib/app/common/view_x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/ledger-zxlib/app/common/view_x.c b/deps/ledger-zxlib/app/common/view_x.c index a863333a..43b1bedd 100644 --- a/deps/ledger-zxlib/app/common/view_x.c +++ b/deps/ledger-zxlib/app/common/view_x.c @@ -106,7 +106,7 @@ void h_review_loop_start() { if (flow_inside_loop) { // coming from right h_paging_decrease(); - if (viewdata.idx<0) { + if (viewdata.itemIdx<0) { // exit to the left flow_inside_loop = 0; ux_flow_prev(); From fd2ba8bc5b689126d47a6835566166694a7b5884 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Wed, 3 Jun 2020 15:09:45 +0200 Subject: [PATCH 69/78] Upgrade to Upgrade to v2.13.0 (#2) * Improved UI/UX (expert mode) * updating unit tests * add test case (show path and sign) --- CMakeLists.txt | 15 +- app/Makefile | 8 +- app/script.ld | 2 +- app/src/apdu_handler.c | 131 +++ app/src/coin.h | 17 +- app/src/common/actions.c | 36 +- app/src/common/actions.h | 35 +- app/src/common/app_main.c | 111 +-- app/src/common/app_main.h | 34 +- app/src/common/app_mode.c | 73 ++ app/src/common/app_mode.h | 37 + app/src/common/parser.h | 2 +- app/src/common/parser_common.h | 2 +- app/src/common/tx.c | 8 +- app/src/common/tx.h | 10 +- app/src/parser.c | 45 +- app/src/parser_impl.c | 1 + app/src/parser_txdef.h | 8 +- app/src/tx_display.c | 293 ++++-- app/src/tx_display.h | 20 +- app/src/tx_parser.c | 45 +- app/src/tx_parser.h | 10 +- deps/ledger-zxlib/app/common/view.c | 35 +- deps/ledger-zxlib/app/common/view.h | 2 +- deps/ledger-zxlib/app/common/view_internal.h | 2 +- deps/ledger-zxlib/app/common/view_s.c | 20 +- deps/ledger-zxlib/app/common/view_x.c | 12 +- deps/ledger-zxlib/dockerized_build.mk | 10 +- deps/ledger-zxlib/include/zxmacros.h | 10 + deps/ledger-zxlib/include/zxversion.h | 4 +- deps/ledger-zxlib/src/bignum.c | 4 +- tests/testcases/manual.json | 905 ++++++++++++++---- tests/tx_parse.cpp | 6 +- tests/ui_output.cpp | 3 + tests/util/common.cpp | 2 +- tests/util/testcases.cpp | 4 + tests/util/testcases.h | 1 + .../show-address-and-sign-basic/.gitkeep | 1 + tests_zemu/snapshots-tmp/sign-expert/.gitkeep | 1 + .../show-address-and-sign-basic/0.png | Bin 0 -> 824 bytes .../show-address-and-sign-basic/1.png | Bin 0 -> 847 bytes .../show-address-and-sign-basic/10.png | Bin 0 -> 733 bytes .../show-address-and-sign-basic/11.png | Bin 0 -> 499 bytes .../show-address-and-sign-basic/12.png | Bin 0 -> 400 bytes .../13.png} | Bin .../14.png} | Bin .../show-address-and-sign-basic/2.png | Bin 0 -> 771 bytes .../show-address-and-sign-basic/3.png | Bin 0 -> 569 bytes .../show-address-and-sign-basic/4.png | Bin 0 -> 303 bytes .../show-address-and-sign-basic/5.png | Bin 0 -> 495 bytes .../show-address-and-sign-basic/6.png | Bin 0 -> 599 bytes .../show-address-and-sign-basic/7.png | Bin 0 -> 1033 bytes .../show-address-and-sign-basic/8.png | Bin 0 -> 748 bytes .../show-address-and-sign-basic/9.png | Bin 0 -> 1011 bytes tests_zemu/snapshots/show-address-huge/7.png | Bin 521 -> 495 bytes tests_zemu/snapshots/show-address/5.png | Bin 521 -> 495 bytes tests_zemu/snapshots/sign-basic/0.png | Bin 461 -> 599 bytes tests_zemu/snapshots/sign-basic/1.png | Bin 361 -> 1033 bytes tests_zemu/snapshots/sign-basic/10.png | Bin 1040 -> 0 bytes tests_zemu/snapshots/sign-basic/11.png | Bin 567 -> 0 bytes tests_zemu/snapshots/sign-basic/12.png | Bin 964 -> 0 bytes tests_zemu/snapshots/sign-basic/13.png | Bin 687 -> 0 bytes tests_zemu/snapshots/sign-basic/2.png | Bin 383 -> 748 bytes tests_zemu/snapshots/sign-basic/3.png | Bin 380 -> 1011 bytes tests_zemu/snapshots/sign-basic/4.png | Bin 355 -> 733 bytes tests_zemu/snapshots/sign-basic/5.png | Bin 518 -> 499 bytes tests_zemu/snapshots/sign-basic/6.png | Bin 1040 -> 400 bytes tests_zemu/snapshots/sign-basic/7.png | Bin 567 -> 644 bytes tests_zemu/snapshots/sign-basic/8.png | Bin 986 -> 713 bytes tests_zemu/snapshots/sign-basic/9.png | Bin 701 -> 0 bytes tests_zemu/snapshots/sign-expert/0.png | Bin 0 -> 509 bytes tests_zemu/snapshots/sign-expert/1.png | Bin 0 -> 409 bytes tests_zemu/snapshots/sign-expert/10.png | Bin 0 -> 1011 bytes tests_zemu/snapshots/sign-expert/11.png | Bin 0 -> 733 bytes tests_zemu/snapshots/sign-expert/12.png | Bin 0 -> 426 bytes tests_zemu/snapshots/sign-expert/13.png | Bin 0 -> 400 bytes tests_zemu/snapshots/sign-expert/14.png | Bin 0 -> 644 bytes tests_zemu/snapshots/sign-expert/15.png | Bin 0 -> 713 bytes tests_zemu/snapshots/sign-expert/2.png | Bin 0 -> 432 bytes tests_zemu/snapshots/sign-expert/3.png | Bin 0 -> 599 bytes tests_zemu/snapshots/sign-expert/4.png | Bin 0 -> 1090 bytes tests_zemu/snapshots/sign-expert/5.png | Bin 0 -> 617 bytes tests_zemu/snapshots/sign-expert/6.png | Bin 0 -> 1033 bytes tests_zemu/snapshots/sign-expert/7.png | Bin 0 -> 748 bytes tests_zemu/snapshots/sign-expert/8.png | Bin 0 -> 1090 bytes tests_zemu/snapshots/sign-expert/9.png | Bin 0 -> 617 bytes tests_zemu/tests/test.js | 169 +++- tests_zemu/tools/debug.mjs | 31 +- 88 files changed, 1664 insertions(+), 501 deletions(-) create mode 100644 app/src/apdu_handler.c create mode 100644 app/src/common/app_mode.c create mode 100644 app/src/common/app_mode.h create mode 100644 tests_zemu/snapshots-tmp/show-address-and-sign-basic/.gitkeep create mode 100644 tests_zemu/snapshots-tmp/sign-expert/.gitkeep create mode 100644 tests_zemu/snapshots/show-address-and-sign-basic/0.png create mode 100644 tests_zemu/snapshots/show-address-and-sign-basic/1.png create mode 100644 tests_zemu/snapshots/show-address-and-sign-basic/10.png create mode 100644 tests_zemu/snapshots/show-address-and-sign-basic/11.png create mode 100644 tests_zemu/snapshots/show-address-and-sign-basic/12.png rename tests_zemu/snapshots/{sign-basic/14.png => show-address-and-sign-basic/13.png} (100%) rename tests_zemu/snapshots/{sign-basic/15.png => show-address-and-sign-basic/14.png} (100%) create mode 100644 tests_zemu/snapshots/show-address-and-sign-basic/2.png create mode 100644 tests_zemu/snapshots/show-address-and-sign-basic/3.png create mode 100644 tests_zemu/snapshots/show-address-and-sign-basic/4.png create mode 100644 tests_zemu/snapshots/show-address-and-sign-basic/5.png create mode 100644 tests_zemu/snapshots/show-address-and-sign-basic/6.png create mode 100644 tests_zemu/snapshots/show-address-and-sign-basic/7.png create mode 100644 tests_zemu/snapshots/show-address-and-sign-basic/8.png create mode 100644 tests_zemu/snapshots/show-address-and-sign-basic/9.png delete mode 100644 tests_zemu/snapshots/sign-basic/10.png delete mode 100644 tests_zemu/snapshots/sign-basic/11.png delete mode 100644 tests_zemu/snapshots/sign-basic/12.png delete mode 100644 tests_zemu/snapshots/sign-basic/13.png delete mode 100644 tests_zemu/snapshots/sign-basic/9.png create mode 100644 tests_zemu/snapshots/sign-expert/0.png create mode 100644 tests_zemu/snapshots/sign-expert/1.png create mode 100644 tests_zemu/snapshots/sign-expert/10.png create mode 100644 tests_zemu/snapshots/sign-expert/11.png create mode 100644 tests_zemu/snapshots/sign-expert/12.png create mode 100644 tests_zemu/snapshots/sign-expert/13.png create mode 100644 tests_zemu/snapshots/sign-expert/14.png create mode 100644 tests_zemu/snapshots/sign-expert/15.png create mode 100644 tests_zemu/snapshots/sign-expert/2.png create mode 100644 tests_zemu/snapshots/sign-expert/3.png create mode 100644 tests_zemu/snapshots/sign-expert/4.png create mode 100644 tests_zemu/snapshots/sign-expert/5.png create mode 100644 tests_zemu/snapshots/sign-expert/6.png create mode 100644 tests_zemu/snapshots/sign-expert/7.png create mode 100644 tests_zemu/snapshots/sign-expert/8.png create mode 100644 tests_zemu/snapshots/sign-expert/9.png diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c474d24..92c46bc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,7 @@ file(GLOB_RECURSE LIB_SRC app/src/tx_validate.c app/src/parser.c app/src/parser_impl.c + app/src/common/app_mode.c ) add_library(app_lib STATIC @@ -89,17 +90,3 @@ target_include_directories(fuzzing_stub PUBLIC deps/jsmn/src ) target_link_libraries(fuzzing_stub app_lib) - -############################################################### -# Force tests to depend from app compiling -############################################################### - -#set(DISABLE_DOCKER_BUILDS OFF CACHE BOOL "Disables Docker Builds") -# -#if (NOT DISABLE_DOCKER_BUILDS) -# add_custom_target(ledger_app -# COMMAND make build -# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} -# ) -# add_dependencies(unittests ledger_app) -#endif () diff --git a/app/Makefile b/app/Makefile index 51e2856b..68317abb 100755 --- a/app/Makefile +++ b/app/Makefile @@ -27,7 +27,7 @@ include $(BOLOS_SDK)/Makefile.defines # Main app configuration APPNAME = "Cosmos" APPVERSION_M=2 -APPVERSION_N=12 +APPVERSION_N=13 APPVERSION_P=0 APPPATH = "44'/118'" @@ -127,7 +127,11 @@ endif ######################### CC := $(CLANGPATH)clang -CFLAGS += -O3 -Os -Wno-unknown-pragmas +#CFLAGS += -Werror +CFLAGS += -O3 -Os -Wpedantic -Wall -Wno-unknown-pragmas -Wno-c11-extensions +# To accept Ledger SDK issues +CFLAGS += -Wno-missing-declarations -Wno-empty-translation-unit -Wno-language-extension-token +CFLAGS += -Wno-embedded-directive -Wno-pointer-arith AS := $(GCCPATH)arm-none-eabi-gcc AFLAGS += diff --git a/app/script.ld b/app/script.ld index 0f71a9c4..b7e4b071 100644 --- a/app/script.ld +++ b/app/script.ld @@ -30,7 +30,7 @@ MEMORY } PAGE_SIZE = 64; -STACK_SIZE = 1104; +STACK_SIZE = 1212; END_STACK = ORIGIN(SRAM) + LENGTH(SRAM); SECTIONS diff --git a/app/src/apdu_handler.c b/app/src/apdu_handler.c new file mode 100644 index 00000000..67dd86ce --- /dev/null +++ b/app/src/apdu_handler.c @@ -0,0 +1,131 @@ +/******************************************************************************* +* (c) 2018, 2019 Zondax GmbH +* (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#include "app_main.h" +#include "app_mode.h" + +#include +#include +#include + +#include "view.h" +#include "actions.h" +#include "tx.h" +#include "crypto.h" +#include "coin.h" +#include "zxmacros.h" +#include "parser_impl.h" + +__Z_INLINE void handleGetAddrSecp256K1(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { + uint8_t len = extractHRP(rx, OFFSET_DATA); + extractHDPath(rx, OFFSET_DATA + 1 + len); + + uint8_t requireConfirmation = G_io_apdu_buffer[OFFSET_P1]; + + if (requireConfirmation) { + app_fill_address(addr_secp256k1); + view_address_show(addr_secp256k1); + *flags |= IO_ASYNCH_REPLY; + return; + } + + *tx = app_fill_address(addr_secp256k1); + THROW(APDU_CODE_OK); +} + +__Z_INLINE void handleSignSecp256K1(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { + if (!process_chunk(tx, rx)) { + THROW(APDU_CODE_OK); + } + + // Put address in output buffer, we will use it to confirm source address + app_fill_address(addr_secp256k1); + parser_tx_obj.own_addr = (const char *)(G_io_apdu_buffer + VIEW_ADDRESS_OFFSET_SECP256K1); + + const char *error_msg = tx_parse(); + + 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); + } + + view_sign_show(); + *flags |= IO_ASYNCH_REPLY; +} + +void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { + uint16_t sw = 0; + + BEGIN_TRY + { + TRY + { + if (G_io_apdu_buffer[OFFSET_CLA] != CLA) { + THROW(APDU_CODE_CLA_NOT_SUPPORTED); + } + + if (rx < APDU_MIN_LENGTH) { + THROW(APDU_CODE_WRONG_LENGTH); + } + + switch (G_io_apdu_buffer[OFFSET_INS]) { + case INS_GET_VERSION: { + handle_getversion(flags, tx, rx); + break; + } + + case INS_GET_ADDR_SECP256K1: { + handleGetAddrSecp256K1(flags, tx, rx); + break; + } + + case INS_SIGN_SECP256K1: { + handleSignSecp256K1(flags, tx, rx); + break; + } + + default: + THROW(APDU_CODE_INS_NOT_SUPPORTED); + } + } + CATCH(EXCEPTION_IO_RESET) + { + THROW(EXCEPTION_IO_RESET); + } + CATCH_OTHER(e) + { + switch (e & 0xF000) { + case 0x6000: + case APDU_CODE_OK: + sw = e; + break; + default: + sw = 0x6800 | (e & 0x7FF); + break; + } + G_io_apdu_buffer[*tx] = sw >> 8; + G_io_apdu_buffer[*tx + 1] = sw; + *tx += 2; + } + FINALLY + { + } + } + END_TRY; +} diff --git a/app/src/coin.h b/app/src/coin.h index a24ccd80..54727608 100644 --- a/app/src/coin.h +++ b/app/src/coin.h @@ -22,6 +22,8 @@ extern "C" { #include #include +#define CLA 0x55 + #define HDPATH_LEN_DEFAULT 5 #define HDPATH_0_DEFAULT (0x80000000u | 0x2cu) @@ -40,9 +42,18 @@ typedef enum { #define VIEW_ADDRESS_ITEM_COUNT 2 #define VIEW_ADDRESS_LAST_PAGE_DEFAULT 0 -#define MENU_MAIN_APP_LINE1 "Cosmos" -#define MENU_MAIN_APP_LINE2 "App" -#define APPVERSION_LINE2 "" +#define MENU_MAIN_APP_LINE1 "Cosmos" +#define MENU_MAIN_APP_LINE2 "Hub" +#define APPVERSION_LINE1 "Version:" +#define APPVERSION_LINE2 ("v" APPVERSION) + +#define CRYPTO_BLOB_SKIP_BYTES 0 +#define COIN_DEFAULT_CHAINID "cosmoshub-3" + +// In non-expert mode, the app will convert from uatom to ATOM +#define COIN_DEFAULT_DENOM_BASE "uatom" +#define COIN_DEFAULT_DENOM_REPR "ATOM" +#define COIN_DEFAULT_DENOM_FACTOR 6 #ifdef __cplusplus } diff --git a/app/src/common/actions.c b/app/src/common/actions.c index 3e682ee1..ee8e1983 100644 --- a/app/src/common/actions.c +++ b/app/src/common/actions.c @@ -22,32 +22,24 @@ #include #include "coin.h" -uint8_t app_sign() { - uint8_t *signature = G_io_apdu_buffer; +uint8_t action_addr_len; - const uint8_t *message = tx_get_buffer(); - const uint16_t messageLength = tx_get_buffer_length(); +void app_sign() { + uint8_t *signature = G_io_apdu_buffer; - return crypto_sign(signature, IO_APDU_BUFFER_SIZE - 2, message, messageLength); + const uint8_t *message = tx_get_buffer() + CRYPTO_BLOB_SKIP_BYTES; + const uint16_t messageLength = tx_get_buffer_length() - CRYPTO_BLOB_SKIP_BYTES; + + const uint8_t replyLen = crypto_sign(signature, IO_APDU_BUFFER_SIZE - 3, message, messageLength); + if (replyLen > 0) { + set_code(G_io_apdu_buffer, replyLen, APDU_CODE_OK); + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, replyLen + 2); + } else { + set_code(G_io_apdu_buffer, 0, APDU_CODE_SIGN_VERIFY_ERROR); + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); + } } void app_set_hrp(char *p) { crypto_set_hrp(p); } - -uint8_t app_fill_address() { - // Put data directly in the apdu buffer - MEMZERO(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE); - return crypto_fillAddress(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 2); -} - -void app_reply_address() { - const uint8_t replyLen = app_fill_address(); - set_code(G_io_apdu_buffer, replyLen, APDU_CODE_OK); - io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, replyLen + 2); -} - -void app_reply_error() { - set_code(G_io_apdu_buffer, 0, APDU_CODE_DATA_INVALID); - io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); -} diff --git a/app/src/common/actions.h b/app/src/common/actions.h index 70522459..c57c79ec 100644 --- a/app/src/common/actions.h +++ b/app/src/common/actions.h @@ -16,13 +16,40 @@ #pragma once #include +#include "crypto.h" +#include "tx.h" +#include "apdu_codes.h" +#include +#include "coin.h" -uint8_t app_sign(); +void app_sign(); void app_set_hrp(char *p); -uint8_t app_fill_address(); +extern uint8_t action_addr_len; -void app_reply_address(); +__Z_INLINE uint8_t app_fill_address(address_kind_e kind) { + // Put data directly in the apdu buffer + MEMZERO(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE); -void app_reply_error(); + switch (kind) { + case addr_secp256k1: + action_addr_len = crypto_fillAddress(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 2); + break; + default: + action_addr_len = 0; + break; + } + + return action_addr_len; +} + +__Z_INLINE void app_reply_address(address_kind_e kind) { + set_code(G_io_apdu_buffer, action_addr_len, APDU_CODE_OK); + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, action_addr_len + 2); +} + +__Z_INLINE void app_reply_error() { + set_code(G_io_apdu_buffer, 0, APDU_CODE_DATA_INVALID); + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); +} diff --git a/app/src/common/app_main.c b/app/src/common/app_main.c index 469b7355..fb6c0b42 100644 --- a/app/src/common/app_main.c +++ b/app/src/common/app_main.c @@ -16,6 +16,7 @@ ********************************************************************************/ #include "app_main.h" +#include "app_mode.h" #include #include @@ -140,109 +141,6 @@ bool process_chunk(volatile uint32_t *tx, uint32_t rx) { THROW(APDU_CODE_INVALIDP1P2); } -void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { - uint16_t sw = 0; - - BEGIN_TRY - { - TRY - { - if (G_io_apdu_buffer[OFFSET_CLA] != CLA) { - THROW(APDU_CODE_CLA_NOT_SUPPORTED); - } - - if (rx < APDU_MIN_LENGTH) { - THROW(APDU_CODE_WRONG_LENGTH); - } - - switch (G_io_apdu_buffer[OFFSET_INS]) { - case INS_GET_VERSION: { -#ifdef TESTING_ENABLED - G_io_apdu_buffer[0] = 0xFF; -#else - G_io_apdu_buffer[0] = 0; -#endif - G_io_apdu_buffer[1] = LEDGER_MAJOR_VERSION; - G_io_apdu_buffer[2] = LEDGER_MINOR_VERSION; - G_io_apdu_buffer[3] = LEDGER_PATCH_VERSION; - G_io_apdu_buffer[4] = !IS_UX_ALLOWED; - - G_io_apdu_buffer[5] = (TARGET_ID >> 24) & 0xFF; - G_io_apdu_buffer[6] = (TARGET_ID >> 16) & 0xFF; - G_io_apdu_buffer[7] = (TARGET_ID >> 8) & 0xFF; - G_io_apdu_buffer[8] = (TARGET_ID >> 0) & 0xFF; - - *tx += 9; - THROW(APDU_CODE_OK); - break; - } - - case INS_GET_ADDR_SECP256K1: { - uint8_t len = extractHRP(rx, OFFSET_DATA); - extractHDPath(rx, OFFSET_DATA + 1 + len); - - uint8_t requireConfirmation = G_io_apdu_buffer[OFFSET_P1]; - - if (requireConfirmation) { - app_fill_address(); - view_address_show(addr_secp256k1); - *flags |= IO_ASYNCH_REPLY; - break; - } - - *tx = app_fill_address(); - THROW(APDU_CODE_OK); - break; - } - - case INS_SIGN_SECP256K1: { - if (!process_chunk(tx, rx)) - THROW(APDU_CODE_OK); - - const char *error_msg = tx_parse(); - - 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); - } - - view_sign_show(); - *flags |= IO_ASYNCH_REPLY; - break; - } - - default: - THROW(APDU_CODE_INS_NOT_SUPPORTED); - } - } - CATCH(EXCEPTION_IO_RESET) - { - THROW(EXCEPTION_IO_RESET); - } - CATCH_OTHER(e) - { - switch (e & 0xF000) { - case 0x6000: - case APDU_CODE_OK: - sw = e; - break; - default: - sw = 0x6800 | (e & 0x7FF); - break; - } - G_io_apdu_buffer[*tx] = sw >> 8; - G_io_apdu_buffer[*tx + 1] = sw; - *tx += 2; - } - FINALLY - { - } - } - END_TRY; -} - void handle_generic_apdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { if (rx > 4 && os_memcmp(G_io_apdu_buffer, "\xE0\x01\x00\x00", 4) == 0) { // Respond to get device info command @@ -272,6 +170,12 @@ void app_init() { io_seproxyhal_init(); USB_power(0); USB_power(1); + + app_mode_reset(); + if (app_mode_expert()) { + view_idle_show(1); + } + view_idle_show(0); } @@ -290,6 +194,7 @@ void app_main() { { rx = tx; tx = 0; + rx = io_exchange(CHANNEL_APDU | flags, rx); flags = 0; CHECK_APP_CANARY() diff --git a/app/src/common/app_main.h b/app/src/common/app_main.h index c4facba9..c645535d 100644 --- a/app/src/common/app_main.h +++ b/app/src/common/app_main.h @@ -19,8 +19,6 @@ #include #include "apdu_codes.h" -#define CLA 0x55 - #define OFFSET_CLA 0 #define OFFSET_INS 1 //< Instruction offset #define OFFSET_P1 2 //< P1 @@ -32,10 +30,36 @@ #define OFFSET_PAYLOAD_TYPE OFFSET_P1 -#define INS_GET_VERSION 0 -#define INS_SIGN_SECP256K1 2 -#define INS_GET_ADDR_SECP256K1 4 +#define INS_GET_VERSION 0x00 +#define INS_SIGN_SECP256K1 0x02 +#define INS_GET_ADDR_SECP256K1 0x04 void app_init(); void app_main(); + +void extractHDPath(uint32_t rx, uint32_t offset); + +bool process_chunk(volatile uint32_t *tx, uint32_t rx); + +void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx); + +__Z_INLINE void handle_getversion(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { +#ifdef DEBUG + G_io_apdu_buffer[0] = 0xFF; +#else + G_io_apdu_buffer[0] = 0; +#endif + G_io_apdu_buffer[1] = LEDGER_MAJOR_VERSION; + G_io_apdu_buffer[2] = LEDGER_MINOR_VERSION; + G_io_apdu_buffer[3] = LEDGER_PATCH_VERSION; + G_io_apdu_buffer[4] = !IS_UX_ALLOWED; + + G_io_apdu_buffer[5] = (TARGET_ID >> 24) & 0xFF; + G_io_apdu_buffer[6] = (TARGET_ID >> 16) & 0xFF; + G_io_apdu_buffer[7] = (TARGET_ID >> 8) & 0xFF; + G_io_apdu_buffer[8] = (TARGET_ID >> 0) & 0xFF; + + *tx += 9; + THROW(APDU_CODE_OK); +} diff --git a/app/src/common/app_mode.c b/app/src/common/app_mode.c new file mode 100644 index 00000000..cb37b5ba --- /dev/null +++ b/app/src/common/app_mode.c @@ -0,0 +1,73 @@ +/******************************************************************************* +* (c) 2020 Zondax GmbH +* +* 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 "app_mode.h" + +#if defined(TARGET_NANOS) || defined(TARGET_NANOX) +////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////// + +//NV_CONST app_mode_t N_appmode NV_ALIGN; +//#define N_APPMODE_PTR ((NV_VOL app_mode_t *)PIC(&N_appmode)) +app_mode_t app_mode; + +void app_mode_reset(){ + app_mode.expert = 0; +} + +bool app_mode_expert() { +// TODO: read from NVRAM +// app_mode_t *p = N_APPMODE_PTR; +// uint8_t expert = p->expert; +// return expert; +// app_mode_t* p =(NV_VOL app_mode_t *)PIC(&N_appmode_impl); +// return p->expert; + return app_mode.expert; +} + +void app_mode_set_expert(uint8_t val) { +// TODO: write to NVRAM + app_mode.expert = val; +} + +#else +////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////// + +app_mode_t app_mode; + +void app_mode_reset() { + app_mode.expert = 0; +} + +bool app_mode_expert() { + return app_mode.expert; +} + +void app_mode_set_expert(uint8_t val) { + app_mode.expert = val; +} + +////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////// + +#endif diff --git a/app/src/common/app_mode.h b/app/src/common/app_mode.h new file mode 100644 index 00000000..311aef96 --- /dev/null +++ b/app/src/common/app_mode.h @@ -0,0 +1,37 @@ +/******************************************************************************* +* (c) 2016 Ledger +* (c) 2018 Zondax GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +#pragma once +#include "zxmacros.h" +#include "stdbool.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint32_t expert; +} app_mode_t; + +void app_mode_reset(); + +bool app_mode_expert(); + +void app_mode_set_expert(uint8_t val); + +#ifdef __cplusplus +} +#endif diff --git a/app/src/common/parser.h b/app/src/common/parser.h index ce899851..eb8b9d95 100644 --- a/app/src/common/parser.h +++ b/app/src/common/parser.h @@ -35,7 +35,7 @@ parser_error_t parser_parse(parser_context_t *ctx, parser_error_t parser_validate(const parser_context_t *ctx); //// returns the number of items in the current parsing context -parser_error_t parser_getNumItems(const parser_context_t *ctx, uint16_t *num_items); +parser_error_t parser_getNumItems(const parser_context_t *ctx, uint8_t *num_items); // retrieves a readable output for each field / page parser_error_t parser_getItem(const parser_context_t *ctx, diff --git a/app/src/common/parser_common.h b/app/src/common/parser_common.h index 9039f0b6..ce23e1ac 100644 --- a/app/src/common/parser_common.h +++ b/app/src/common/parser_common.h @@ -34,7 +34,7 @@ typedef enum { parser_init_context_empty, parser_display_idx_out_of_range, parser_display_page_out_of_range, - parser_unexepected_error, + parser_unexpected_error, // Coin generic parser_unexpected_type, parser_unexpected_method, diff --git a/app/src/common/tx.c b/app/src/common/tx.c index 2bcdf1ab..ef567c48 100644 --- a/app/src/common/tx.c +++ b/app/src/common/tx.c @@ -25,7 +25,7 @@ #define RAM_BUFFER_SIZE 8192 #define FLASH_BUFFER_SIZE 16384 #elif defined(TARGET_NANOS) -#define RAM_BUFFER_SIZE 384 +#define RAM_BUFFER_SIZE 256 #define FLASH_BUFFER_SIZE 8192 #endif @@ -93,7 +93,7 @@ const char *tx_parse() { return NULL; } -tx_error_t tx_getNumItems(uint16_t *num_items) { +tx_error_t tx_getNumItems(uint8_t *num_items) { parser_error_t err = parser_getNumItems(&ctx_parsed_tx, num_items); if (err != parser_ok) { @@ -103,13 +103,13 @@ tx_error_t tx_getNumItems(uint16_t *num_items) { return tx_no_error; } -tx_error_t tx_getItem(int8_t displayIdx, +tx_error_t tx_getItem(uint8_t displayIdx, char *outKey, uint16_t outKeyLen, char *outVal, uint16_t outValLen, uint8_t pageIdx, uint8_t *pageCount) { tx_error_t err = tx_no_error; - uint16_t numItems = 0; + uint8_t numItems = 0; err = tx_getNumItems(&numItems); if (err != tx_no_error) { return err; diff --git a/app/src/common/tx.h b/app/src/common/tx.h index 600873e5..d5df157c 100644 --- a/app/src/common/tx.h +++ b/app/src/common/tx.h @@ -49,10 +49,10 @@ uint8_t *tx_get_buffer(); const char *tx_parse(); /// Return the number of items in the transaction -tx_error_t tx_getNumItems(uint16_t *num_items); +tx_error_t tx_getNumItems(uint8_t *num_items); /// Gets an specific item from the transaction (including paging) -tx_error_t tx_getItem(int8_t displayIdx, - char *outKey, uint16_t outKeyLen, - char *outValue, uint16_t outValueLen, - uint8_t pageIdx, uint8_t *pageCount); +tx_error_t tx_getItem(uint8_t displayIdx, + char *outKey, uint16_t outKeyLen, + char *outValue, uint16_t outValueLen, + uint8_t pageIdx, uint8_t *pageCount); diff --git a/app/src/parser.c b/app/src/parser.c index 89bd26a8..9fdda176 100644 --- a/app/src/parser.c +++ b/app/src/parser.c @@ -34,7 +34,7 @@ parser_error_t parser_validate(const parser_context_t *ctx) { CHECK_PARSER_ERR(tx_validate(&parser_tx_obj.json)) // Iterate through all items to check that all can be shown and are valid - uint16_t numItems = 0; + uint8_t numItems = 0; CHECK_PARSER_ERR(parser_getNumItems(ctx, &numItems)); char tmpKey[40]; @@ -48,7 +48,7 @@ parser_error_t parser_validate(const parser_context_t *ctx) { return parser_ok; } -parser_error_t parser_getNumItems(const parser_context_t *ctx, uint16_t *num_items) { +parser_error_t parser_getNumItems(const parser_context_t *ctx, uint8_t *num_items) { *num_items = 0; return tx_display_numItems(num_items); } @@ -93,6 +93,21 @@ __Z_INLINE bool_t parser_isAmount(char *key) { return bool_false; } +__Z_INLINE bool_t is_default_denom_base(const char *denom, uint8_t denom_len) { + if (tx_is_expert_mode()){ + return false; + } + + if (strlen(COIN_DEFAULT_DENOM_BASE) != denom_len) { + return bool_false; + } + + if (memcmp(denom, COIN_DEFAULT_DENOM_BASE, denom_len) == 0) + return bool_true; + + return bool_false; +} + __Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, char *outVal, uint16_t outValLen, uint8_t pageIdx, uint8_t *pageCount) { @@ -150,9 +165,27 @@ __Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, return parser_unexpected_buffer_end; } - MEMCPY(bufferUI, amountPtr, amountLen); - bufferUI[amountLen] = ' '; - MEMCPY(bufferUI + 1 + amountLen, denomPtr, denomLen); + if (is_default_denom_base(denomPtr, denomLen)) { + // Then we convert denomination + char tmp[50]; + if (amountLen < 0 || ((uint16_t) amountLen) >= sizeof(tmp)) { + return parser_unexpected_error; + } + MEMZERO(tmp, sizeof(tmp)); + MEMCPY(tmp, amountPtr, amountLen); + + if (fpstr_to_str(bufferUI, sizeof(tmp), tmp, COIN_DEFAULT_DENOM_FACTOR)!=0) { + return parser_unexpected_error; + } + + const uint16_t formatted_len =strlen(bufferUI); + bufferUI[formatted_len] = ' '; + MEMCPY(bufferUI + 1 + formatted_len, COIN_DEFAULT_DENOM_REPR, strlen(COIN_DEFAULT_DENOM_REPR)); + } else { + MEMCPY(bufferUI, amountPtr, amountLen); + bufferUI[amountLen] = ' '; + MEMCPY(bufferUI + 1 + amountLen, denomPtr, denomLen); + } pageString(outVal, outValLen, bufferUI, pageIdx, pageCount); @@ -169,7 +202,7 @@ parser_error_t parser_getItem(const parser_context_t *ctx, MEMZERO(outKey, outKeyLen); MEMZERO(outVal, outValLen); - uint16_t numItems; + uint8_t numItems; CHECK_PARSER_ERR(parser_getNumItems(ctx, &numItems)) CHECK_APP_CANARY() diff --git a/app/src/parser_impl.c b/app/src/parser_impl.c index 7ac3d36b..641e2e8c 100644 --- a/app/src/parser_impl.c +++ b/app/src/parser_impl.c @@ -117,6 +117,7 @@ parser_error_t _readTx(parser_context_t *c, parser_tx_t *v) { parser_tx_obj.tx = (const char *) c->buffer; parser_tx_obj.flags.cache_valid = 0; parser_tx_obj.filter_msg_type_count = 0; + parser_tx_obj.filter_msg_from_count = 0; return parser_ok; } diff --git a/app/src/parser_txdef.h b/app/src/parser_txdef.h index 150f9197..b00aac9f 100644 --- a/app/src/parser_txdef.h +++ b/app/src/parser_txdef.h @@ -57,13 +57,19 @@ typedef struct { struct { unsigned int cache_valid:1; unsigned int msg_type_grouping:1; + unsigned int msg_from_grouping:1; + unsigned int msg_from_grouping_hide_all:1; } flags; // indicates that N identical msg_type fields have been detected - uint8_t filter_msg_type_count; int32_t filter_msg_type_valid_idx; + // indicates that N identical msg_from fields have been detected + uint8_t filter_msg_from_count; + int32_t filter_msg_from_valid_idx; + const char *own_addr; + // current tx query tx_query_t query; } parser_tx_t; diff --git a/app/src/tx_display.c b/app/src/tx_display.c index 9858bb65..4aca498b 100644 --- a/app/src/tx_display.c +++ b/app/src/tx_display.c @@ -14,6 +14,8 @@ * limitations under the License. ********************************************************************************/ +#include "coin.h" +#include "app_mode.h" #include "tx_display.h" #include "tx_parser.h" #include "parser_impl.h" @@ -40,23 +42,36 @@ const char *get_required_root_item(root_item_e i) { } } -static const uint8_t root_max_level[NUM_REQUIRED_ROOT_PAGES] = { - 2, // "chain_id", - 2, // "account_number", - 2, // "sequence", - 1, // "fee", - 2, // "memo" - 2, // "msgs" -}; +__Z_INLINE uint8_t get_root_max_level(root_item_e i) { + switch (i) { + case root_item_chain_id: + return 2; + case root_item_account_number: + return 2; + case root_item_sequence: + return 2; + case root_item_fee: + return 1; + case root_item_memo: + return 2; + case root_item_msgs: + return 2; + default: + return 0; + } +} typedef struct { - uint16_t total_item_count; - - uint8_t root_item_start_valid[NUM_REQUIRED_ROOT_PAGES]; + bool root_item_start_token_valid[NUM_REQUIRED_ROOT_PAGES]; // token where the root_item starts (negative for non-existing) uint16_t root_item_start_token_idx[NUM_REQUIRED_ROOT_PAGES]; + + // total items + uint16_t total_item_count; // number of items the root_item contains uint8_t root_item_number_subitems[NUM_REQUIRED_ROOT_PAGES]; + + uint8_t is_default_chain; } display_cache_t; display_cache_t display_cache; @@ -67,6 +82,47 @@ parser_error_t tx_display_readTx(parser_context_t *ctx, const uint8_t *data, siz return parser_ok; } +__Z_INLINE parser_error_t calculate_is_default_chainid() { + display_cache.is_default_chain = false; + + // get chain_id + char outKey[2]; + char outVal[20]; + uint8_t pageCount; + INIT_QUERY_CONTEXT(outKey, sizeof(outKey), + outVal, sizeof(outVal), + 0, get_root_max_level(root_item_chain_id)) + parser_tx_obj.query.item_index = 0; + parser_tx_obj.query._item_index_current = 0; + + uint16_t ret_value_token_index; + CHECK_PARSER_ERR(tx_traverse_find( + display_cache.root_item_start_token_idx[root_item_chain_id], + &ret_value_token_index)) + + CHECK_PARSER_ERR(tx_getToken( + ret_value_token_index, + outVal, sizeof(outVal), + 0, &pageCount)) + + if (strcmp(outVal, COIN_DEFAULT_CHAINID) != 0) { + // If we don't match the default chainid, switch to expert mode + display_cache.is_default_chain = true; + } + + return parser_ok; +} + +__Z_INLINE bool address_matches_own(char *addr) { + if (parser_tx_obj.own_addr == NULL) { + return false; + } + if (strcmp(parser_tx_obj.own_addr, addr) != 0) { + return false; + } + return true; +} + parser_error_t tx_indexRootFields() { if (parser_tx_obj.flags.cache_valid) { return parser_ok; @@ -74,34 +130,38 @@ parser_error_t tx_indexRootFields() { // Clear cache MEMZERO(&display_cache, sizeof(display_cache_t)); - char tmp_key[20]; - char tmp_val[40]; - char tmp_val_ref[40]; + char tmp_key[70]; + char tmp_val[70]; MEMZERO(&tmp_key, sizeof(tmp_key)); MEMZERO(&tmp_val, sizeof(tmp_val)); - MEMZERO(&tmp_val_ref, sizeof(tmp_val_ref)); - parser_tx_obj.flags.msg_type_grouping = 1; - parser_tx_obj.filter_msg_type_count = 0; + // Grouping references + char reference_msg_type[40]; + MEMZERO(&reference_msg_type, sizeof(reference_msg_type)); + char reference_msg_from[70]; + MEMZERO(&reference_msg_from, sizeof(reference_msg_from)); - for (int8_t root_item_idx = 0; root_item_idx < NUM_REQUIRED_ROOT_PAGES; root_item_idx++) { - const char *req_root_item_key = get_required_root_item((root_item_e) root_item_idx); + parser_tx_obj.filter_msg_type_count = 0; + parser_tx_obj.filter_msg_from_count = 0; + parser_tx_obj.flags.msg_type_grouping = 1; + parser_tx_obj.flags.msg_from_grouping = 1; + for (root_item_e root_item_idx = 0; root_item_idx < NUM_REQUIRED_ROOT_PAGES; root_item_idx++) { uint16_t req_root_item_key_token_idx = 0; parser_error_t err = object_get_value( &parser_tx_obj.json, ROOT_TOKEN_INDEX, - req_root_item_key, + get_required_root_item(root_item_idx), &req_root_item_key_token_idx); - if (err == parser_no_data ) { + if (err == parser_no_data) { continue; } CHECK_PARSER_ERR(err) // Remember root item start token - display_cache.root_item_start_valid[root_item_idx] = 1; + display_cache.root_item_start_token_valid[root_item_idx] = 1; display_cache.root_item_start_token_idx[root_item_idx] = req_root_item_key_token_idx; // Now count how many items can be found in this root item @@ -109,9 +169,11 @@ parser_error_t tx_indexRootFields() { while (err == parser_ok) { INIT_QUERY_CONTEXT(tmp_key, sizeof(tmp_key), tmp_val, sizeof(tmp_val), - 0, root_max_level[root_item_idx]) + 0, get_root_max_level(root_item_idx)) parser_tx_obj.query.item_index = current_item_idx; - strncpy_s(parser_tx_obj.query.out_key, req_root_item_key, parser_tx_obj.query.out_key_len); + strncpy_s(parser_tx_obj.query.out_key, + get_required_root_item(root_item_idx), + parser_tx_obj.query.out_key_len); uint16_t ret_value_token_index; @@ -130,29 +192,51 @@ parser_error_t tx_indexRootFields() { parser_tx_obj.query.out_key_len, 0, &pageCount)) - if (root_item_idx == root_item_memo) { - if (strlen(parser_tx_obj.query.out_val) == 0) { - err = parser_query_no_results; - continue; + switch (root_item_idx) { + case root_item_memo: { + if (strlen(parser_tx_obj.query.out_val) == 0) { + err = parser_query_no_results; + continue; + } + break; } - } - - if (root_item_idx == root_item_msgs && parser_tx_obj.flags.msg_type_grouping == 1u) { - if (strcmp(tmp_key, "msgs/type") == 0) { - if (parser_tx_obj.filter_msg_type_count == 0) { + case root_item_msgs: { + // GROUPING: Message Type + if (parser_tx_obj.flags.msg_type_grouping && is_msg_type_field(tmp_key)) { // First message, initialize expected type - strcpy(tmp_val_ref, tmp_val); - parser_tx_obj.filter_msg_type_valid_idx = current_item_idx; + if (parser_tx_obj.filter_msg_type_count == 0) { + strcpy(reference_msg_type, tmp_val); + parser_tx_obj.filter_msg_type_valid_idx = current_item_idx; + } + + if (strcmp(reference_msg_type, tmp_val) != 0) { + // different values, so disable grouping + parser_tx_obj.flags.msg_type_grouping = 0; + parser_tx_obj.filter_msg_type_count = 0; + } + + parser_tx_obj.filter_msg_type_count++; } - if (strcmp(tmp_val_ref, tmp_val) != 0) { - // different values, so disable grouping - parser_tx_obj.flags.msg_type_grouping = 0; - parser_tx_obj.filter_msg_type_count = 0; + // GROUPING: Message From + if (parser_tx_obj.flags.msg_from_grouping && is_msg_from_field(tmp_key)) { + // First message, initialize expected from + if (parser_tx_obj.filter_msg_from_count == 0) { + strcpy(reference_msg_from, tmp_val); + parser_tx_obj.filter_msg_from_valid_idx = current_item_idx; + } + + if (strcmp(reference_msg_from, tmp_val) != 0) { + // different values, so disable grouping + parser_tx_obj.flags.msg_from_grouping = 0; + parser_tx_obj.filter_msg_from_count = 0; + } + + parser_tx_obj.filter_msg_from_count++; } - - parser_tx_obj.filter_msg_type_count++; } + default: + break; } display_cache.root_item_number_subitems[root_item_idx]++; @@ -168,17 +252,100 @@ parser_error_t tx_indexRootFields() { parser_tx_obj.flags.cache_valid = 1; + CHECK_PARSER_ERR(calculate_is_default_chainid()); + + // turn off grouping if we are not in expert mode + parser_tx_obj.flags.msg_type_grouping = parser_tx_obj.filter_msg_type_count > 0; + parser_tx_obj.flags.msg_from_grouping = !tx_is_expert_mode() && parser_tx_obj.filter_msg_from_count > 0; + + // check if from reference value matches the device address that will be signing + parser_tx_obj.flags.msg_from_grouping_hide_all = 0; + if (address_matches_own(reference_msg_from)){ + parser_tx_obj.flags.msg_from_grouping_hide_all = 1; + } + + return parser_ok; +} + +__Z_INLINE bool is_default_chainid() { + CHECK_PARSER_ERR(tx_indexRootFields()); + return display_cache.is_default_chain; +} + +bool tx_is_expert_mode() { + return app_mode_expert() || is_default_chainid(); +} + +__Z_INLINE uint8_t get_subitem_count(root_item_e root_item) { + CHECK_PARSER_ERR(tx_indexRootFields()) + if (display_cache.total_item_count == 0) + return 0; + + int16_t tmp_num_items = display_cache.root_item_number_subitems[root_item]; + + switch (root_item) { + case root_item_chain_id: + case root_item_sequence: + case root_item_account_number: + if (!tx_is_expert_mode()) { + tmp_num_items = 0; + } + break; + case root_item_msgs: + // Remove grouped items from list + if (parser_tx_obj.flags.msg_type_grouping == 1u && parser_tx_obj.filter_msg_type_count > 0) { + tmp_num_items += 1; // we leave main type + tmp_num_items -= parser_tx_obj.filter_msg_type_count; + } + if (parser_tx_obj.flags.msg_from_grouping == 1u && parser_tx_obj.filter_msg_from_count > 0) { + if (!parser_tx_obj.flags.msg_from_grouping_hide_all) { + tmp_num_items += 1; // we leave main from + } + tmp_num_items -= parser_tx_obj.filter_msg_from_count; + } + break; + case root_item_memo: + break; + case root_item_fee: + default: + break; + } + + return tmp_num_items; +} + +__Z_INLINE parser_error_t retrieve_tree_indexes(uint8_t display_index, root_item_e *root_item, uint8_t *subitem_index) { + // Find root index | display_index idx -> item_index + // consume indexed subpages until we get the item index in the subpage + *root_item = 0; + *subitem_index = 0; + while (get_subitem_count(*root_item) == 0) (*root_item)++; + + for (uint16_t i = 0; i < display_index; i++) { + (*subitem_index)++; + const uint8_t subitem_count = get_subitem_count(*root_item); + if (*subitem_index >= subitem_count) { + // Advance root index and skip empty items + *subitem_index = 0; + (*root_item)++; + while (get_subitem_count(*root_item) == 0) (*root_item)++; + } + } + + if (*root_item > NUM_REQUIRED_ROOT_PAGES) { + return parser_no_data; + } + return parser_ok; } -parser_error_t tx_display_numItems(uint16_t *num_items) { +parser_error_t tx_display_numItems(uint8_t *num_items) { + *num_items = 0; CHECK_PARSER_ERR(tx_indexRootFields()) - *num_items = display_cache.total_item_count; - // Remove grouped items from list - if (parser_tx_obj.flags.msg_type_grouping == 1u && parser_tx_obj.filter_msg_type_count > 0) { - *num_items += 1; // we leave main type - *num_items -= parser_tx_obj.filter_msg_type_count; + *num_items = 0; + for (root_item_e root_item = 0; root_item < NUM_REQUIRED_ROOT_PAGES; root_item++) { + *num_items += get_subitem_count(root_item); } return parser_ok; @@ -190,37 +357,27 @@ parser_error_t tx_display_query(uint16_t displayIdx, uint16_t *ret_value_token_index) { CHECK_PARSER_ERR(tx_indexRootFields()) - if (displayIdx < 0 || displayIdx >= display_cache.total_item_count) { + uint8_t num_items; + CHECK_PARSER_ERR(tx_display_numItems(&num_items)); + + if (displayIdx < 0 || displayIdx >= num_items) { return parser_display_idx_out_of_range; } - uint16_t current_item_index = 0; - uint16_t root_index = 0; - - // Find root index | display idx -> item_index - // consume indexed subpages until we get the item index in the subpage - for (uint16_t i = 0; i < displayIdx; i++) { - current_item_index++; - if (current_item_index >= display_cache.root_item_number_subitems[root_index]) { - current_item_index = 0; - - // Advance root index and skip empty items - root_index++; - while (display_cache.root_item_number_subitems[root_index] == 0) root_index++; - } - } + root_item_e root_index = 0; + uint8_t subitem_index = 0; + CHECK_PARSER_ERR(retrieve_tree_indexes(displayIdx, &root_index, &subitem_index)); // Prepare query char tmp_val[2]; INIT_QUERY_CONTEXT(outKey, outKeyLen, tmp_val, sizeof(tmp_val), - 0, root_max_level[root_index]) - parser_tx_obj.query.item_index = current_item_index; + 0, get_root_max_level(root_index)) + parser_tx_obj.query.item_index = subitem_index; parser_tx_obj.query._item_index_current = 0; - parser_tx_obj.query.max_level = root_max_level[root_index]; - strncpy_s(outKey, get_required_root_item((root_item_e) root_index), outKeyLen); + strncpy_s(outKey, get_required_root_item(root_index), outKeyLen); - if (!display_cache.root_item_start_valid[root_index]) { + if (!display_cache.root_item_start_token_valid[root_index]) { return parser_no_data; } diff --git a/app/src/tx_display.h b/app/src/tx_display.h index 49d672e8..2e51e5b3 100644 --- a/app/src/tx_display.h +++ b/app/src/tx_display.h @@ -26,25 +26,25 @@ extern "C" { typedef enum { root_item_chain_id = 0, - root_item_account_number = 1, - root_item_sequence = 2, - root_item_fee = 3, - root_item_memo = 4, - root_item_msgs = 5, + root_item_account_number, + root_item_sequence, + root_item_msgs, + root_item_memo, + root_item_fee, } root_item_e; +bool tx_is_expert_mode(); + const char *get_required_root_item(root_item_e i); parser_error_t tx_display_query(uint16_t displayIdx, - char *outKey, - uint16_t outKeyLen, + char *outKey, uint16_t outKeyLen, uint16_t *ret_value_token_index); parser_error_t tx_display_readTx(parser_context_t *c, - const uint8_t *data, - size_t dataLen); + const uint8_t *data, size_t dataLen); -parser_error_t tx_display_numItems(uint16_t *num_items); +parser_error_t tx_display_numItems(uint8_t *num_items); parser_error_t tx_display_make_friendly(); diff --git a/app/src/tx_parser.c b/app/src/tx_parser.c index 19209028..edcdd6bf 100644 --- a/app/src/tx_parser.c +++ b/app/src/tx_parser.c @@ -129,43 +129,45 @@ __always_inline void append_key_item(int16_t token_index) { /////////////////////////// parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_token_index) { - parser_error_t err; const jsmntype_t token_type = parser_tx_obj.json.tokens[root_token_index].type; + CHECK_APP_CANARY() + if (parser_tx_obj.tx == NULL || root_token_index < 0) { - CHECK_APP_CANARY() return parser_no_data; } - CHECK_APP_CANARY() - if (parser_tx_obj.query.max_level <= 0 || parser_tx_obj.query.max_depth <= 0 || token_type == JSMN_STRING || token_type == JSMN_PRIMITIVE) { + const bool skipTypeField = + parser_tx_obj.flags.cache_valid && + parser_tx_obj.flags.msg_type_grouping && + is_msg_type_field(parser_tx_obj.query.out_key) && + parser_tx_obj.filter_msg_type_valid_idx != parser_tx_obj.query._item_index_current; - CHECK_APP_CANARY() + const bool skipFromFieldHidingRule = + parser_tx_obj.flags.msg_from_grouping_hide_all || + parser_tx_obj.filter_msg_from_valid_idx != parser_tx_obj.query._item_index_current; - const bool groupedField = strcmp("msgs/type", parser_tx_obj.query.out_key) == 0; - CHECK_APP_CANARY() - - const bool isMainIndex = parser_tx_obj.filter_msg_type_valid_idx != parser_tx_obj.query._item_index_current; - CHECK_APP_CANARY() + const bool skipFromField = + parser_tx_obj.flags.cache_valid && + parser_tx_obj.flags.msg_from_grouping && + is_msg_from_field(parser_tx_obj.query.out_key) && + skipFromFieldHidingRule; - const bool skipItem = parser_tx_obj.flags.cache_valid == 1u && - parser_tx_obj.flags.msg_type_grouping == 1u && - groupedField && - isMainIndex; + const bool skipField = skipFromField || skipTypeField; CHECK_APP_CANARY() // Early bail out - if (!skipItem && parser_tx_obj.query._item_index_current == parser_tx_obj.query.item_index) { + if (!skipField && parser_tx_obj.query._item_index_current == parser_tx_obj.query.item_index) { *ret_value_token_index = root_token_index; CHECK_APP_CANARY() return parser_ok; } - if (skipItem) { + if (skipField) { parser_tx_obj.query.item_index++; } @@ -175,6 +177,8 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to } uint16_t el_count; + parser_error_t err; + CHECK_PARSER_ERR(object_get_element_count(&parser_tx_obj.json, root_token_index, &el_count)) switch (token_type) { @@ -189,23 +193,24 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to // Skip writing keys if we are actually exploring to count append_key_item(key_index); + CHECK_APP_CANARY() // When traversing objects both level and depth should be considered parser_tx_obj.query.max_level--; parser_tx_obj.query.max_depth--; + // Traverse the value, extracting subkeys - CHECK_APP_CANARY() err = tx_traverse_find(value_index, ret_value_token_index); CHECK_APP_CANARY() parser_tx_obj.query.max_level++; parser_tx_obj.query.max_depth++; if (err == parser_ok) { - CHECK_APP_CANARY() return parser_ok; } *(parser_tx_obj.query.out_key + key_len) = 0; + CHECK_APP_CANARY() } break; } @@ -215,6 +220,7 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to CHECK_PARSER_ERR(array_get_nth_element(&parser_tx_obj.json, root_token_index, i, &element_index)); + CHECK_APP_CANARY() // When iterating along an array, // the level does not change but we need to count the recursion @@ -222,8 +228,9 @@ parser_error_t tx_traverse_find(int16_t root_token_index, uint16_t *ret_value_to err = tx_traverse_find(element_index, ret_value_token_index); parser_tx_obj.query.max_depth++; + CHECK_APP_CANARY() + if (err == parser_ok) { - CHECK_APP_CANARY() return parser_ok; } } diff --git a/app/src/tx_parser.h b/app/src/tx_parser.h index 9b7c4409..022bacd6 100644 --- a/app/src/tx_parser.h +++ b/app/src/tx_parser.h @@ -19,6 +19,7 @@ #include "json/json_parser.h" #include #include +#include "zxmacros.h" #ifdef __cplusplus extern "C" { @@ -50,7 +51,14 @@ parser_error_t tx_traverse(int16_t root_token_index, uint8_t *numChunks); parser_error_t tx_getToken(uint16_t token_index, char *out_val, uint16_t out_val_len, uint8_t pageIdx, uint8_t *pageCount); -//--------------------------------------------- + +__Z_INLINE bool is_msg_type_field(char *field_name) { + return strcmp(field_name, "msgs/type") == 0; +} + +__Z_INLINE bool is_msg_from_field(char *field_name) { + return strcmp(field_name, "msgs/value/delegator_address") == 0; +} #ifdef __cplusplus } diff --git a/deps/ledger-zxlib/app/common/view.c b/deps/ledger-zxlib/app/common/view.c index 18243adf..7cfba6da 100644 --- a/deps/ledger-zxlib/app/common/view.c +++ b/deps/ledger-zxlib/app/common/view.c @@ -49,19 +49,9 @@ void h_error_accept(unsigned int _) { void h_sign_accept(unsigned int _) { UNUSED(_); - - const uint8_t replyLen = app_sign(); - + app_sign(); view_idle_show(0); UX_WAIT(); - - if (replyLen > 0) { - set_code(G_io_apdu_buffer, replyLen, APDU_CODE_OK); - io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, replyLen + 2); - } else { - set_code(G_io_apdu_buffer, 0, APDU_CODE_SIGN_VERIFY_ERROR); - io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); - } } void h_sign_reject(unsigned int _) { @@ -115,6 +105,17 @@ view_error_t h_review_update_data() { tx_error_t err = tx_no_error; do { + err = tx_getNumItems(&viewdata.itemCount); + viewdata.itemCount++; + + if (err == tx_no_data) { + return view_no_data; + } + + if (err != tx_no_error) { + return view_error_detected; + } + err = tx_getItem(viewdata.itemIdx, viewdata.key, MAX_CHARS_PER_KEY_LINE, viewdata.value, MAX_CHARS_PER_VALUE1_LINE, @@ -124,15 +125,15 @@ view_error_t h_review_update_data() { return view_no_data; } + if (err != tx_no_error) { + return view_error_detected; + } + if (viewdata.pageCount == 0) { h_paging_increase(); } } while (viewdata.pageCount == 0); - if (err != tx_no_error) { - return view_error_detected; - } - splitValueField(); return view_no_error; } @@ -156,8 +157,8 @@ void view_init(void) { UX_INIT(); } -void view_idle_show(unsigned int ignored) { - view_idle_show_impl(); +void view_idle_show(uint8_t item_idx) { + view_idle_show_impl(item_idx); } void view_address_show(address_kind_e addressKind) { diff --git a/deps/ledger-zxlib/app/common/view.h b/deps/ledger-zxlib/app/common/view.h index e9389a54..8e3c4481 100644 --- a/deps/ledger-zxlib/app/common/view.h +++ b/deps/ledger-zxlib/app/common/view.h @@ -31,7 +31,7 @@ void view_init(); /// view_idle_show (idle view - main menu + status) -void view_idle_show(unsigned int ignored); +void view_idle_show(uint8_t item_idx); /// view_error (error view) void view_error_show(); diff --git a/deps/ledger-zxlib/app/common/view_internal.h b/deps/ledger-zxlib/app/common/view_internal.h index e5c39ad7..9df78069 100644 --- a/deps/ledger-zxlib/app/common/view_internal.h +++ b/deps/ledger-zxlib/app/common/view_internal.h @@ -83,7 +83,7 @@ void splitValueField(); /////////////////////////////////////////////// /////////////////////////////////////////////// -void view_idle_show_impl(); +void view_idle_show_impl(uint8_t item_idx); void view_address_show_impl(); diff --git a/deps/ledger-zxlib/app/common/view_s.c b/deps/ledger-zxlib/app/common/view_s.c index 7a9127eb..ce9f344d 100644 --- a/deps/ledger-zxlib/app/common/view_s.c +++ b/deps/ledger-zxlib/app/common/view_s.c @@ -15,6 +15,7 @@ * limitations under the License. ********************************************************************************/ +#include "app_mode.h" #include "view.h" #include "view_internal.h" #include "actions.h" @@ -30,6 +31,7 @@ #if defined(TARGET_NANOS) +void h_expert_toggle(); void h_review_button_left(); void h_review_button_right(); void view_review_show(); @@ -43,7 +45,10 @@ void os_exit(uint32_t id) { const ux_menu_entry_t menu_main[] = { {NULL, NULL, 0, &C_icon_app, MENU_MAIN_APP_LINE1, MENU_MAIN_APP_LINE2, 33, 12}, - {NULL, NULL, 0, &C_icon_app, "v"APPVERSION, APPVERSION_LINE2, 33, 12}, + {NULL, h_expert_toggle, 0, &C_icon_app, "Expert mode:", viewdata.value, 33, 12}, + {NULL, NULL, 0, &C_icon_app, APPVERSION_LINE1, APPVERSION_LINE2, 33, 12}, + {NULL, NULL, 0, &C_icon_app, "Developed by:", "Zondax.ch", 33, 12}, + {NULL, NULL, 0, &C_icon_app, "License: ", "Apache 2.0", 33, 12}, {NULL, os_exit, 0, &C_icon_dashboard, "Quit", NULL, 50, 29}, UX_MENU_END }; @@ -226,8 +231,12 @@ void splitValueField() { ////////////////////////// ////////////////////////// -void view_idle_show_impl() { - UX_MENU_DISPLAY(0, menu_main, NULL); +void view_idle_show_impl(uint8_t item_idx) { + strcpy(viewdata.value, "disabled"); + if (app_mode_expert()) { + strcpy(viewdata.value, "enabled"); + } + UX_MENU_DISPLAY(item_idx, menu_main, NULL); } void view_address_show_impl() { @@ -283,6 +292,11 @@ void view_review_show() { UX_DISPLAY(view_review, view_prepro); } +void h_expert_toggle() { + app_mode_set_expert(!app_mode_expert()); + view_idle_show(1); +} + #if !defined(HAVE_UX_FLOW) void h_addr_button_left() { h_paging_decrease(); diff --git a/deps/ledger-zxlib/app/common/view_x.c b/deps/ledger-zxlib/app/common/view_x.c index 43b1bedd..4fe25614 100644 --- a/deps/ledger-zxlib/app/common/view_x.c +++ b/deps/ledger-zxlib/app/common/view_x.c @@ -40,12 +40,18 @@ bolos_ux_params_t G_ux_params; uint8_t flow_inside_loop; UX_FLOW_DEF_NOCB(ux_idle_flow_1_step, pbb, { &C_icon_app, MENU_MAIN_APP_LINE1, MENU_MAIN_APP_LINE2,}); -UX_FLOW_DEF_NOCB(ux_idle_flow_3_step, bn, { "Version", APPVERSION, }); -UX_FLOW_DEF_VALID(ux_idle_flow_4_step, pb, os_sched_exit(-1), { &C_icon_dashboard, "Quit",}); +UX_FLOW_DEF_NOCB(ux_idle_flow_2_step, bn, { "Expert mode:", "disabled", }); +UX_FLOW_DEF_NOCB(ux_idle_flow_2_step, bn, { APPVERSION_LINE1, APPVERSION_LINE2, }); +UX_FLOW_DEF_NOCB(ux_idle_flow_3_step, bn, { "Developed by:", "Zondax.ch", }); +UX_FLOW_DEF_NOCB(ux_idle_flow_4_step, bn, { "License:", "Apache 2.0", }); +UX_FLOW_DEF_VALID(ux_idle_flow_5_step, pb, os_sched_exit(-1), { &C_icon_dashboard, "Quit",}); + const ux_flow_step_t *const ux_idle_flow [] = { &ux_idle_flow_1_step, + &ux_idle_flow_2_step, &ux_idle_flow_3_step, &ux_idle_flow_4_step, + &ux_idle_flow_5_step, FLOW_END_STEP, }; @@ -190,7 +196,7 @@ void splitValueField() { ////////////////////////// ////////////////////////// -void view_idle_show_impl() { +void view_idle_show_impl(uint8_t item_idx) { if(G_ux.stack_count == 0) { ux_stack_push(); } diff --git a/deps/ledger-zxlib/dockerized_build.mk b/deps/ledger-zxlib/dockerized_build.mk index 24bc057c..3fe7042b 100644 --- a/deps/ledger-zxlib/dockerized_build.mk +++ b/deps/ledger-zxlib/dockerized_build.mk @@ -60,7 +60,7 @@ define run_docker -u $(USERID) \ -v $(shell pwd):/project \ $(DOCKER_IMAGE) \ - "$(2)" + "COIN=$(COIN) $(2)" endef all: build @@ -99,6 +99,10 @@ buildX: build_rust clean: $(call run_docker,$(DOCKER_BOLOS_SDK),make -C $(DOCKER_APP_SRC) clean) +.PHONY: listvariants +listvariants: + $(call run_docker,$(DOCKER_BOLOS_SDK),make -C $(DOCKER_APP_SRC) listvariants) + .PHONY: shell shell: $(call run_docker,$(DOCKER_BOLOS_SDK) -t,bash) @@ -175,6 +179,10 @@ zemu_install: zemu_install_js_link zemu: cd $(TESTS_ZEMU_DIR)/tools && node debug.mjs +.PHONY: zemu_val +zemu_val: + cd $(TESTS_ZEMU_DIR)/tools && node debug_val.mjs + .PHONY: zemu_debug zemu_debug: cd $(TESTS_ZEMU_DIR)/tools && node debug.mjs debug diff --git a/deps/ledger-zxlib/include/zxmacros.h b/deps/ledger-zxlib/include/zxmacros.h index 067ec02c..b500231c 100644 --- a/deps/ledger-zxlib/include/zxmacros.h +++ b/deps/ledger-zxlib/include/zxmacros.h @@ -53,6 +53,16 @@ void handle_stack_overflow(); #if defined (TARGET_NANOS) || defined(TARGET_NANOX) +__Z_INLINE void debug_log(char *buf) +{ + asm volatile ( + "movs r0, #0x04\n" + "movs r1, %0\n" + "svc 0xab\n" + :: "r"(buf) : "r0", "r1" + ); +} + #include "bolos_target.h" #include "os.h" #include "cx.h" diff --git a/deps/ledger-zxlib/include/zxversion.h b/deps/ledger-zxlib/include/zxversion.h index a1b97cdb..d7d45776 100644 --- a/deps/ledger-zxlib/include/zxversion.h +++ b/deps/ledger-zxlib/include/zxversion.h @@ -15,6 +15,6 @@ ********************************************************************************/ #pragma once -#define ZXLIB_MAJOR 1 +#define ZXLIB_MAJOR 2 #define ZXLIB_MINOR 0 -#define ZXLIB_PATCH 2 +#define ZXLIB_PATCH 0 diff --git a/deps/ledger-zxlib/src/bignum.c b/deps/ledger-zxlib/src/bignum.c index b4c3e483..59cb0459 100644 --- a/deps/ledger-zxlib/src/bignum.c +++ b/deps/ledger-zxlib/src/bignum.c @@ -68,7 +68,7 @@ void bignumLittleEndian_to_bcd(uint8_t *bcdOut, uint16_t bcdOutLen, // get bit const uint16_t byteIdx = bitIdx >> 3u; - const uint8_t mask = 0x80u >> (bitIdx & 0b111u); + const uint8_t mask = 0x80u >> (bitIdx & 0x7u); carry = (binValue[binValueLen - byteIdx - 1] & mask) > 0; // Shift bcd @@ -134,7 +134,7 @@ void bignumBigEndian_to_bcd(uint8_t *bcdOut, uint16_t bcdOutLen, // get bit const uint16_t byteIdx = bitIdx >> 3u; - const uint8_t mask = 0x80u >> (bitIdx & 0b111u); + const uint8_t mask = 0x80u >> (bitIdx & 0x7u); carry = (binValue[byteIdx] & mask) > 0; // Shift bcd diff --git a/tests/testcases/manual.json b/tests/testcases/manual.json index 9d840250..1da7c619 100644 --- a/tests/testcases/manual.json +++ b/tests/testcases/manual.json @@ -4,7 +4,8 @@ "tx": "", "parsingErr": "No error", "validationErr": "JSON Missing chain_id", - "expected": [] + "expected": [], + "expert": true }, { "name": "minimal", @@ -35,10 +36,11 @@ "0 | Chain ID : V2", "1 | Account : V1", "2 | Sequence : V5", - "3 | fee/amount : Unexpected field", - "4 | Gas : V3", - "5 | Memo : V4" - ] + "3 | Memo : V4", + "4 | fee/amount : Unexpected field", + "5 | Gas : V3" + ], + "expert": true }, { "name": "multipleMessages", @@ -74,19 +76,20 @@ "0 | Chain ID : V2", "1 | Account : V1", "2 | Sequence : V5", - "3 | Fee : b d", - "4 | Gas : V3", - "5 | Memo : V4", - "6 | msgs/m1 : z1", - "7 | msgs/m2 : z2", - "8 | msgs/m3 : z3" - ] + "3 | msgs/m1 : z1", + "4 | msgs/m2 : z2", + "5 | msgs/m3 : z3", + "6 | Memo : V4", + "7 | Fee : b d", + "8 | Gas : V3" + ], + "expert": true }, { - "name": "completeTransfer", + "name": "completeTransferExpert", "tx": { "account_number": "0", - "chain_id": "test-chain-1", + "chain_id": "cosmoshub-3", "fee": { "amount": [ { @@ -128,20 +131,77 @@ "parsingErr": "No error", "validationErr": "No error", "expected": [ - "0 | Chain ID : test-chain-1", + "0 | Chain ID : cosmoshub-3", "1 | Account : 0", "2 | Sequence : 1", - "3 | Fee : 5 photon", - "4 | Gas : 10000", - "5 | Memo : testmemo", - "6 | Source Address : cosmosaccaddr1d9h8qat5e4ehc5", - "7 | Source Coins : 10 atom", - "8 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", - "9 | Dest Coins : 10 atom" - ] + "3 | Source Address : cosmosaccaddr1d9h8qat5e4ehc5", + "4 | Source Coins : 10 atom", + "5 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", + "6 | Dest Coins : 10 atom", + "7 | Memo : testmemo", + "8 | Fee : 5 photon", + "9 | Gas : 10000" + ], + "expert": true }, { - "name": "completeTransferNoMemo", + "name": "completeTransfer", + "tx": { + "account_number": "0", + "chain_id": "cosmoshub-3", + "fee": { + "amount": [ + { + "amount": "5", + "denom": "photon" + } + ], + "gas": "10000" + }, + "memo": "testmemo", + "msgs": [ + { + "inputs": [ + { + "address": "cosmosaccaddr1d9h8qat5e4ehc5", + "coins": [ + { + "amount": "10", + "denom": "atom" + } + ] + } + ], + "outputs": [ + { + "address": "cosmosaccaddr1da6hgur4wse3jx32", + "coins": [ + { + "amount": "10", + "denom": "atom" + } + ] + } + ] + } + ], + "sequence": "1" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Source Address : cosmosaccaddr1d9h8qat5e4ehc5", + "1 | Source Coins : 10 atom", + "2 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", + "3 | Dest Coins : 10 atom", + "4 | Memo : testmemo", + "5 | Fee : 5 photon", + "6 | Gas : 10000" + ], + "expert": false + }, + { + "name": "completeTransferNoMemoExpert", "tx": { "account_number": "0", "chain_id": "test-chain-1", @@ -188,16 +248,71 @@ "0 | Chain ID : test-chain-1", "1 | Account : 0", "2 | Sequence : 1", - "3 | Fee : 5 photon", - "4 | Gas : 10000", - "5 | Source Address : cosmosaccaddr1d9h8qat5e4ehc5", - "6 | Source Coins : 10 atom", - "7 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", - "8 | Dest Coins : 10 atom" - ] + "3 | Source Address : cosmosaccaddr1d9h8qat5e4ehc5", + "4 | Source Coins : 10 atom", + "5 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", + "6 | Dest Coins : 10 atom", + "7 | Fee : 5 photon", + "8 | Gas : 10000" + ], + "expert": true }, { - "name": "completeTransferEmptyMemo", + "name": "completeTransferNoMemo", + "tx": { + "account_number": "0", + "chain_id": "cosmoshub-3", + "fee": { + "amount": [ + { + "amount": "5", + "denom": "photon" + } + ], + "gas": "10000" + }, + "msgs": [ + { + "inputs": [ + { + "address": "cosmosaccaddr1d9h8qat5e4ehc5", + "coins": [ + { + "amount": "10", + "denom": "atom" + } + ] + } + ], + "outputs": [ + { + "address": "cosmosaccaddr1da6hgur4wse3jx32", + "coins": [ + { + "amount": "10", + "denom": "atom" + } + ] + } + ] + } + ], + "sequence": "1" + }, + "parsingErr": "No error", + "validationErr": "JSON Missing memo", + "expected": [ + "0 | Source Address : cosmosaccaddr1d9h8qat5e4ehc5", + "1 | Source Coins : 10 atom", + "2 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", + "3 | Dest Coins : 10 atom", + "4 | Fee : 5 photon", + "5 | Gas : 10000" + ], + "expert": false + }, + { + "name": "completeTransferEmptyMemoExpert", "tx": { "account_number": "0", "chain_id": "test-chain-1", @@ -245,16 +360,72 @@ "0 | Chain ID : test-chain-1", "1 | Account : 0", "2 | Sequence : 1", - "3 | Fee : 5 photon", - "4 | Gas : 10000", - "5 | Source Address : cosmosaccaddr1d9h8qat5e4ehc5", - "6 | Source Coins : 10 atom", - "7 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", - "8 | Dest Coins : 10 atom" - ] + "3 | Source Address : cosmosaccaddr1d9h8qat5e4ehc5", + "4 | Source Coins : 10 atom", + "5 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", + "6 | Dest Coins : 10 atom", + "7 | Fee : 5 photon", + "8 | Gas : 10000" + ], + "expert": true }, { - "name": "multipleTransfers", + "name": "completeTransferEmptyMemo", + "tx": { + "account_number": "0", + "chain_id": "cosmoshub-3", + "fee": { + "amount": [ + { + "amount": "5", + "denom": "photon" + } + ], + "gas": "10000" + }, + "memo": "", + "msgs": [ + { + "inputs": [ + { + "address": "cosmosaccaddr1d9h8qat5e4ehc5", + "coins": [ + { + "amount": "10", + "denom": "atom" + } + ] + } + ], + "outputs": [ + { + "address": "cosmosaccaddr1da6hgur4wse3jx32", + "coins": [ + { + "amount": "10", + "denom": "atom" + } + ] + } + ] + } + ], + "sequence": "1" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Source Address : cosmosaccaddr1d9h8qat5e4ehc5", + "1 | Source Coins : 10 atom", + "2 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", + "3 | Dest Coins : 10 atom", + "4 | Fee : 5 photon", + "5 | Gas : 10000" + ], + "expert": false + }, + { + "name": "multipleTransfersExpert", "tx": { "account_number": "0", "chain_id": "test-chain-1", @@ -326,24 +497,109 @@ "0 | Chain ID : test-chain-1", "1 | Account : 0", "2 | Sequence : 1", - "3 | Fee : 5 photon", - "4 | Gas : 10000", - "5 | Memo : testmemo", - "6 | Source Address : cosmosaccaddr1d9h8qat5e4ehc5", - "7 | Source Coins : 10 atom", - "8 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", - "9 | Dest Coins : 10 atom", - "10 | Source Address : test2", - "11 | Source Coins : 20 bitcoin", - "12 | Dest Address : test3", - "13 | Dest Coins : 50 ripple" - ] + "3 | Source Address : cosmosaccaddr1d9h8qat5e4ehc5", + "4 | Source Coins : 10 atom", + "5 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", + "6 | Dest Coins : 10 atom", + "7 | Source Address : test2", + "8 | Source Coins : 20 bitcoin", + "9 | Dest Address : test3", + "10 | Dest Coins : 50 ripple", + "11 | Memo : testmemo", + "12 | Fee : 5 photon", + "13 | Gas : 10000" + ], + "expert": true }, { - "name": "delegation", + "name": "multipleTransfers", + "tx": { + "account_number": "0", + "chain_id": "cosmoshub-3", + "fee": { + "amount": [ + { + "amount": "5", + "denom": "photon" + } + ], + "gas": "10000" + }, + "memo": "testmemo", + "msgs": [ + { + "inputs": [ + { + "address": "cosmosaccaddr1d9h8qat5e4ehc5", + "coins": [ + { + "amount": "10", + "denom": "atom" + } + ] + } + ], + "outputs": [ + { + "address": "cosmosaccaddr1da6hgur4wse3jx32", + "coins": [ + { + "amount": "10", + "denom": "atom" + } + ] + } + ] + }, + { + "inputs": [ + { + "address": "test2", + "coins": [ + { + "amount": "20", + "denom": "bitcoin" + } + ] + } + ], + "outputs": [ + { + "address": "test3", + "coins": [ + { + "amount": "50", + "denom": "ripple" + } + ] + } + ] + } + ], + "sequence": "1" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Source Address : cosmosaccaddr1d9h8qat5e4ehc5", + "1 | Source Coins : 10 atom", + "2 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", + "3 | Dest Coins : 10 atom", + "4 | Source Address : test2", + "5 | Source Coins : 20 bitcoin", + "6 | Dest Address : test3", + "7 | Dest Coins : 50 ripple", + "8 | Memo : testmemo", + "9 | Fee : 5 photon", + "10 | Gas : 10000" + ], + "expert": false + }, + { + "name": "delegationExpert", "tx": { "account_number": "6571", - "chain_id": "cosmoshub-2", + "chain_id": "cosmoshub-3", "fee": { "amount": [ { @@ -353,7 +609,7 @@ ], "gas": "200000" }, - "memo": "Delegated with Ledger from union.market", + "memo": "Zondax.ch", "msgs": [ { "type": "cosmos-sdk/MsgDelegate", @@ -372,22 +628,116 @@ "parsingErr": "No error", "validationErr": "No error", "expected": [ - "0 | Chain ID : cosmoshub-2", + "0 | Chain ID : cosmoshub-3", "1 | Account : 6571", "2 | Sequence : 1", - "3 | Fee : 5000 uatom", - "4 | Gas : 200000", - "5 | Memo : Delegated with Ledger from union.market", - "6 | Type : Delegate", - "7 | Amount : 1000000 uatom", - "8 | Delegator [1/2] : cosmos102hty0jv2s29lyc4u0tv97z9v298e24t", - "8 | Delegator [2/2] : 3vwtpl", - "9 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", - "9 | Validator [2/2] : 9m5s03xfytvz7" - ] + "3 | Type : Delegate", + "4 | Amount : 1000000 uatom", + "5 | Delegator [1/2] : cosmos102hty0jv2s29lyc4u0tv97z9v298e24t", + "5 | Delegator [2/2] : 3vwtpl", + "6 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", + "6 | Validator [2/2] : 9m5s03xfytvz7", + "7 | Memo : Zondax.ch", + "8 | Fee : 5000 uatom", + "9 | Gas : 200000" + ], + "expert": true }, { - "name": "proposal", + "name": "delegationDifferentChain", + "tx": { + "account_number": "6571", + "chain_id": "otherhub", + "fee": { + "amount": [ + { + "amount": "5000", + "denom": "uatom" + } + ], + "gas": "200000" + }, + "memo": "Zondax.ch", + "msgs": [ + { + "type": "cosmos-sdk/MsgDelegate", + "value": { + "amount": { + "amount": "1000000", + "denom": "uatom" + }, + "delegator_address": "cosmos102hty0jv2s29lyc4u0tv97z9v298e24t3vwtpl", + "validator_address": "cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt9m5s03xfytvz7" + } + } + ], + "sequence": "1" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Chain ID : otherhub", + "1 | Account : 6571", + "2 | Sequence : 1", + "3 | Type : Delegate", + "4 | Amount : 1000000 uatom", + "5 | Delegator [1/2] : cosmos102hty0jv2s29lyc4u0tv97z9v298e24t", + "5 | Delegator [2/2] : 3vwtpl", + "6 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", + "6 | Validator [2/2] : 9m5s03xfytvz7", + "7 | Memo : Zondax.ch", + "8 | Fee : 5000 uatom", + "9 | Gas : 200000" + ], + "expert": false + }, + { + "name": "delegation", + "tx": { + "account_number": "6571", + "chain_id": "cosmoshub-3", + "fee": { + "amount": [ + { + "amount": "5000", + "denom": "uatom" + } + ], + "gas": "200000" + }, + "memo": "Zondax.ch", + "msgs": [ + { + "type": "cosmos-sdk/MsgDelegate", + "value": { + "amount": { + "amount": "1000000", + "denom": "uatom" + }, + "delegator_address": "cosmos102hty0jv2s29lyc4u0tv97z9v298e24t3vwtpl", + "validator_address": "cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt9m5s03xfytvz7" + } + } + ], + "sequence": "1" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Type : Delegate", + "1 | Amount : 1.000000 ATOM", + "2 | Delegator [1/2] : cosmos102hty0jv2s29lyc4u0tv97z9v298e24t", + "2 | Delegator [2/2] : 3vwtpl", + "3 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", + "3 | Validator [2/2] : 9m5s03xfytvz7", + "4 | Memo : Zondax.ch", + "5 | Fee : 0.005000 ATOM", + "6 | Gas : 200000" + ], + "expert": false + }, + { + "name": "proposalExpert", "tx": { "account_number": "2", "chain_id": "local-testnet", @@ -418,23 +768,66 @@ "0 | Chain ID : local-testnet", "1 | Account : 2", "2 | Sequence : 0", - "3 | Fee : Empty", - "4 | Gas : 500000", - "5 | Memo : abc", - "6 | msgs/description : test", - "7 | msgs/initial_deposit/amount : 1", - "8 | msgs/initial_deposit/denom : stake", - "9 | msgs/proposal_type : Text", - "10 | msgs/proposer [1/2] : cosmos101234567890abcdefghijklmnopqrstu", - "10 | msgs/proposer [2/2] : vwxyz0", - "11 | msgs/title : test" - ] + "3 | msgs/description : test", + "4 | msgs/initial_deposit/amount : 1", + "5 | msgs/initial_deposit/denom : stake", + "6 | msgs/proposal_type : Text", + "7 | msgs/proposer [1/2] : cosmos101234567890abcdefghijklmnopqrstu", + "7 | msgs/proposer [2/2] : vwxyz0", + "8 | msgs/title : test", + "9 | Memo : abc", + "10 | Fee : Empty", + "11 | Gas : 500000" + ], + "expert": true }, { - "name": "delegation2", + "name": "proposal", + "tx": { + "account_number": "2", + "chain_id": "cosmoshub-3", + "fee": { + "amount": [], + "gas": "500000" + }, + "memo": "abc", + "msgs": [ + { + "description": "test", + "initial_deposit": [ + { + "amount": "1", + "denom": "stake" + } + ], + "proposal_type": "Text", + "proposer": "cosmos101234567890abcdefghijklmnopqrstuvwxyz0", + "title": "test" + } + ], + "sequence": "0" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | msgs/description : test", + "1 | msgs/initial_deposit/amount : 1", + "2 | msgs/initial_deposit/denom : stake", + "3 | msgs/proposal_type : Text", + "4 | msgs/proposer [1/2] : cosmos101234567890abcdefghijklmnopqrstu", + "4 | msgs/proposer [2/2] : vwxyz0", + "5 | msgs/title : test", + "6 | Memo : abc", + "7 | Fee : Empty", + "8 | Gas : 500000" + ], + "expert": false + }, + { + "name": "delegation2Expert", "tx": { "account_number": "6571", - "chain_id": "cosmoshub-2", + "chain_id": "cosmoshub-3", "fee": { "amount": [ { @@ -463,19 +856,65 @@ "parsingErr": "No error", "validationErr": "No error", "expected": [ - "0 | Chain ID : cosmoshub-2", + "0 | Chain ID : cosmoshub-3", "1 | Account : 6571", "2 | Sequence : 0", - "3 | Fee : 5000 uatom", - "4 | Gas : 200000", - "5 | Memo : Delegated with Ledger from union.market", - "6 | Type : Delegate", - "7 | Amount : 1000000 uatom", - "8 | Delegator [1/2] : cosmos102hty0jv2s29lyc4u0tv97z9v298e24t", - "8 | Delegator [2/2] : 3vwtpl", - "9 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", - "9 | Validator [2/2] : 9m5s03xfytvz7" - ] + "3 | Type : Delegate", + "4 | Amount : 1000000 uatom", + "5 | Delegator [1/2] : cosmos102hty0jv2s29lyc4u0tv97z9v298e24t", + "5 | Delegator [2/2] : 3vwtpl", + "6 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", + "6 | Validator [2/2] : 9m5s03xfytvz7", + "7 | Memo : Delegated with Ledger from union.market", + "8 | Fee : 5000 uatom", + "9 | Gas : 200000" + ], + "expert": true + }, + { + "name": "delegation2", + "tx": { + "account_number": "6571", + "chain_id": "cosmoshub-3", + "fee": { + "amount": [ + { + "amount": "5000", + "denom": "uatom" + } + ], + "gas": "200000" + }, + "memo": "Delegated with Ledger from union.market", + "msgs": [ + { + "type": "cosmos-sdk/MsgDelegate", + "value": { + "amount": { + "amount": "1000000", + "denom": "uatom" + }, + "delegator_address": "cosmos102hty0jv2s29lyc4u0tv97z9v298e24t3vwtpl", + "validator_address": "cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt9m5s03xfytvz7" + } + } + ], + "sequence": "0" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Type : Delegate", + "1 | Amount : 1.000000 ATOM", + "2 | Delegator [1/2] : cosmos102hty0jv2s29lyc4u0tv97z9v298e24t", + "2 | Delegator [2/2] : 3vwtpl", + "3 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", + "3 | Validator [2/2] : 9m5s03xfytvz7", + "4 | Memo : Delegated with Ledger from union.market", + "5 | Fee : 0.005000 ATOM", + "6 | Gas : 200000" + ], + "expert": false }, { "name": "badcase", @@ -488,13 +927,14 @@ "expected": [ "0 | Chain ID : 1234567890AB", "1 | fee : 1234" - ] + ], + "expert": true }, { "name": "badcase2", "tx": { "account_number": "6571", - "chain_id": "cosmoshub-2", + "chain_id": "cosmoshub-3", "fee": { "amount": [ { @@ -523,19 +963,20 @@ "parsingErr": "No error", "validationErr": "No error", "expected": [ - "0 | Chain ID : cosmoshub-2", + "0 | Chain ID : cosmoshub-3", "1 | Account : 6571", "2 | Sequence : 0", - "3 | Fee : 5000 uatom", - "4 | Gas : 200000", - "5 | Memo : Delegated with Ledger from union.market", - "6 | Type : Delegate", - "7 | Amount : 1000000 uatom", - "8 | Delegator [1/2] : cosmos102hty0jv2s29lyc4u0tv97z9v298e24t", - "8 | Delegator [2/2] : 3vwtpl", - "9 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", - "9 | Validator [2/2] : 9m5s03xfytvz7" - ] + "3 | Type : Delegate", + "4 | Amount : 1000000 uatom", + "5 | Delegator [1/2] : cosmos102hty0jv2s29lyc4u0tv97z9v298e24t", + "5 | Delegator [2/2] : 3vwtpl", + "6 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", + "6 | Validator [2/2] : 9m5s03xfytvz7", + "7 | Memo : Delegated with Ledger from union.market", + "8 | Fee : 5000 uatom", + "9 | Gas : 200000" + ], + "expert": true }, { "name": "oldStackOverflow", @@ -571,13 +1012,115 @@ "parsingErr": "No error", "validationErr": "JSON Missing chain_id", "expected": [ - ] + ], + "expert": true + }, + { + "name": "groupingSmallExpert", + "tx": { + "account_number": "108", + "chain_id": "cosmoshub-3", + "fee": { + "amount": [ + { + "amount": "600", + "denom": "uatom" + } + ], + "gas": "200000" + }, + "memo": "", + "msgs": [ + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper1qwl879nx9t6kef4supyazayf7vjhennyh568ys" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper1x88j7vp2xnw3zec8ur3g4waxycyz7m0mahdv3p" + } + } + ], + "sequence": "106" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Chain ID : cosmoshub-3", + "1 | Account : 108", + "2 | Sequence : 106", + "3 | Type : Withdraw Reward", + "4 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "4 | Delegator [2/2] : hgqhwh", + "5 | Validator [1/2] : cosmosvaloper1qwl879nx9t6kef4supyazayf7", + "5 | Validator [2/2] : vjhennyh568ys", + "6 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "6 | Delegator [2/2] : hgqhwh", + "7 | Validator [1/2] : cosmosvaloper1x88j7vp2xnw3zec8ur3g4waxy", + "7 | Validator [2/2] : cyz7m0mahdv3p", + "8 | Fee : 600 uatom", + "9 | Gas : 200000" + ], + "expert": true }, { - "name": "grouping", + "name": "groupingSmall", "tx": { "account_number": "108", - "chain_id": "cosmoshub-2", + "chain_id": "cosmoshub-3", + "fee": { + "amount": [ + { + "amount": "600", + "denom": "uatom" + } + ], + "gas": "200000" + }, + "memo": "", + "msgs": [ + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper1qwl879nx9t6kef4supyazayf7vjhennyh568ys" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos14lultfckehtszvzw4ehu0apvsr77afvyhgqhwh", + "validator_address": "cosmosvaloper1x88j7vp2xnw3zec8ur3g4waxycyz7m0mahdv3p" + } + } + ], + "sequence": "106" + }, + "parsingErr": "No error", + "validationErr": "No error", + "expected": [ + "0 | Type : Withdraw Reward", + "1 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "1 | Delegator [2/2] : hgqhwh", + "2 | Validator [1/2] : cosmosvaloper1qwl879nx9t6kef4supyazayf7", + "2 | Validator [2/2] : vjhennyh568ys", + "3 | Validator [1/2] : cosmosvaloper1x88j7vp2xnw3zec8ur3g4waxy", + "3 | Validator [2/2] : cyz7m0mahdv3p", + "4 | Fee : 0.000600 ATOM", + "5 | Gas : 200000" + ], + "expert": false + }, + { + "name": "groupingExpert", + "tx": { + "account_number": "108", + "chain_id": "cosmoshub-3", "fee": { "amount": [ { @@ -623,38 +1166,36 @@ "parsingErr": "No error", "validationErr": "No error", "expected": [ - "0 | Chain ID : cosmoshub-2", + "0 | Chain ID : cosmoshub-3", "1 | Account : 108", "2 | Sequence : 106", - "3 | Fee : 600 uatom", - "4 | Gas : 200000", - "5 | Type : Withdraw Reward", + "3 | Type : Withdraw Reward", + "4 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "4 | Delegator [2/2] : hgqhwh", + "5 | Validator [1/2] : cosmosvaloper1qwl879nx9t6kef4supyazayf7", + "5 | Validator [2/2] : vjhennyh568ys", "6 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", "6 | Delegator [2/2] : hgqhwh", - "7 | Validator [1/2] : cosmosvaloper1qwl879nx9t6kef4supyazayf7", - "7 | Validator [2/2] : vjhennyh568ys", - + "7 | Validator [1/2] : cosmosvaloper1x88j7vp2xnw3zec8ur3g4waxy", + "7 | Validator [2/2] : cyz7m0mahdv3p", "8 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", "8 | Delegator [2/2] : hgqhwh", - "9 | Validator [1/2] : cosmosvaloper1x88j7vp2xnw3zec8ur3g4waxy", - "9 | Validator [2/2] : cyz7m0mahdv3p", - + "9 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", + "9 | Validator [2/2] : 9m5s03xfytvz7", "10 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", "10 | Delegator [2/2] : hgqhwh", - "11 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", - "11 | Validator [2/2] : 9m5s03xfytvz7", - - "12 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", - "12 | Delegator [2/2] : hgqhwh", - "13 | Validator [1/2] : cosmosvaloper1ttfytaf43nkytzp8hkfjfgjc6", - "13 | Validator [2/2] : 93ky4t3y2n2ku" - ] + "11 | Validator [1/2] : cosmosvaloper1ttfytaf43nkytzp8hkfjfgjc6", + "11 | Validator [2/2] : 93ky4t3y2n2ku", + "12 | Fee : 600 uatom", + "13 | Gas : 200000" + ], + "expert": true }, { "name": "grouping_ledger_testcase", "tx": { "account_number": "108", - "chain_id": "cosmoshub-2", + "chain_id": "cosmoshub-3", "fee": { "amount": [ { @@ -700,35 +1241,36 @@ "parsingErr": "No error", "validationErr": "No error", "expected": [ - "0 | Chain ID : cosmoshub-2", + "0 | Chain ID : cosmoshub-3", "1 | Account : 108", "2 | Sequence : 106", - "3 | Fee : 600 uatom", - "4 | Gas : 200000", - "5 | Type : Withdraw Reward", + "3 | Type : Withdraw Reward", + "4 | Delegator [1/2] : cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl8", + "4 | Delegator [2/2] : 7jycah", + "5 | Validator [1/2] : cosmosvaloper1kn3wugetjuy4zetlq6wadchfh", + "5 | Validator [2/2] : vu3x740ae6z6x", "6 | Delegator [1/2] : cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl8", "6 | Delegator [2/2] : 7jycah", - "7 | Validator [1/2] : cosmosvaloper1kn3wugetjuy4zetlq6wadchfh", - "7 | Validator [2/2] : vu3x740ae6z6x", + "7 | Validator [1/2] : cosmosvaloper1sjllsnramtg3ewxqwwrwjxfgc", + "7 | Validator [2/2] : 4n4ef9u2lcnj0", "8 | Delegator [1/2] : cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl8", "8 | Delegator [2/2] : 7jycah", - "9 | Validator [1/2] : cosmosvaloper1sjllsnramtg3ewxqwwrwjxfgc", - "9 | Validator [2/2] : 4n4ef9u2lcnj0", + "9 | Validator [1/2] : cosmosvaloper1ey69r37gfxvxg62sh4r0ktpuc", + "9 | Validator [2/2] : 46pzjrm873ae8", "10 | Delegator [1/2] : cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl8", "10 | Delegator [2/2] : 7jycah", - "11 | Validator [1/2] : cosmosvaloper1ey69r37gfxvxg62sh4r0ktpuc", - "11 | Validator [2/2] : 46pzjrm873ae8", - "12 | Delegator [1/2] : cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl8", - "12 | Delegator [2/2] : 7jycah", - "13 | Validator [1/2] : cosmosvaloper1648ynlpdw7fqa2axt0w2yp3fk", - "13 | Validator [2/2] : 542junl7rsvq6" - ] + "11 | Validator [1/2] : cosmosvaloper1648ynlpdw7fqa2axt0w2yp3fk", + "11 | Validator [2/2] : 542junl7rsvq6", + "12 | Fee : 600 uatom", + "13 | Gas : 200000" + ], + "expert": true }, { "name": "massive", "tx": { "account_number": "108", - "chain_id": "cosmoshub-2", + "chain_id": "cosmoshub-3", "fee": { "amount": [ { @@ -858,76 +1400,77 @@ "parsingErr": "No error", "validationErr": "No error", "expected": [ - "0 | Chain ID : cosmoshub-2", + "0 | Chain ID : cosmoshub-3", "1 | Account : 108", "2 | Sequence : 106", - "3 | Fee : 600 uatom", - "4 | Gas : 200000", - "5 | Type : Withdraw Reward", + "3 | Type : Withdraw Reward", + "4 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", + "4 | Delegator [2/2] : hgqhwh", + "5 | Validator [1/2] : cosmosvaloper1qwl879nx9t6kef4supyazayf7", + "5 | Validator [2/2] : vjhennyh568ys", "6 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", "6 | Delegator [2/2] : hgqhwh", - "7 | Validator [1/2] : cosmosvaloper1qwl879nx9t6kef4supyazayf7", - "7 | Validator [2/2] : vjhennyh568ys", + "7 | Validator [1/2] : cosmosvaloper1x88j7vp2xnw3zec8ur3g4waxy", + "7 | Validator [2/2] : cyz7m0mahdv3p", "8 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", "8 | Delegator [2/2] : hgqhwh", - "9 | Validator [1/2] : cosmosvaloper1x88j7vp2xnw3zec8ur3g4waxy", - "9 | Validator [2/2] : cyz7m0mahdv3p", + "9 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", + "9 | Validator [2/2] : 9m5s03xfytvz7", "10 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", "10 | Delegator [2/2] : hgqhwh", - "11 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", - "11 | Validator [2/2] : 9m5s03xfytvz7", + "11 | Validator [1/2] : cosmosvaloper1ttfytaf43nkytzp8hkfjfgjc6", + "11 | Validator [2/2] : 93ky4t3y2n2ku", "12 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", "12 | Delegator [2/2] : hgqhwh", - "13 | Validator [1/2] : cosmosvaloper1ttfytaf43nkytzp8hkfjfgjc6", - "13 | Validator [2/2] : 93ky4t3y2n2ku", + "13 | Validator [1/2] : cosmosvaloper1wdrypwex63geqswmcy5qynv4w", + "13 | Validator [2/2] : 3z3dyef2qmyna", "14 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", "14 | Delegator [2/2] : hgqhwh", - "15 | Validator [1/2] : cosmosvaloper1wdrypwex63geqswmcy5qynv4w", - "15 | Validator [2/2] : 3z3dyef2qmyna", + "15 | Validator [1/2] : cosmosvaloper102ruvpv2srmunfffxavttxnhe", + "15 | Validator [2/2] : zln6fnc54at8c", "16 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", "16 | Delegator [2/2] : hgqhwh", - "17 | Validator [1/2] : cosmosvaloper102ruvpv2srmunfffxavttxnhe", - "17 | Validator [2/2] : zln6fnc54at8c", + "17 | Validator [1/2] : cosmosvaloper10e4vsut6suau8tk9m6dnrm0sl", + "17 | Validator [2/2] : gd6npe3jx5xpv", "18 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", "18 | Delegator [2/2] : hgqhwh", - "19 | Validator [1/2] : cosmosvaloper10e4vsut6suau8tk9m6dnrm0sl", - "19 | Validator [2/2] : gd6npe3jx5xpv", + "19 | Validator [1/2] : cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkf", + "19 | Validator [2/2] : v8z992ax69k08", "20 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", "20 | Delegator [2/2] : hgqhwh", - "21 | Validator [1/2] : cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkf", - "21 | Validator [2/2] : v8z992ax69k08", + "21 | Validator [1/2] : cosmosvaloper1ssm0d433seakyak8kcf93yefh", + "21 | Validator [2/2] : knjleeds4y3em", "22 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", "22 | Delegator [2/2] : hgqhwh", - "23 | Validator [1/2] : cosmosvaloper1ssm0d433seakyak8kcf93yefh", - "23 | Validator [2/2] : knjleeds4y3em", + "23 | Validator [1/2] : cosmosvaloper13sduv92y3xdhy3rpmhakrc3v7", + "23 | Validator [2/2] : t37e7ps9l0kpv", "24 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", "24 | Delegator [2/2] : hgqhwh", - "25 | Validator [1/2] : cosmosvaloper13sduv92y3xdhy3rpmhakrc3v7", - "25 | Validator [2/2] : t37e7ps9l0kpv", + "25 | Validator [1/2] : cosmosvaloper15urq2dtp9qce4fyc85m6upwm9", + "25 | Validator [2/2] : xul3049e02707", "26 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", "26 | Delegator [2/2] : hgqhwh", - "27 | Validator [1/2] : cosmosvaloper15urq2dtp9qce4fyc85m6upwm9", - "27 | Validator [2/2] : xul3049e02707", + "27 | Validator [1/2] : cosmosvaloper14kn0kk33szpwus9nh8n87fjel", + "27 | Validator [2/2] : 8djx0y070ymmj", "28 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", "28 | Delegator [2/2] : hgqhwh", - "29 | Validator [1/2] : cosmosvaloper14kn0kk33szpwus9nh8n87fjel", - "29 | Validator [2/2] : 8djx0y070ymmj", + "29 | Validator [1/2] : cosmosvaloper14lultfckehtszvzw4ehu0apvs", + "29 | Validator [2/2] : r77afvyju5zzy", "30 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", "30 | Delegator [2/2] : hgqhwh", - "31 | Validator [1/2] : cosmosvaloper14lultfckehtszvzw4ehu0apvs", - "31 | Validator [2/2] : r77afvyju5zzy", + "31 | Validator [1/2] : cosmosvaloper1k9a0cs97vul8w2vwknlfmpez6", + "31 | Validator [2/2] : prv8klv03lv3d", "32 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", "32 | Delegator [2/2] : hgqhwh", - "33 | Validator [1/2] : cosmosvaloper1k9a0cs97vul8w2vwknlfmpez6", - "33 | Validator [2/2] : prv8klv03lv3d", + "33 | Validator [1/2] : cosmosvaloper1kj0h4kn4z5xvedu2nd9c4a9a5", + "33 | Validator [2/2] : 59wvpuvu0h6qn", "34 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", "34 | Delegator [2/2] : hgqhwh", - "35 | Validator [1/2] : cosmosvaloper1kj0h4kn4z5xvedu2nd9c4a9a5", - "35 | Validator [2/2] : 59wvpuvu0h6qn", - "36 | Delegator [1/2] : cosmos14lultfckehtszvzw4ehu0apvsr77afvy", - "36 | Delegator [2/2] : hgqhwh", - "37 | Validator [1/2] : cosmosvaloper1hjct6q7npsspsg3dgvzk3sdf8", - "37 | Validator [2/2] : 9spmlpfdn6m9d" - ] + "35 | Validator [1/2] : cosmosvaloper1hjct6q7npsspsg3dgvzk3sdf8", + "35 | Validator [2/2] : 9spmlpfdn6m9d", + "36 | Fee : 600 uatom", + "37 | Gas : 200000" + ], + "expert": true } ] diff --git a/tests/tx_parse.cpp b/tests/tx_parse.cpp index 3378cbc6..973717dd 100644 --- a/tests/tx_parse.cpp +++ b/tests/tx_parse.cpp @@ -131,7 +131,7 @@ namespace { parser_error_t err = JSON_PARSE(&parser_tx_obj.json, parser_tx_obj.tx); EXPECT_EQ(err, parser_ok); - uint16_t numItems; + uint8_t numItems; tx_display_numItems(&numItems); EXPECT_EQ(1, numItems) << "Wrong number of items"; @@ -145,7 +145,7 @@ namespace { parser_error_t err = JSON_PARSE(&parser_tx_obj.json, parser_tx_obj.tx); EXPECT_EQ(err, parser_ok); - uint16_t numItems; + uint8_t numItems; tx_display_numItems(&numItems); EXPECT_EQ(10, numItems) << "Wrong number of items"; } @@ -159,7 +159,7 @@ namespace { parser_error_t err = JSON_PARSE(&parser_tx_obj.json, parser_tx_obj.tx); EXPECT_EQ(err, parser_ok); - uint16_t numItems; + uint8_t numItems; tx_display_numItems(&numItems); EXPECT_EQ(22, numItems) << "Wrong number of items"; } diff --git a/tests/ui_output.cpp b/tests/ui_output.cpp index 04c2cc09..131622c9 100644 --- a/tests/ui_output.cpp +++ b/tests/ui_output.cpp @@ -21,6 +21,7 @@ #include #include "common/parser.h" #include "util/common.h" +#include "common/app_mode.h" using ::testing::TestWithParam; using ::testing::Values; @@ -46,6 +47,8 @@ void check_testcase(const testcase_t &tc) { parser_context_t ctx; parser_error_t err; + app_mode_set_expert(tc.expert); + const auto *buffer = (const uint8_t *) tc.tx.c_str(); size_t bufferLen = tc.tx.size(); diff --git a/tests/util/common.cpp b/tests/util/common.cpp index 22dde7de..795e55f4 100644 --- a/tests/util/common.cpp +++ b/tests/util/common.cpp @@ -21,7 +21,7 @@ std::vector dumpUI(parser_context_t *ctx, uint16_t maxKeyLen, uint16_t maxValueLen) { - uint16_t numItems; + uint8_t numItems; parser_error_t err = parser_getNumItems(ctx, &numItems); auto answer = std::vector(); diff --git a/tests/util/testcases.cpp b/tests/util/testcases.cpp index 7b484d2e..54d4d8b9 100644 --- a/tests/util/testcases.cpp +++ b/tests/util/testcases.cpp @@ -53,6 +53,9 @@ std::vector GetJsonTestCases(const std::string &filename) { expected.push_back(j.asString()); } + bool expert = false; + expert = v["expert"].asBool(); + answer.push_back( testcase_t{ v["name"].asString(), @@ -60,6 +63,7 @@ std::vector GetJsonTestCases(const std::string &filename) { v["parsingErr"].asString(), v["validationErr"].asString(), expected, + expert }); } diff --git a/tests/util/testcases.h b/tests/util/testcases.h index 0ebd3cdf..ab29ad2d 100644 --- a/tests/util/testcases.h +++ b/tests/util/testcases.h @@ -23,6 +23,7 @@ typedef struct { std::string parsingErr; std::string validationErr; std::vector expected; + bool expert; } testcase_t; std::vector GetJsonTestCases(const std::string& filename); diff --git a/tests_zemu/snapshots-tmp/show-address-and-sign-basic/.gitkeep b/tests_zemu/snapshots-tmp/show-address-and-sign-basic/.gitkeep new file mode 100644 index 00000000..33662f55 --- /dev/null +++ b/tests_zemu/snapshots-tmp/show-address-and-sign-basic/.gitkeep @@ -0,0 +1 @@ +/* diff --git a/tests_zemu/snapshots-tmp/sign-expert/.gitkeep b/tests_zemu/snapshots-tmp/sign-expert/.gitkeep new file mode 100644 index 00000000..33662f55 --- /dev/null +++ b/tests_zemu/snapshots-tmp/sign-expert/.gitkeep @@ -0,0 +1 @@ +/* diff --git a/tests_zemu/snapshots/show-address-and-sign-basic/0.png b/tests_zemu/snapshots/show-address-and-sign-basic/0.png new file mode 100644 index 0000000000000000000000000000000000000000..542ff64ce1cb31b60c7c8cf80897925df4f0a013 GIT binary patch literal 824 zcmXX_eMl2=7~T|4vt1H??h>4uQ_4)vn;L#FJ!g&OtSD5>Y;fot=0^otjPir7M5P;| zahTs>+sgFlBFuzryH#s^L=QN}?3J_b0-QvoRF-wnd2-4fWvISlRiWH$98|K3i83L$ zve7ZaZcE3m0rf&PR_2U87!OjEVYCGA(?!(twG!M5C5uhzE&~cMs$#;}8Z_O=>+`T{ z3zY%v+&Ezw)^X6lPXeQ6Uf(TdH*c;^60`0|ib+Sl#6L^tzUP+{&0%#X1HI?fm%URzH< zN!KVqt|(;NBYrs(Eqessl|j=*hoWsoprq+A z(i(0iVsMS1We8|9C+Nz&YX?)xQ7;@SfZ+IUQwUm4YppR5c47sLNh`F1S|H}T z?&^FTm9}cLlgV2JN~n%Y3Xt9E4UFNwMv^gnsL4EFQ4yZ A-2eap literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/show-address-and-sign-basic/1.png b/tests_zemu/snapshots/show-address-and-sign-basic/1.png new file mode 100644 index 0000000000000000000000000000000000000000..88bf23eee0225dde16f281ac2590a4dda4e561b6 GIT binary patch literal 847 zcmX9-e@GKy7~WhQWxGO(ayPTheh{TO&YP5`IB%O-wlXWVRyH`sT!D^7tdg~?ZVk<5 zWp!9lEh)5YutEh(>QlJtK%E+5Qe2SzV`&-`6xn<0kGt>QkMDWj=Xu`y0=p~)csvm= zlgaSHt;XH3`ry&b$KXAdQoTkdQ!Fbq8uobPUtbpQR+ld|zATA%&Mj=eMD$%0&o^Jw zk!9q=jJ~;(YXit#+`nn4ynnUJlN|S^{J^ny8HGu=QJ~^SJt|4q_3g2Zw~pwT;{+?v zlqN!@lG!-TvN+cw=T2q0s_^ZoV<WKAg^dWaA|`@DBKd&`r-djQExh&4VgZ4f`RHCSJ5H`56rZA*FFQ2|F0?c8pu0 zV*Xwb8ibyrye|KLDl=k*Xjq#8{-jUGXiN!sv{RG(fR4!|GMQ_v)}0>r<^awoZ3I6R z?KZxq3u8WHecz`b{TrCS>$#qMmhG5#wG@tc!x(eumS@B#i2f^g#gRZMNhNtZrOjVW zO5DG)RnAo`SXZUjxFZnB&dNmcqXnfzAfj=Tf~~>&&Y(5Es=k z8=Ym0W3;gxDhmqUwZ#%0tZr1|*^qI8Bp4{}L)w*|>k=UMQ*Ro?2a?q|rR3_QltVUt zyeKxNpaJ7dc6wqx*9PM?D{#s>oQG1*Dr01g20K;HYG;enYu0dsVJ)q|>4~@c?l!4y zv%cnr;J72_Ah%2+S?mqHuGx=*hKcuJCicH9g)gml&}h=fXvDGTR@CQsUf-gR(oQU^*sz(H3l&YwmtOVcu(FkGk6-hRxJ@1n;P$ zTu?*SUKi8E0;n@-tfAHu=nA6NS&29~M4z=3ldjQ8GnOYJ%gli76RBzC3E~ zk(yAsq>X##%7<&)j%PVMyWH(>1XOKdlz(!M>Sqt$6E&tkyxQ8{HiE2b_>{tXV$aor zNtUK_Qs((M+?-~5W188GY2O|#H}Ns5U!6Efwr!MaQ_7|8;SFp105ng zGx)Gd@kN7^HOs~A7c$OV$vAU1TgoyFlqWS|CJa@sW|WkgV0qGq_e6||LhGxY}( zqHjIul*(A}u->t)EtuKJKJkN#(TP9VKs_lL6M29Ne~rAc}Vh+7q(l TbhQsKG8sHw{an^LB{Ts5^#SF` literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/show-address-and-sign-basic/12.png b/tests_zemu/snapshots/show-address-and-sign-basic/12.png new file mode 100644 index 0000000000000000000000000000000000000000..3bfe48e647c2265d038e08ea82adb81aaa730150 GIT binary patch literal 400 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFffLDx;TbZFupzLm?vY%zp_e9O|xE(Jpb*soNWt{o=Qp1^W zrc+CGcYRUnJf7k3ES#5n=E{e@&kUs|{QPpz`>?nE8@Fd`6BR#O7?~)1{&cXrM`}Xl zo;J_4tRJDwM*TqT%MYqcO3C>@W0y=3GdgkMg9Ok9(;r@KZI8JnrSjG@&V0`@WqCHx z>a2(pKD_xx{;CUPZcE8TOUp$6esE6eUWyp+nu(ohzKX-h9bb++ek^fanoy(6enpd!B@;r}6FAF&|)N?O-`m22WB4PIL zg77@Ku7;v)Ugu}AK)fLF#2r^=qs57e#TN`xG9pgAk(#h`X>WA)4bwlxQes9Y6pW?L zO_BuDV!YeG9P+>XVO`s1sR;`cPxQ34o&o9rQSsS7PMYPde7GR-xj3j%;g|;+p{H% z?gP!-Zgwxr;n_=Bqj1$?Q6rN*stfjj_~O|P&#oR@&6YB+&Qek;E_ctQg%4j&2Mf+m z$*B0XRBFP{$qk>vn2o}L2ESXDsQ!6n+uSo55=QaYHcY;0$}==zgH^{!2i2xILRAzHVhA(0dXfmxA204jRA~K##xAa%gL7Zc{7{JKT82>6tUH z^~~&>JJdjd!YehOf0 zoo4goZXMeT4D#i_1q0^ET|0K!`F2HB#(&0kThFM^=NW;int{R7)z4*}Q$iB}nCe#@ literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/show-address-and-sign-basic/3.png b/tests_zemu/snapshots/show-address-and-sign-basic/3.png new file mode 100644 index 0000000000000000000000000000000000000000..637c1acf473228fb7d38b3aae12b69556c5a2ead GIT binary patch literal 569 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfcKAx;TbZFupw)op*;r#QC7} z!8`w(ejYT@JJxhHi~aF8<<2EqALrlmcvPhKir0MC%U%_|=ZXvNY;iT;JMqX<8L@jW zc&7&o2J|gSbbqGF>-=nmSfAH3XQ>Q}+2ZGxBs!;=tdVLvvno-wScN-XshP)UapH+J zDHCo^uv{hHw<>Y@!%bfg1)j_?;hA}{pt1GzO5HiFXEGKfx~G|3C=g>hwU;eLkNr}^ znZ2#myeDG#+}hfzx${jGiY+xHrS3&lTx~e>^g&8S#numAZEdFynkszGdZ;TYrN<7G zhz9H4=W=*L`6eAyty?Qq+f%Tg0|ZvW76 zW_retwQc1m-vCw1W!=b>FggKJFFW(1LCOS4UTGt1kbe}6rQ{|_@;bB4y!gRnrn}fb zR$ggde$$%HHvh~Y8`|s>AMQ3$_`HrSMQ+khUCER(p!X+A@&dg)^~~}Ue^!hAD+7jG zCPaS#(79fRC;Yq*bgzH(4?f<-iB~sdN${?2JAJ|q0%7A_p1w|r}XaNeA*cA7l@! z_|II-_L_TSDZs+%0Bg1qiQCikcWuuYE{T~Ugf(c=3l59UW zw2K`5b=rAW(%)wfm#MiVUpSYKNtQK4U5dua3C`^AVb3>L&GAoG%PYp!-34F z8T<(vCYh<>h72+X4aiIl$lPd{WTu82GRS2!za4%sD`J= zyp#+LO9L`5Aw$E`fXpWuygQk@WEL4@9wu{_%p!x#r@HViWN4sak(p#@pkb0(8V+P0 zB0~cWlg!d^AoFPilt(n&C3Db#%+#>RZ0}?63Nkljw)Z!9mdw&X!$HIL4I1uh_-gPB znWbTpIgr`DfeZ}?4aj^oc!h>ZW@*^InG6k612TU%comtYVS5uYG$3=&;1y(+hDqij z4KyqbH2f}~42^twH#C7)?KGk=~smF=0fNrC0zr##b*&xD6b?U?hh zsO{&ee$)7cjXzH*3r zjexvKAiIo>W?yJH6D;h1^1_BlBNHHdor%KdJDtx{%2p;$kW9&tNU2kqd40|et(oR0 z_b7Z;o4IzD#hY0TMK{)VR%b?JE=XLOc=5xsw*A*Wyp+mV(AcJby})1iyOQ(a*-{fC zPw?>GpT}c#;=%@1^?gpvMkYE^Vn*F%?V1MR0OgH=>bNDPCM;wG6EpcUfyy?(xIi(G-P%S#&pRKs zoqJlrX7(o`=f|l1>AS{@;CO(L>gm^=g|lerO4QR{^R7IiQ9c=%md+b7q#G zyzoH}Y!EO|%8MUaXGEO%Gm(e4e(l3xVDO%(@i@FYCF6$<)2YPmYJ9uR3U6%fJiN|V trY!f-hHT;X*x43uZXHRyjw1Ee{&B(QDBH~zbAbtl!PC{xWt~$(696`102KfL literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/show-address-and-sign-basic/7.png b/tests_zemu/snapshots/show-address-and-sign-basic/7.png new file mode 100644 index 0000000000000000000000000000000000000000..bcea849f4df186a3f2ceaab80fc8cd232e1b2afb GIT binary patch literal 1033 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 LNkvXXu0mjfDjU;# delta 495 zcmV8^K@m_CPqa@Q8!^-dDgXIS{6cT68eK)H&B4Vmd@-MET|mCW?(-1t*8 z3^GH*hRmt~85&kHGz>CB!yq#>Y{VN@mrtBZJIF1Aj8B24r>`Rx+!G9T{Za z(Zba=Jk+q!K*J#o4>fEw(D06K9M$j=nN!KoFf<@@3K<%P24vpJ!qv$aZEHX>Ofy}2F{0SN+nW^E13^E4|$bU=?$lPd{WTu82GRS2!za4%sD`J=yp#+LO9L`5Aw$E`fXpWuygQk@WEL4@9wu{_ z%p!x#r@HViWN4sak(p#@pkb0(8V+P0B0~cWlg!d^AoFPilt(n&C3Db#%+#>RZ0}?6 z3Nkljw)Z!9mVeCBK*K@9_6-{DYWQmK44I{2k~xsszJUx42Mx%4HF$-FNoHx-zL^XS zQv))8H+U79rD1y$GBhA_&)^kgmWD~@Aq_Mv4K(~K4K!>&A@ehXXEYo%OfpNu_P#DW zL*_u{py6d2CK(zgnS+K!X8X0lD>U3__$>`I{1%z*F-{kr(XceUgv`|Nlm=wBe>T`o zZSbjNwqLvOD4B27fD8>c8n(v>DBEcbwo@Byr#9G5ZLpo%U^}%RtUk97XS+>;00000 LNkvXXu0mjfDjU;# delta 495 zcmV8^K@m_CPqa@Q8!^-dDgXIS{6cT68eK)H&B4Vmd@-MET|mCW?(-1t*8 z3^GH*hRmt~85&kHGz>CB!yq#>Y{VN@mrtBZJIF1Aj8B24r>`Rx+!G9T{Za z(Zba=Jk+q!K*J#o4>fEw(D06K9M$j=nN!KoFf<@@3K<%P24vpJ!qv$}~42^twH#C7)?KGk=~smF=0fNrC0zr##b*&xD6b?U?hh zsO{&ee$)7cjXzH*3r zjexvKAiIo>W?yJH6D;h1^1_BlBNHHdor%KdJDtx{%2p;$kW9&tNU2kqd40|et(oR0 z_b7Z;o4IzD#hY0TMK{)VR%b?JE=XLOc=5xsw*A*Wyp+mV(AcJby})1iyOQ(a*-{fC zPw?>GpT}c#;=%@1^?gpvMkYE^Vn*F%?V1MR0OgH=>bNDPCM;wG6EpcUfyy?(xIi(G-P%S#&pRKs zoqJlrX7(o`=f|l1>AS{@;CO(L>gm^=g|lerO4QR{^R7IiQ9c=%md+b7q#G zyzoH}Y!EO|%8MUaXGEO%Gm(e4e(l3xVDO%(@i@FYCF6$<)2YPmYJ9uR3U6%fJiN|V trY!f-hHT;X*x43uZXHRyjw1Ee{&B(QDBH~zbAbtl!PC{xWt~$(696`102KfL literal 461 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfdN>ba4!+V0?QpFz>d5fXl(_ z+~5B4+sMnd8L%meuoo#cCzY)}(*8!}HF6Uz5~7OXkT$GcFe*rV}`;?d4CJZ5zhWy{qr z2PqU!$ken`1u{PGzx_j@^M6>^)9-AXCpe~AO5gGAdRl7s&ZSLcNrsWX=SN+j&38H* zHuz+3@W}-s8?TSyK{mRqnjVEqd!I@f>9QJy-`Ek{5F(nE_6)HJ*0c|mvIc?F& zEh4_lmO6k@hSB@H$6A^bEIk{2yc>NcS1N#9;&CwuL|;ykJF!LNgpDgu>9e`K?^KR% z_b8kxav~pHp8tIL0BhEe^LMJHn%9U_*QMl3TUguXE!-Z`gGzqO(m7)=bGu6{1- HoD!Maa(|1lwBbUb0dbl9V{EmcH;_28Y^Lc3OfH>^3t>uw`qZ3 zz=dJ<41)6ty5)qR9l=FmjM$jaFo}9Ix#?vqRoKvON2thL!}sPtC}UK$Li)u zARl`SEe5@tJMZRw9mvAlixi`p!+HJp`sfW)j7nnYAcuP#*o;q@VrU>Z? zGp$`xq$YDPAp51k*$J0oBqm^8#V>$IngJe4 zm*eSX1)0g0IDGV}w>+s7VLpxTNubSVG$PnBsNI0=gWKduYsX_h{Pee=%^%?Kzs-w( zTrDfXuO!l&q546~@8^P(qr6ZqjL*p&YTub|u0ZBeCGRAG>pzA)We$BrHG8Qm3U=eA zq}tG&NTEC_7qp0~q{gUgn@|pK)Jl}XCiimym0Ir#FwN}c$?i87*CDeB3_}GM6Ts^+ z9uc4p1kB75JiGwOzzE=D(3#YsF}sfR8KBiba-E=M&ouhgCu1M+r1e^-bxt_)#exTz zyk{jkt%6g@F7`Y^PJ`&CsI=rwc^H*&cCycbrurjlIC6p~D_gMC(anT`c&NMR47e~w zCF3duV-Qj)**%+fIl&P#%Sr>U<>U4z>V~{!;++c>>AS;gHb1{-?!@wEst-*CCmT2< zCEZ3r_O5zk4Pfu6H8uuiAUGW@y2g?RM}u0U0De72Va6cC5wYvY$Ehpyc~N_RqX3J*#dyR%eh3c`dl_eNeVSH5E?1afljMN>WO&Uq zxnPO~GHloPys`MQFb%rpLMu=@G_o*sYU#Whp4VbMbVP*{>C%rX{@~Iw>rs)-D`q5#iF)&BO_K=mG33`; zHbXNlXk&MG{F7tnk^(*oG`|1y%^M2PSOK@O?$OtJZ;? Q7a4%S)78&qol`;+075E^rT_o{ diff --git a/tests_zemu/snapshots/sign-basic/10.png b/tests_zemu/snapshots/sign-basic/10.png deleted file mode 100644 index dcff32de30660c06bd64b35910f398d90342719c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1040 zcmXYwe@GKy7{_;AoK`!P`eW@XU9&X`vp8?`lhStnSkv{>Us;)OjMK`j1zK5*pbJ6R zWwtnyEVQUuSxB^SC~}pzILL}6n1v^~{)o!5D1)-+o&9m|-M#PoywCIfe4n>rn=6-` zP0iNnbfhDX-2rbeJf?U8#vyZ!MW@3D9c7pB}l=qej( zZop=VSG$iGdcR9cJ_ocj>Saot^izJ7pHCYo@mfW=QLs$J=eB;$fBrjvZmq@h(yPig z)5J2a2&b7ne5PBuO>m--^c~F`o%$WyReqx)9J+}@=g>@xGO&sgRKA4nfY~k4+N0wu zPdzQJ%5*cKo0#;X#+*TkMp#q^yeiDvOkLbxHm)~sw2ZIYDMzTDvjmsUm?jDndKO5l z(ZNcK(kL9hj!B)z#;xZk55Zz2A^DERy2AtaxO4@EBDtxQTv;`rMn4-1a1i5Hi<7pU zt|gTg#^ixleSqMSwUPdQn_fz+tYfgT{_>9vo3e1y3X#7=Ve6~$073D=QrULp*IUq> zM5er~c>rp&qWco=M(%MrD2vmLH2zQNbSt4-m^6n(Y8*J-ff*?PWhFbImNo!z3mEgk zNS}=o*H%ueO-BTC8ww%{e9n-A_r z8pTK51oy6hkWQW$P3FXF+Rm`aE*Elq5WT1zoNUvaHYhE3(eMSxP`}(riO10wNbsuQ zw~{7ZAUMCCv_m3~fo>fep=c(}$W0qK%nE`9<$oAiHQgnhO%T+=q)ChI@|~H@DN|6S zn^cDPBGpQ0aNefBRWqLXdSCaY0_fTUYtBz@C6(L4Vh@nqm>nYs0aki&U0pM9!09hb zJzWIX(~P;%TIG@=rLqEVB(KgEv0-dsfh2Hn8RVB1<$<_Lc1m}XA`PGTjKfgWp^$tzx YR^)zopZBAE1>AIOu~S&fG0Eq0*aoG7s=oPMRsDHZy>|w%G{&X78d={i`{OfL^0dW7cAEs&C$q~$C_EbAjCwm>xC!Q z3*D?17nO}6MNlowGNgg*56@U_T0>1yOxQm)2`o#K>UVR01h?C>-*fx@zTeOHIoH0j zA~FS~$Ye6Iq}X@>zB%})mnz`?XR7>w#_f>o3Y8EB=@Zgb~HI3;PYS7lJwUwm0R7x8qr zdTlvwU)jEOuO4 z|9m-VVI5uhDwM855L=PkJ25t6EyY-KL5d5+cO*s69uD4Sfp8|2rP6y)JGEEexWhzH zsQ~{;-1$4Y0Au{YIR$Fz#0|?fK7&kQ^7NTIJ1Zdj(jaKfpn$|CGLxdgi3c+n6I8`X zsH(M6OdWL5o0FS>-O5*v!VOXq_b@` z+N-);P|c=l+eYeBROsk=C1sY59|O}svKXmLlcIvAK#U8mGZ0i;YjS^Alv7f4vpsBv z;2HFEzzPi diff --git a/tests_zemu/snapshots/sign-basic/13.png b/tests_zemu/snapshots/sign-basic/13.png deleted file mode 100644 index 00e4c0099cc2934f778d34959041def7cb4112bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 687 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFff&Qx;TbZFupw)n0MQQ!R6p} zmf!!_-}U#E3Ghr?AbZ+O)lzZwk^H?Ak2squY`)xZX1CM?NvR2y!OTYf7Y$BcFgW?f zM8Wy6im{20(fKXk+NAG(aTDTKc4jsLswkiQa~fL;pQ7?;52@i{U z*i!7SG@S9@_=yW>%;w7l=8{tPu6|h7=Ex=~r8ZMPdqzrzL)!AhNtLRMPd`=io{(9^ zI5RzJXHD#3h0nUY>y;M7_+>xz;5Ct1^zgLQyK4@I-I#gHvlleB^|s|_-pE++&_mlj z)8Ux_Z~V1_?QAJ#;Bfjm;SA7HMUczd*i!m76t%Q1OaumC8N{0oZ9v_;Gv~6UWZcLE zIzU2-ZKi#eLt2)@vtTJTNhvdsF_lZEDg*s_rtb`p03ttTqxX|$N=*PFP&7CnRse>! z|3#1jpk2mm=eQoeE46P4M^%s6xRi5wGfX#MF8lUtiONirp4Mv9ylFe;wK@=iYEc6c{1h-5+GWBhU-7G-fy502h3=hmU8 gSH$#|Zv#rjSLW&%UGLwX4NQm(p00i_>zopr0PJHPr2qf` diff --git a/tests_zemu/snapshots/sign-basic/2.png b/tests_zemu/snapshots/sign-basic/2.png index 83ebf4072b245b644de623c4ca3d48fd76368a4d..c61097adc9b3af6146ee3d9538e3ff043df10c0f 100644 GIT binary patch literal 748 zcmY*XZAepL6yCaV6}xZ#iRo^lH$N66(z9>{O?O>5azkx)0dvSp`$=pG9Y9l&hAwpq#hv?)I>xETo`cqIFzDCDTXnIGhlnTbJc0;Om&V} zwGCEn*o#uP4omX7!RnqdflzWJ?9g8T{J+qt1 zjkw%WE*scg?oc^MP-CRvSBhO#d^$9s5m#_??1Dj?v%ic|kG)dPUY*lb%M%fn`JOtS z)YkO%3S{K9uyTkv2Ho*6fE&QsO*)ZEey0@8@0DV1@O36+P)3#`WTjXFcSOB`a*J_x zp%@{jUj4v;%CA(zY*YEQe4xc+p*QIH=N9XH4MN(R#(Pm*gVW<7FA8%n^8)uUg%i1b zP&7uWh}Hsl1QarWu~5ttt#c@)fxc&v^b{EA^QB$=^-Rj$XWSNeQU&L>=)@U;2fOY#2)G=a-uvml{SLm&osm-AbC25fI4)kt@ljRX z^NwE^_snm0?tXJhPI(G#SHAqO-&tt?(2Gs*;hi6KxM!{XXt$!$W zMo*d<Dr7ROU@h0g* ziBa6!boRHqMY_*S=Dk&JbmC3Ig>7aT(bhMBW~$9}Z4;S%<}xQoZglgut&X>EEy&FU lOG|HA3$%ePHycU#8@F0Xad!Up?%51L;OXk;vd$@?2>>o2qtgHY diff --git a/tests_zemu/snapshots/sign-basic/3.png b/tests_zemu/snapshots/sign-basic/3.png index 524e5c0c9a35d72c3bd6dcb084aabab873d4bef6..d0842b5cf2fdf7768e8fe99dbc536f8b8d4484ec 100644 GIT binary patch literal 1011 zcmYLIe@s(X6mC=Vn50kVT*P-3Y%5}%9gyo2Lik}>DcCepLR_#x(Izjo(8M$$Dn!Am z=}io{yo!OmU>F4*B-Kj9jdc^5Ym6m%!BU1ITO`er&OgMERM|8{Jr|}X@7{Orx#!;V zo$q}2LRDo2n~G8u3I%K4TUHH!4-Cy`49v4S_cnz>^^3Wzbbpg_{$6c$t?kRQd*7x` zFQpvZGVT8T>3mz}=g+(s@%@|@?P?^3E-=CxUej2u!WI>-*sW`|XiCmc*YOex)?Gc&DLhI<4rE?EJ3^;_Q)jVj_%C4z^O# zCapdQsyS6RKB$zs;Q)t3j;)zm_!{u`Dy3gthQvtv*m!iojUxxWYCKn|!Y3bld`CM%-r~n_M<>zl0*+!sT40L< z>UcH4eBv|^!U8u$cFw?RBesOa(!?MQ7DGQ3cQqIsP~xOV77H}svZ4QL-X%BNTg%i-+5zTqoM7u*pIq6Yj{tDIV1}aL(Jxqa_KN@TVUeAtG zPp5Ke9v#~z$5yl=fELm^{PDxu-X(TfC<#CGZ3B$@U;2m3?}1zZkJpZ58`{SH3Qd zmkFO7h5DyHYCSXQ(OQvMAFBzK-p_Pr>an?JPhOYevGJsi(UKeWDf4_BEN)53tmW;y zrdB>JY4Xh=9V2}pK7N1Zolve6JI}^5lbB|zvKo1FrTps@Ibm~k$z+ieIhqq9gZ4~N zJUS_9Vq{Q`QFNC$(6p3yX+`Nk$KVAmoEzh&PmTF@S|vho@O5O})!xvXg)gml&}h=fXvDGTR@CQsUf-gR(oQU^*sz(H3l&YwmtOVcu(FkGk6-hRxJ@1n;P$ zTu?*SUKic-Syxx&%h0ZL11pa z+JQL-IJtmUi>Pa8_yq*aS+Kyu(Xp_p=@AR-QJ6%<>Z2LChEdTVrN{n9@0km9>HNq& zGeMMa!o5=`-3}f`m=q^>bP0 Hl+XkKO(%?6 diff --git a/tests_zemu/snapshots/sign-basic/5.png b/tests_zemu/snapshots/sign-basic/5.png index f6fa06307b04002121f668c03b3737f1fc523bc7..610edeb1a7024e87430acf5c9f0dcd587144c1e8 100644 GIT binary patch literal 499 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfi`&ba4!+V0?SfG4H7xgY&`T z$DaS?Kf~&tccjVm7iXHFbmy+sN8+m{u<)L+aXZ|5rbueS&$(3(7C7@ys+hu-vdx*< zD4tWJecOu*%_?$>8E0;n@-tfAHu=nA6NS&29~M4z=3ldjQ8GnOYJ%gli76RBzC3E~ zk(yAsq>X##%7<&)j%PVMyWH(>1XOKdlz(!M>Sqt$6E&tkyxQ8{HiE2b_>{tXV$aor zNtUK_Qs((M+?-~5W188GY2O|#H}Ns5U!6Efwr!MaQ_7|8;SFp105ng zGx)Gd@kN7^HOs~A7c$OV$vAU1TgoyFlqWS|CJa@sW|WkgV0qGq_e6||LhGxY}( zqHjIul*(A}u->t)EtuKJKJkN#(TP9VKs_lL6M29Ne~rAc}Vh+7q(l TbhQsKG8sHw{an^LB{Ts5^#SF` literal 518 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfg9=ba4!+V0?SWrPOA6wzYXa^C>&$^-OQ3dCI#1q#L{K1R-mH@~rQW;Oz%_mdVf&Sajsl5r-G zUir`=ZPH9BHc6m?j9_A>f2Knk(C7?@HlR;I1juHGv@9U1STc8WR>cgkrRz=hG|e@h zEGhM`^UQ@0ZD-JX`yoxF1kFhIp_e9O|xE(Jpb*soNWt{o=Qp1^W zrc+CGcYRUnJf7k3ES#5n=E{e@&kUs|{QPpz`>?nE8@Fd`6BR#O7?~)1{&cXrM`}Xl zo;J_4tRJDwM*TqT%MYqcO3C>@W0y=3GdgkMg9Ok9(;r@KZI8JnrSjG@&V0`@WqCHx z>a2(pKD_xx{;CUPZcE8TOUp$6esE6eUWyp+Us;)OjMK`j1zK5*pbJ6R zWwtnyEVQUuSxB^SC~}pzILL}6n1v^~{)o!5D1)-+o&9m|-M#PoywCIfe4n>rn=6-` zP0iNnbfhDX-2rbeJf?U8#vyZ!MW@3D9c7pB}l=qej( zZop=VSG$iGdcR9cJ_ocj>Saot^izJ7pHCYo@mfW=QLs$J=eB;$fBrjvZmq@h(yPig z)5J2a2&b7ne5PBuO>m--^c~F`o%$WyReqx)9J+}@=g>@xGO&sgRKA4nfY~k4+N0wu zPdzQJ%5*cKo0#;X#+*TkMp#q^yeiDvOkLbxHm)~sw2ZIYDMzTDvjmsUm?jDndKO5l z(ZNcK(kL9hj!B)z#;xZk55Zz2A^DERy2AtaxO4@EBDtxQTv;`rMn4-1a1i5Hi<7pU zt|gTg#^ixleSqMSwUPdQn_fz+tYfgT{_>9vo3e1y3X#7=Ve6~$073D=QrULp*IUq> zM5er~c>rp&qWco=M(%MrD2vmLH2zQNbSt4-m^6n(Y8*J-ff*?PWhFbImNo!z3mEgk zNS}=o*H%ueO-BTC8ww%{e9n-A_r z8pTK51oy6hkWQW$P3FXF+Rm`aE*Elq5WT1zoNUvaHYhE3(eMSxP`}(riO10wNbsuQ zw~{7ZAUMCCv_m3~fo>fep=c(}$W0qK%nE`9<$oAiHQgnhO%T+=q)ChI@|~H@DN|6S zn^cDPBGpQ0aNefBRWqLXdSCaY0_fTUYtBz@C6(L4Vh@nqm>nYs0aki&U0pM9!09hb zJzWIX(~P;%TIG@=rLqEVB(KgEv0-dsfh2Hn8RVB1<$<_Lc1m}XA`PGTjKfgWp^$tzx YR^)zopZBAE1>AIO}fDt^Zo#T71H9q0L|M26{9XmrU{ZO=JOFL%CeA$#lC%L`_H?b`~iw)9?Jm}|4Tv2AH%+eL@NFB{pU_FZlbpH#WB zP4V+%Q=XZw&vwUFENxTVZ1U#B1%ni~!xJQVozpTSq}q6RK>`^!CPqq4SeQ6blGoa3 zaiV+L@`nrC6rB%Gh?EkWxs*{7NT%oR2|hf*G0jB5SSrtiXQu2-SGJi;6W!S)fyVhU z8}Xl1*nC2v`Ai1LP@@wdPH{NM9&6qcHC~5fdF5j(rt$Ea=opzxO>1ihV*SaHQu8i> zTocP1f2|;#*{I&9jV)ywTZ-MT53(uumL;C(Y4e}FFflU%sHR`lnc2uh;q&U=@`*on zL6*BdyM038v!+qJTJiKV!6_LI&*t^+cTAgD>5*n~!QpUliW@U;`N}X7VBk6cLpo&| zP$qoW2R_~tf2N%Q3V{Or=EQ}Jpny!v1c!fQN*PeuQpTBc*;3wR0d+xCRZitzuVyUO lcILKejVH5__u>7|?5|%G_GmoX+y+ct44$rjF6*2UngDC22r2*o literal 567 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfjh}ba4!+V0?Qpdfj6;2Iqs$ zj(7ey{XAG`)$GP%T`=L}*VUn_b+21h>Ou~S&fG0Eq0*aoG7s=oPMRsDHZyvUzxavyHU=5CL*{+Pq7ZDhh@)XvY_e5PC|VBXTi2|tT~?EdQpC;phI z$1O~pASq>b@%l5SqmE5rna@4=44gO06K&BM9sDjtJzZ6 zB&FUd8JqqIJFM_oRfY+ad5r9*@fev; ztekO1ZRUSpU|?>W^TQ`?@)`M)K+k-R26{F&C1XM2gq;e`%)I5<5fde)GH$2<*^_yU zOfDFlh?%gkvCZ*WX39FjfH;!?bn3^_;zna32^m$j=;e3 zm6~vKb8ERG(9>nyJxfSl8;RO>hR_F=R2|-xv4`H5#4ls_S?(;eCe}lYz0@j949&IJRBSDjo2yU(k2Vwfl?UueUO_)2J4hw&i`bW;rWsI(js8WC1a zGm1-hz6+=_4BgXt;U8a}*m^y=aoYI{JHPhr8}x5ug|u*_UdIXxzVy8q6*BcDIa!?d zZf`DMLua#lyu(4+t#n|c8DSVJEkK7Zpb7A#A<~l@ZpAPGSVB0TWLpeo1Zm0<@iWSN zOhdkrf~$!(0V4*pNXwLf@!{mP->~8O0kyQ!a92z6$pB!l6LUBu$=Zw~ubW5K==jq< zYicuFF*Ig^4sCQ@bIMz693(N?354(Ym{Jd}RF#$;lZ{>M4K32$nd$|80c6gkrWhjK9 z&}s1sR^xtOp7UJqEDqFf{5@sLD8kxkYiHtn7l=$x9GHqB2gE;nFe2zVN@ds`Oa$d* z0nIG+Rf6apJZQr=qu2@QJuo;dX(_9$IK_xl=oPMQfc_I}1w`>I3gCw+L$O%;qyfkqrw za0Z(KG-&df{B57wRqii#UO zBQtL?2LoO2%6lTmgvW>6lggMO1tiU+G8QCeSzI~XO)oew^GfrYTqp=-29gekLwSP_D~HW<1bUB6Qfh*wvazW`GtdP< zbZ2Segq_jMM$)_|{)o<8nV98}mT@CvM#_YlQb43OGd=nzFDSZz`q*Yp&-@W|*cBN3 zZa@t{O2rxIvyQ2@KuwDifexCm(-WwB!q1msbwVKZ?O=BoPdx)vpMSaGOsv!dp!Gl3 zP5s{uj4id9()~~}GG)0BH)M0S$3~+_)iW$`uxGj*tDgl-mJFV*elF{r5}E+tcN>fV diff --git a/tests_zemu/snapshots/sign-expert/0.png b/tests_zemu/snapshots/sign-expert/0.png new file mode 100644 index 0000000000000000000000000000000000000000..79f90ca16603806be79a072e8ba1ecd1a8879217 GIT binary patch literal 509 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfbnRba4!+V0?Qp&`&v1#O2^* z|8sx!E$oYWuEr!vxLsh@a^B1F(S5y&BeN0z>W6)2e3^~rUurnB+(e=H@`tuF&RGu6 zwtBDMaVd1Y%Et{-c`5r^&iq#^Ud}kPUD=u0NIzEPR+N0up*g5;gKPP6R^|2Mc&k|2wa5y~S=Y+QWiv{k_6i@z< zW=yqSd$cU8`|TC6+tIxBOCKJV%2@EwLwsBEi5|9;zJiAyyw1;-C7$SMQ!Kt*;NI4z zXbf_ngwbN47+Z=PGl*fci`zZZ;h7(h!#Gp%Gf>_EsK-RXSZac#Q9ekWolBaD!sioW z_MV5gXIS_!8@WgR5ivS(;lt9D2@4-an_egYhJ~_jos^{1zBxR+_ube)9=xb0;|jE~ znTOXo&4g#B|HOr0hkRD#eV-*^^nYdI2^mL+n||3F=iW-P-U{=^O5JV2(K*Yv-SEpr cmiWiW>A0o$T?+R{V6-xLy85}Sb4q9e03KG{(f|Me literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-expert/1.png b/tests_zemu/snapshots/sign-expert/1.png new file mode 100644 index 0000000000000000000000000000000000000000..1b843f7340ea697d45f436764cc03064ab12b6d8 GIT binary patch literal 409 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfb-~x;TbZFupzLm=|rx;C#^i z;GX}DI}>sh1z$<9_N=SZIoqMXcQOm_iI_NMqvNxsCj89&(*5pM)0wBH3eJa9O=4#8 z@Y>&1`FO$l?pHsd{b7fdj7@cnf)6WgHc7B#lX^4P^P8|h0;aBOQ^$|#wV@uQ^KU%mLx?cT>Td3YzE zSuQPe9xUl^Zn4eGVp~~qxRHsEQ8!plIG`_=w{I;$^q0}<$D~lfxAR(nLC)am>gTe~ HDWM4fz*?jb literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-expert/10.png b/tests_zemu/snapshots/sign-expert/10.png new file mode 100644 index 0000000000000000000000000000000000000000..d0842b5cf2fdf7768e8fe99dbc536f8b8d4484ec GIT binary patch literal 1011 zcmYLIe@s(X6mC=Vn50kVT*P-3Y%5}%9gyo2Lik}>DcCepLR_#x(Izjo(8M$$Dn!Am z=}io{yo!OmU>F4*B-Kj9jdc^5Ym6m%!BU1ITO`er&OgMERM|8{Jr|}X@7{Orx#!;V zo$q}2LRDo2n~G8u3I%K4TUHH!4-Cy`49v4S_cnz>^^3Wzbbpg_{$6c$t?kRQd*7x` zFQpvZGVT8T>3mz}=g+(s@%@|@?P?^3E-=CxUej2u!WI>-*sW`|XiCmc*YOex)?Gc&DLhI<4rE?EJ3^;_Q)jVj_%C4z^O# zCapdQsyS6RKB$zs;Q)t3j;)zm_!{u`Dy3gthQvtv*m!iojUxxWYCKn|!Y3bld`CM%-r~n_M<>zl0*+!sT40L< z>UcH4eBv|^!U8u$cFw?RBesOa(!?MQ7DGQ3cQqIsP~xOV77H}svZ4QL-X%BNTg%i-+5zTqoM7u*pIq6Yj{tDIV1}aL(Jxqa_KN@TVUeAtG zPp5Ke9v#~z$5yl=fELm^{PDxu-X(TfC<#CGZ3B$g)gml&}h=fXvDGTR@CQsUf-gR(oQU^*sz(H3l&YwmtOVcu(FkGk6-hRxJ@1n;P$ zTu?*SUKiezcw`lbY~zX4S(*-ujbvOl3>y^JX@R z=hT?K?Zu^TmAu7_Gp9?5&0Lx|aVJpDj@Qnl=0$6)i9++4$!sYTcB)EB+1dV3Sj?7^ zxAI}MpWmh|W7TbvI8#sPz;tDnm{r-UW|h!3%b literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-expert/13.png b/tests_zemu/snapshots/sign-expert/13.png new file mode 100644 index 0000000000000000000000000000000000000000..3bfe48e647c2265d038e08ea82adb81aaa730150 GIT binary patch literal 400 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFffLDx;TbZFupzLm?vY%zp_e9O|xE(Jpb*soNWt{o=Qp1^W zrc+CGcYRUnJf7k3ES#5n=E{e@&kUs|{QPpz`>?nE8@Fd`6BR#O7?~)1{&cXrM`}Xl zo;J_4tRJDwM*TqT%MYqcO3C>@W0y=3GdgkMg9Ok9(;r@KZI8JnrSjG@&V0`@WqCHx z>a2(pKD_xx{;CUPZcE8TOUp$6esE6eUWyp+O}fDt^Zo#T71H9q0L|M26{9XmrU{ZO=JOFL%CeA$#lC%L`_H?b`~iw)9?Jm}|4Tv2AH%+eL@NFB{pU_FZlbpH#WB zP4V+%Q=XZw&vwUFENxTVZ1U#B1%ni~!xJQVozpTSq}q6RK>`^!CPqq4SeQ6blGoa3 zaiV+L@`nrC6rB%Gh?EkWxs*{7NT%oR2|hf*G0jB5SSrtiXQu2-SGJi;6W!S)fyVhU z8}Xl1*nC2v`Ai1LP@@wdPH{NM9&6qcHC~5fdF5j(rt$Ea=opzxO>1ihV*SaHQu8i> zTocP1f2|;#*{I&9jV)ywTZ-MT53(uumL;C(Y4e}FFflU%sHR`lnc2uh;q&U=@`*on zL6*BdyM038v!+qJTJiKV!6_LI&*t^+cTAgD>5*n~!QpUliW@U;`N}X7VBk6cLpo&| zP$qoW2R_~tf2N%Q3V{Or=EQ}Jpny!v1c!fQN*PeuQpTBc*;3wR0d+xCRZitzuVyUO lcILKejVH5__u>7|?5|%G_GmoX+y+ct44$rjF6*2UngDC22r2*o literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-expert/15.png b/tests_zemu/snapshots/sign-expert/15.png new file mode 100644 index 0000000000000000000000000000000000000000..1e430afe4949126c0cf7e2855169f446ec26c4f6 GIT binary patch literal 713 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfdKvUzxavyHU=5CL*{+Pq7ZDhh@)XvY_e5PC|VBXTi2|tT~?EdQpC;phI z$1O~pASq>b@%l5SqmE5rna@4=44gO06K&BM9sDjtJzZ6 zB&FUd8JqqIJFM_oRfY+ad5r9*@fev; ztekO1ZRUSpU|?>W^TQ`?@)`M)K+k-R26{F&C1XM2gq;e`%)I5<5fde)GH$2<*^_yU zOfDFlh?%gkvCZ*WX39FjfH;!?bn3^_;zna32^m$j=;e3 zm6~vKb8ERG(9qEIZ&D{geVKEvUe_16QA?Q$xG+Po)n^e;4= z31>RBP}k~Pl#Cnq0+@~N+y3x-7A7@s(!#_^mM1TK zaOd?udBNc1pQ${&lX;9xe2lg$eC}!ZbWUo*&3V!}-ogRfQf(s~4|_ga^iZYvqQS{M zo7vy5W0aKAgSaQlAx*ToTz&K3o6T>R1AQ`=cia4w36TdAqHh6FbLhzn1(Rl`WLTWs jW6hWveGMXvpVW_@*rA=& literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-expert/3.png b/tests_zemu/snapshots/sign-expert/3.png new file mode 100644 index 0000000000000000000000000000000000000000..5b34a92c9c97a52ed765e8ea42221e632c281456 GIT binary patch literal 599 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfhq^x;TbZFupx_*Y9=~gUdzR zyk}b%M^wJqIYW5OC(h5oUj70J)w&;}^Hm&~jqJk?a~tix-f*V)6|ecOm%S=x3mIn~ z7Un%+^EG?k^V2rxx9okf>}~42^twH#C7)?KGk=~smF=0fNrC0zr##b*&xD6b?U?hh zsO{&ee$)7cjXzH*3r zjexvKAiIo>W?yJH6D;h1^1_BlBNHHdor%KdJDtx{%2p;$kW9&tNU2kqd40|et(oR0 z_b7Z;o4IzD#hY0TMK{)VR%b?JE=XLOc=5xsw*A*Wyp+mV(AcJby})1iyOQ(a*-{fC zPw?>GpT}c#;=%@1^?gpvMkYE^Vn*F%?V1MR0OgH=>bNDPCM;wG6EpcUfyy?(xIi(G-P%S#&pRKs zoqJlrX7(o`=f|l1>AS{@;CO(L>gm^=g|lerO4QR{^R7IiQ9c=%md+b7q#G zyzoH}Y!EO|%8MUaXGEO%Gm(e4e(l3xVDO%(@i@FYCF6$<)2YPmYJ9uR3U6%fJiN|V trY!f-hHT;X*x43uZXHRyjw1Ee{&B(QDBH~zbAbtl!PC{xWt~$(696`102KfL literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-expert/4.png b/tests_zemu/snapshots/sign-expert/4.png new file mode 100644 index 0000000000000000000000000000000000000000..cadf7aecaec5ff58bb9966d9cc03f55471c589eb GIT binary patch literal 1090 zcmX|>4NMbf7{^=Av`Bg*kqO+nW!EBcbJdWyS3ly{VOy0D%H~H3uE=VR(h7nJi6CKH zb>>`y#uJR@f*76dnKN+E$*gB142?i~cW z{-F6r(AjJXcC_91bIBQ6yp|y)0tQk~%4T$4OYx_Kp7xMds@&VwG@8}<+`iy!mq(8N zjC1J``&1t6WzDEKCQ_s=xdUYu3p)P$@utz)%S&3ibP%0mW%^*7SI2f3INV%J}o+C%Re;RrIPX4TCs9M_nb zHImgeoQ~q_s;1qf&Fjxa=P`^m{}&a@`N?6m50>eJ!ahzApl4^S2igGc(Th`@*B=>< zuck$S?qwMZ{}I250E)l`e=Q$U!&?mijg*h-U>R0mYqwSkYXJpDn2VseI?JA!4u0d0 z1cg2;(*VqF1JF7oPYh*UWqve}u6!7(PlTpGM;~h@;W~TippIX^XZjRAf=hTPK_E8I zMj$BKs;Ii)XmRbjjme!W?ig(`u}~0<7Ftjo7%4Qp_Obx&U{Ce%zA}D4ESYSv@Vt}t z?`yc^?i6Am9le+J@SQ*H*3}jT%+SB zOK^ggrMOj-1#J0~;*0zq57UAnh1zBP1O(zddeGu8!-RkxEtZ1}-xxMpr%om5R1JT8 ztSK`^Zm2+2i>qWyiQc;fJeXGoCkGTUq^0Q8K!N)uhZoEZU~uAWsZ(%xab7qABj%jH zVg_E}a#^EWyzEoei#hG(v`C?10=>NQPx=SrdIm6atyH{sZJLw`a7;KWu6P7fj&@+^ z%W5Z7_)d&NkF8Y(tyHjQe(?Y*V?$}$t?HBq>Nh~CFH2iqcfZvi5;WA~la|BqZ_pGu Loc3QgetYOYPua;7 literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-expert/5.png b/tests_zemu/snapshots/sign-expert/5.png new file mode 100644 index 0000000000000000000000000000000000000000..ece626fa613b223af068574db91690754c880192 GIT binary patch literal 617 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfbW-x;TbZFupw)UG&l@3LM+ocqOIU+8on>CN2Y-n0xX_*2{(auJd~QS zb2ZS`#HSygOR>$gzw{x8=~Uu&vAW%D^;b7sbU5tB%xiLC!^MJd-uG!wgH3>bQZPQ@ z!z*o6z5NrPl-kVQnGQg6&7^>CRFjl41Bb=-=t_2wZ^ctGB!E^b6i)?(0vP3h^#bMg z-#oQE!yygmghVO3qle^xs=|#~9$u#_QI{ V_QvqPn}JD)!PC{xWt~$(695Z@0IUE2 literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-expert/6.png b/tests_zemu/snapshots/sign-expert/6.png new file mode 100644 index 0000000000000000000000000000000000000000..bcea849f4df186a3f2ceaab80fc8cd232e1b2afb GIT binary patch literal 1033 zcmXYw4@?_X9LHNPxwz2>aa(|1lwBbUb0dbl9V{EmcH;_28Y^Lc3OfH>^3t>uw`qZ3 zz=dJ<41)6ty5)qR9l=FmjM$jaFo}9Ix#?vqRoKvON2thL!}sPtC}UK$Li)u zARl`SEe5@tJMZRw9mvAlixi`p!+HJp`sfW)j7nnYAcuP#*o;q@VrU>Z? zGp$`xq$YDPAp51k*$J0oBqm^8#V>$IngJe4 zm*eSX1)0g0IDGV}w>+s7VLpxTNubSVG$PnBsNI0=gWKduYsX_h{Pee=%^%?Kzs-w( zTrDfXuO!l&q546~@8^P(qr6ZqjL*p&YTub|u0ZBeCGRAG>pzA)We$BrHG8Qm3U=eA zq}tG&NTEC_7qp0~q{gUgn@|pK)Jl}XCiimym0Ir#FwN}c$?i87*CDeB3_}GM6Ts^+ z9uc4p1kB75JiGwOzzE=D(3#YsF}sfR8KBiba-E=M&ouhgCu1M+r1e^-bxt_)#exTz zyk{jkt%6g@F7`Y^PJ`&CsI=rwc^H*&cCycbrurjlIC6p~D_gMC(anT`c&NMR47e~w zCF3duV-Qj)**%+fIl&P#%Sr>U<>U4z>V~{!;++c>>AS;gHb1{-?!@wEst-*CCmT2< zCEZ3r_O5zk4Pfu6H8uuiAUGW@y2g?RM}u0U0De72Va6cC5wYvY$Ehpyc~N_RqX3J*#dyR%eh3c`dl_eNeVSH5E?1afljMN>WO&Uq zxnPO~GHloPys`MQFb%rpLMu=@G_o*sYU#Whp4VbMbVP*{>C%rX{@~9>{O?O>5azkx)0dvSp`$=pG9Y9l&hAwpq#hv?)I>xETo`cqIFzDCDTXnIGhlnTbJc0;Om&V} zwGCEn*o#uP4omX7!RnqdflzWJ?9g8T{J+qt1 zjkw%WE*scg?oc^MP-CRvSBhO#d^$9s5m#_??1Dj?v%ic|kG)dPUY*lb%M%fn`JOtS z)YkO%3S{K9uyTkv2Ho*6fE&QsO*)ZEey0@8@0DV1@O36+P)3#`WTjXFcSOB`a*J_x zp%@{jUj4v;%CA(zY*YEQe4xc+p*QIH=N9XH4MN(R#(Pm*gVW<7FA8%n^8)uUg%i1b zP&7uWh}Hsl1QarWu~5ttt#c@)fxc&v^b{EA^QB$=^-Rj$XWSNeQU&L>=)4NMbf7{^=Av`Bg*kqO+nW!EBcbJdWyS3ly{VOy0D%H~H3uE=VR(h7nJi6CKH zb>>`y#uJR@f*76dnKN+E$*gB142?i~cW z{-F6r(AjJXcC_91bIBQ6yp|y)0tQk~%4T$4OYx_Kp7xMds@&VwG@8}<+`iy!mq(8N zjC1J``&1t6WzDEKCQ_s=xdUYu3p)P$@utz)%S&3ibP%0mW%^*7SI2f3INV%J}o+C%Re;RrIPX4TCs9M_nb zHImgeoQ~q_s;1qf&Fjxa=P`^m{}&a@`N?6m50>eJ!ahzApl4^S2igGc(Th`@*B=>< zuck$S?qwMZ{}I250E)l`e=Q$U!&?mijg*h-U>R0mYqwSkYXJpDn2VseI?JA!4u0d0 z1cg2;(*VqF1JF7oPYh*UWqve}u6!7(PlTpGM;~h@;W~TippIX^XZjRAf=hTPK_E8I zMj$BKs;Ii)XmRbjjme!W?ig(`u}~0<7Ftjo7%4Qp_Obx&U{Ce%zA}D4ESYSv@Vt}t z?`yc^?i6Am9le+J@SQ*H*3}jT%+SB zOK^ggrMOj-1#J0~;*0zq57UAnh1zBP1O(zddeGu8!-RkxEtZ1}-xxMpr%om5R1JT8 ztSK`^Zm2+2i>qWyiQc;fJeXGoCkGTUq^0Q8K!N)uhZoEZU~uAWsZ(%xab7qABj%jH zVg_E}a#^EWyzEoei#hG(v`C?10=>NQPx=SrdIm6atyH{sZJLw`a7;KWu6P7fj&@+^ z%W5Z7_)d&NkF8Y(tyHjQe(?Y*V?$}$t?HBq>Nh~CFH2iqcfZvi5;WA~la|BqZ_pGu Loc3QgetYOYPua;7 literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-expert/9.png b/tests_zemu/snapshots/sign-expert/9.png new file mode 100644 index 0000000000000000000000000000000000000000..ece626fa613b223af068574db91690754c880192 GIT binary patch literal 617 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfbW-x;TbZFupw)UG&l@3LM+ocqOIU+8on>CN2Y-n0xX_*2{(auJd~QS zb2ZS`#HSygOR>$gzw{x8=~Uu&vAW%D^;b7sbU5tB%xiLC!^MJd-uG!wgH3>bQZPQ@ z!z*o6z5NrPl-kVQnGQg6&7^>CRFjl41Bb=-=t_2wZ^ctGB!E^b6i)?(0vP3h^#bMg z-#oQE!yygmghVO3qle^xs=|#~9$u#_QI{ V_QvqPn}JD)!PC{xWt~$(695Z@0IUE2 literal 0 HcmV?d00001 diff --git a/tests_zemu/tests/test.js b/tests_zemu/tests/test.js index 9ddead38..35200958 100644 --- a/tests_zemu/tests/test.js +++ b/tests_zemu/tests/test.js @@ -12,12 +12,44 @@ const sim_options = { logging: true, start_delay: 3000, custom: `-s "${APP_SEED}"` -// , X11: true + , X11: true }; jest.setTimeout(30000) -const example_tx_str = { +const example_tx_str_basic = { + "account_number": "108", + "chain_id": "cosmoshub-3", + "fee": { + "amount": [ + { + "amount": "600", + "denom": "uatom" + } + ], + "gas": "200000" + }, + "memo": "", + "msgs": [ + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos1w34k53py5v5xyluazqpq65agyajavep2rflq6h", + "validator_address": "cosmosvaloper1kn3wugetjuy4zetlq6wadchfhvu3x740ae6z6x" + } + }, + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos1w34k53py5v5xyluazqpq65agyajavep2rflq6h", + "validator_address": "cosmosvaloper1sjllsnramtg3ewxqwwrwjxfgc4n4ef9u2lcnj0" + } + } + ], + "sequence": "106" +}; + +const example_tx_str_expert = { "account_number": "108", "chain_id": "cosmoshub-2", "fee": { @@ -244,7 +276,13 @@ describe('Basic checks', function () { const app = new CosmosApp(sim.getTransport()); const path = [44, 118, 0, 0, 0]; - let tx = JSON.stringify(example_tx_str); + let tx = JSON.stringify(example_tx_str_basic); + + // get address / publickey + const respPk = await app.getAddressAndPubKey(path, "cosmos"); + expect(respPk.return_code).toEqual(0x9000); + expect(respPk.error_message).toEqual("No errors"); + console.log(respPk) // do not wait here.. const signatureRequest = app.sign(path, tx); @@ -253,7 +291,7 @@ describe('Basic checks', function () { // Reference window await sim.snapshot(`${snapshotPrefixTmp}${snapshotCount++}.png`); - for (let i = 0; i < 15; i++) { + for (let i = 0; i < 8; i++) { await sim.clickRight(Resolve(`${snapshotPrefixTmp}${snapshotCount++}.png`)); } await sim.clickBoth(); @@ -267,10 +305,133 @@ describe('Basic checks', function () { expect(resp.error_message).toEqual("No errors"); // Now verify the signature + const hash = crypto.createHash("sha256"); + const msgHash = Uint8Array.from(hash.update(tx).digest()); + + const signatureDER = resp.signature; + const signature = secp256k1.signatureImport(Uint8Array.from(signatureDER)); + + const pk = Uint8Array.from(respPk.compressed_pk) + + const signatureOk = secp256k1.ecdsaVerify(signature, msgHash, pk); + expect(signatureOk).toEqual(true); + + } finally { + await sim.close(); + } + }); + + it('show address and sign basic', async function () { + const snapshotPrefixGolden = "snapshots/show-address-and-sign-basic/"; + const snapshotPrefixTmp = "snapshots-tmp/show-address-and-sign-basic/"; + let snapshotCount = 0; + + const sim = new Zemu(APP_PATH); + try { + await sim.start(sim_options); + const app = new CosmosApp(sim.getTransport()); + + const path = [44, 118, 0, 0, 0]; + let tx = JSON.stringify(example_tx_str_basic); + + // get address / publickey + const respRequest = app.showAddressAndPubKey(path, "cosmos"); + + // We need to wait until the app responds to the APDU + await Zemu.sleep(2000); + + // Now navigate the address / path + await sim.snapshot(`${snapshotPrefixTmp}${snapshotCount++}.png`); + await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); + await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); + await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); + await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); + await sim.clickBoth(`${snapshotPrefixTmp}${snapshotCount++}.png`); + + const respPk = await respRequest; + console.log(respPk); + + expect(respPk.return_code).toEqual(0x9000); + expect(respPk.error_message).toEqual("No errors"); + console.log(respPk) + + // do not wait here.. + const signatureRequest = app.sign(path, tx); + + await Zemu.sleep(2000); + + // Reference window + await sim.snapshot(`${snapshotPrefixTmp}${snapshotCount++}.png`); + for (let i = 0; i < 8; i++) { + await sim.clickRight(Resolve(`${snapshotPrefixTmp}${snapshotCount++}.png`)); + } + await sim.clickBoth(); + + let resp = await signatureRequest; + console.log(resp); + + compareSnapshots(snapshotPrefixTmp, snapshotPrefixGolden, snapshotCount); + + expect(resp.return_code).toEqual(0x9000); + expect(resp.error_message).toEqual("No errors"); + + // Now verify the signature + const hash = crypto.createHash("sha256"); + const msgHash = Uint8Array.from(hash.update(tx).digest()); + + const signatureDER = resp.signature; + const signature = secp256k1.signatureImport(Uint8Array.from(signatureDER)); + + const pk = Uint8Array.from(respPk.compressed_pk) + + const signatureOk = secp256k1.ecdsaVerify(signature, msgHash, pk); + expect(signatureOk).toEqual(true); + + } finally { + await sim.close(); + } + }); + + it('sign expert', async function () { + const snapshotPrefixGolden = "snapshots/sign-expert/"; + const snapshotPrefixTmp = "snapshots-tmp/sign-expert/"; + let snapshotCount = 0; + + const sim = new Zemu(APP_PATH); + try { + await sim.start(sim_options); + const app = new CosmosApp(sim.getTransport()); + + const path = [44, 118, 0, 0, 0]; + let tx = JSON.stringify(example_tx_str_expert); + + // get address / publickey const respPk = await app.getAddressAndPubKey(path, "cosmos"); expect(respPk.return_code).toEqual(0x9000); expect(respPk.error_message).toEqual("No errors"); + console.log(respPk) + // do not wait here.. + const signatureRequest = app.sign(path, tx); + + await Zemu.sleep(2000); + + // Reference window + await sim.snapshot(`${snapshotPrefixTmp}${snapshotCount++}.png`); + for (let i = 0; i < 15; i++) { + await sim.clickRight(Resolve(`${snapshotPrefixTmp}${snapshotCount++}.png`)); + } + await sim.clickBoth(); + + let resp = await signatureRequest; + console.log(resp); + + compareSnapshots(snapshotPrefixTmp, snapshotPrefixGolden, snapshotCount); + + expect(resp.return_code).toEqual(0x9000); + expect(resp.error_message).toEqual("No errors"); + + // Now verify the signature const hash = crypto.createHash("sha256"); const msgHash = Uint8Array.from(hash.update(tx).digest()); diff --git a/tests_zemu/tools/debug.mjs b/tests_zemu/tools/debug.mjs index 9de4488a..3114fec3 100644 --- a/tests_zemu/tools/debug.mjs +++ b/tests_zemu/tools/debug.mjs @@ -14,7 +14,7 @@ const SIM_OPTIONS = { const example_tx_str = { "account_number": "108", - "chain_id": "cosmoshub-2", + "chain_id": "cosmoshub-3", "fee": { "amount": [ { @@ -29,14 +29,14 @@ const example_tx_str = { { "type": "cosmos-sdk/MsgWithdrawDelegationReward", "value": { - "delegator_address": "cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl87jycah", + "delegator_address": "cosmos1w34k53py5v5xyluazqpq65agyajavep2rflq6h", "validator_address": "cosmosvaloper1kn3wugetjuy4zetlq6wadchfhvu3x740ae6z6x" } }, { "type": "cosmos-sdk/MsgWithdrawDelegationReward", "value": { - "delegator_address": "cosmos1kky4yzth6gdrm8ga5zlfwhav33yr7hl87jycah", + "delegator_address": "cosmos1w34k53py5v5xyluazqpq65agyajavep2rflq6h", "validator_address": "cosmosvaloper1sjllsnramtg3ewxqwwrwjxfgc4n4ef9u2lcnj0" } } @@ -61,19 +61,26 @@ async function debugScenario(sim, app) { const path = [44, 118, 0, 0, 0]; let tx = JSON.stringify(example_tx_str); +// await Zemu.default.sleep(120000); + + const addr = await app.getAddressAndPubKey(path, "cosmos"); + console.log(addr) + + console.log(tx); + // do not wait here.. const signatureRequest = app.sign(path, tx); await Zemu.default.sleep(1000); - // await sim.clickRight(); - // await sim.clickRight(); - // await sim.clickRight(); - // await sim.clickRight(); - // await sim.clickRight(); - // await sim.clickRight(); - // await sim.clickRight(); - // await sim.clickRight(); - // await sim.clickRight(); + await sim.clickRight(); + await sim.clickRight(); + await sim.clickRight(); + await sim.clickRight(); + await sim.clickRight(); + await sim.clickRight(); + await sim.clickRight(); + await sim.clickRight(); + await sim.clickBoth(); let resp = await signatureRequest; console.log(resp); From 62e2247b33b6e1e3d81a732d9f9dfca4a2e8c927 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Fri, 5 Jun 2020 15:45:59 +0200 Subject: [PATCH 70/78] Fixing NanoX UI --- app/Makefile | 1 + deps/ledger-zxlib/app/common/view_s.c | 13 ++++++++---- deps/ledger-zxlib/app/common/view_x.c | 30 ++++++++++++++++++++++----- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/app/Makefile b/app/Makefile index 68317abb..2f5524d7 100755 --- a/app/Makefile +++ b/app/Makefile @@ -132,6 +132,7 @@ CFLAGS += -O3 -Os -Wpedantic -Wall -Wno-unknown-pragmas -Wno-c11-extensions # To accept Ledger SDK issues CFLAGS += -Wno-missing-declarations -Wno-empty-translation-unit -Wno-language-extension-token CFLAGS += -Wno-embedded-directive -Wno-pointer-arith +CFLAGS += -Wno-typedef-redefinition -Wno-newline-eof AS := $(GCCPATH)arm-none-eabi-gcc AFLAGS += diff --git a/deps/ledger-zxlib/app/common/view_s.c b/deps/ledger-zxlib/app/common/view_s.c index ce9f344d..a25ed8f2 100644 --- a/deps/ledger-zxlib/app/common/view_s.c +++ b/deps/ledger-zxlib/app/common/view_s.c @@ -32,6 +32,7 @@ #if defined(TARGET_NANOS) void h_expert_toggle(); +void h_expert_update(); void h_review_button_left(); void h_review_button_right(); void view_review_show(); @@ -232,10 +233,7 @@ void splitValueField() { ////////////////////////// void view_idle_show_impl(uint8_t item_idx) { - strcpy(viewdata.value, "disabled"); - if (app_mode_expert()) { - strcpy(viewdata.value, "enabled"); - } + h_expert_update(); UX_MENU_DISPLAY(item_idx, menu_main, NULL); } @@ -297,6 +295,13 @@ void h_expert_toggle() { view_idle_show(1); } +void h_expert_update() { + strcpy(viewdata.value, "disabled"); + if (app_mode_expert()) { + strcpy(viewdata.value, "enabled"); + } +} + #if !defined(HAVE_UX_FLOW) void h_addr_button_left() { h_paging_decrease(); diff --git a/deps/ledger-zxlib/app/common/view_x.c b/deps/ledger-zxlib/app/common/view_x.c index 4fe25614..5f31e1d7 100644 --- a/deps/ledger-zxlib/app/common/view_x.c +++ b/deps/ledger-zxlib/app/common/view_x.c @@ -15,6 +15,7 @@ * limitations under the License. ********************************************************************************/ +#include "app_mode.h" #include "view.h" #include "view_internal.h" #include "actions.h" @@ -30,6 +31,8 @@ #if defined(TARGET_NANOX) +void h_expert_toggle(); +void h_expert_update(); void h_review_loop_start(); void h_review_loop_inside(); void h_review_loop_end(); @@ -39,12 +42,13 @@ ux_state_t G_ux; bolos_ux_params_t G_ux_params; uint8_t flow_inside_loop; + UX_FLOW_DEF_NOCB(ux_idle_flow_1_step, pbb, { &C_icon_app, MENU_MAIN_APP_LINE1, MENU_MAIN_APP_LINE2,}); -UX_FLOW_DEF_NOCB(ux_idle_flow_2_step, bn, { "Expert mode:", "disabled", }); -UX_FLOW_DEF_NOCB(ux_idle_flow_2_step, bn, { APPVERSION_LINE1, APPVERSION_LINE2, }); -UX_FLOW_DEF_NOCB(ux_idle_flow_3_step, bn, { "Developed by:", "Zondax.ch", }); -UX_FLOW_DEF_NOCB(ux_idle_flow_4_step, bn, { "License:", "Apache 2.0", }); -UX_FLOW_DEF_VALID(ux_idle_flow_5_step, pb, os_sched_exit(-1), { &C_icon_dashboard, "Quit",}); +UX_STEP_CB_INIT(ux_idle_flow_2_step, bn, h_expert_update(), h_expert_toggle(), { "Expert mode:", viewdata.value, }); +UX_FLOW_DEF_NOCB(ux_idle_flow_3_step, bn, { APPVERSION_LINE1, APPVERSION_LINE2, }); +UX_FLOW_DEF_NOCB(ux_idle_flow_4_step, bn, { "Developed by:", "Zondax.ch", }); +UX_FLOW_DEF_NOCB(ux_idle_flow_5_step, bn, { "License:", "Apache 2.0", }); +UX_FLOW_DEF_VALID(ux_idle_flow_6_step, pb, os_sched_exit(-1), { &C_icon_dashboard, "Quit",}); const ux_flow_step_t *const ux_idle_flow [] = { &ux_idle_flow_1_step, @@ -52,6 +56,7 @@ const ux_flow_step_t *const ux_idle_flow [] = { &ux_idle_flow_3_step, &ux_idle_flow_4_step, &ux_idle_flow_5_step, + &ux_idle_flow_6_step, FLOW_END_STEP, }; @@ -190,6 +195,21 @@ void splitValueField() { } } +void h_expert_toggle() { + app_mode_set_expert(!app_mode_expert()); + + // refresh menu + view_idle_show(0); + ux_flow_next(); +} + +void h_expert_update() { + strcpy(viewdata.value, "disabled"); + if (app_mode_expert()) { + strcpy(viewdata.value, "enabled"); + } +} + ////////////////////////// ////////////////////////// ////////////////////////// From 8d2b46a9d1ab846b6310f20989ef79b03882345c Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Fri, 5 Jun 2020 15:53:26 +0200 Subject: [PATCH 71/78] fixing flashing issue --- deps/ledger-zxlib/app/common/view_x.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/deps/ledger-zxlib/app/common/view_x.c b/deps/ledger-zxlib/app/common/view_x.c index 5f31e1d7..62d9e27a 100644 --- a/deps/ledger-zxlib/app/common/view_x.c +++ b/deps/ledger-zxlib/app/common/view_x.c @@ -197,10 +197,7 @@ void splitValueField() { void h_expert_toggle() { app_mode_set_expert(!app_mode_expert()); - - // refresh menu - view_idle_show(0); - ux_flow_next(); + ux_flow_init(0, ux_idle_flow, &ux_idle_flow_2_step); } void h_expert_update() { From 26a785e4d0d16d5d49d74a9de9828415e6d498e3 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 9 Jun 2020 15:23:12 +0200 Subject: [PATCH 72/78] Adding test+fix for combined txs (#3) * Adding test+fix for combined txs * bump version number --- app/Makefile | 2 +- app/src/tx_display.c | 13 ++- docs/img/clion_debugging.png | Bin 139227 -> 107484 bytes tests/testcases/manual.json | 54 ++++++++++ .../sign-basic-combined/.gitkeep | 1 + .../snapshots/sign-basic-combined/0.png | Bin 0 -> 599 bytes .../snapshots/sign-basic-combined/1.png | Bin 0 -> 1067 bytes .../snapshots/sign-basic-combined/10.png | Bin 0 -> 713 bytes .../snapshots/sign-basic-combined/2.png | Bin 0 -> 731 bytes .../snapshots/sign-basic-combined/3.png | Bin 0 -> 493 bytes .../snapshots/sign-basic-combined/4.png | Bin 0 -> 586 bytes .../snapshots/sign-basic-combined/5.png | Bin 0 -> 1067 bytes .../snapshots/sign-basic-combined/6.png | Bin 0 -> 731 bytes .../snapshots/sign-basic-combined/7.png | Bin 0 -> 499 bytes .../snapshots/sign-basic-combined/8.png | Bin 0 -> 400 bytes .../snapshots/sign-basic-combined/9.png | Bin 0 -> 644 bytes tests_zemu/tests/test.js | 92 ++++++++++++++++++ tests_zemu/tools/debug.mjs | 14 ++- 18 files changed, 166 insertions(+), 10 deletions(-) create mode 100644 tests_zemu/snapshots-tmp/sign-basic-combined/.gitkeep create mode 100644 tests_zemu/snapshots/sign-basic-combined/0.png create mode 100644 tests_zemu/snapshots/sign-basic-combined/1.png create mode 100644 tests_zemu/snapshots/sign-basic-combined/10.png create mode 100644 tests_zemu/snapshots/sign-basic-combined/2.png create mode 100644 tests_zemu/snapshots/sign-basic-combined/3.png create mode 100644 tests_zemu/snapshots/sign-basic-combined/4.png create mode 100644 tests_zemu/snapshots/sign-basic-combined/5.png create mode 100644 tests_zemu/snapshots/sign-basic-combined/6.png create mode 100644 tests_zemu/snapshots/sign-basic-combined/7.png create mode 100644 tests_zemu/snapshots/sign-basic-combined/8.png create mode 100644 tests_zemu/snapshots/sign-basic-combined/9.png diff --git a/app/Makefile b/app/Makefile index 2f5524d7..2f2f48a6 100755 --- a/app/Makefile +++ b/app/Makefile @@ -27,7 +27,7 @@ include $(BOLOS_SDK)/Makefile.defines # Main app configuration APPNAME = "Cosmos" APPVERSION_M=2 -APPVERSION_N=13 +APPVERSION_N=14 APPVERSION_P=0 APPPATH = "44'/118'" diff --git a/app/src/tx_display.c b/app/src/tx_display.c index 4aca498b..04e448e4 100644 --- a/app/src/tx_display.c +++ b/app/src/tx_display.c @@ -255,8 +255,9 @@ parser_error_t tx_indexRootFields() { CHECK_PARSER_ERR(calculate_is_default_chainid()); // turn off grouping if we are not in expert mode - parser_tx_obj.flags.msg_type_grouping = parser_tx_obj.filter_msg_type_count > 0; - parser_tx_obj.flags.msg_from_grouping = !tx_is_expert_mode() && parser_tx_obj.filter_msg_from_count > 0; + if (tx_is_expert_mode()) { + parser_tx_obj.flags.msg_from_grouping = 0; + } // check if from reference value matches the device address that will be signing parser_tx_obj.flags.msg_from_grouping_hide_all = 0; @@ -319,7 +320,9 @@ __Z_INLINE parser_error_t retrieve_tree_indexes(uint8_t display_index, root_item // consume indexed subpages until we get the item index in the subpage *root_item = 0; *subitem_index = 0; - while (get_subitem_count(*root_item) == 0) (*root_item)++; + while (get_subitem_count(*root_item) == 0) { + (*root_item)++; + } for (uint16_t i = 0; i < display_index; i++) { (*subitem_index)++; @@ -328,7 +331,9 @@ __Z_INLINE parser_error_t retrieve_tree_indexes(uint8_t display_index, root_item // Advance root index and skip empty items *subitem_index = 0; (*root_item)++; - while (get_subitem_count(*root_item) == 0) (*root_item)++; + while (get_subitem_count(*root_item) == 0){ + (*root_item)++; + } } } diff --git a/docs/img/clion_debugging.png b/docs/img/clion_debugging.png index d6e8848c8c4b1f9468a03f255b7a424c8aecfda0..f26ff75908db78149134ed0b2ebd9ad6fcd5fdee 100644 GIT binary patch literal 107484 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∨;${%nGo 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 literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-basic-combined/1.png b/tests_zemu/snapshots/sign-basic-combined/1.png new file mode 100644 index 0000000000000000000000000000000000000000..57c55af75f0b3e6740d41cbfae35fe3d5671e1b7 GIT binary patch literal 1067 zcmXX`4{Q@v9PZhqlF-VqbxZ}H;7Jaw1fpZ z)SJ4ov`jH4BV&|p!m0GfU+%uTR~r(W|Ehh>d&DHqo#s>{PcIE}Y9FU2gxG-(6-&}yMjjPny$!UC zm-1_c*u8A+;mqI#FD2vh)2Tiocc{yFIOsHL3wuGh7L|~YTG_n{;L&-x_Wcjv>1Mz(3VP`J{K^O zhj+hwn4Xw_yQWYczP;F`{hXeNYOP8V^TX(;xKs|6kn${>vn)JDt}j`=^)ul0Lx3|! z2d?~ho(B4INz87TT)N@ql;oIbKGkXxW^du9)K5je9x=(GsYX{`;-QpZh>ebo*#t%V zVIAY)XM{^gLZ=H7RXm+I|8#geV_1bs3O1Gy`tPT}s5r~1Crz?%x}r{B2l9%>shKFC z7+-2uEkJjw{%#djHG`NI&xl22n~`7lzWz=wt9z8+DGu*w{uz zv8)G0&;QiAZ_ZmvQTxD(*v)D?IMVJdisH%ddgMm-O`f(q!PCGm9}KM8;yQ5uOXu*d zsCEtg)u%c&wEJLJDmHo+tE~vxQ_gRfsu?gI{ag#V@*{R}eSypgR!%y{GrE4A*sWEM z2P2KvJwf{&U5HgpQy|Lt_ck={lslVTK^>zKl0o0lRJ?M}uYrPANEh@>G{p5x>BDrR zD~OUU)pPhvxNt?q?nt+y1#*Jl;k3;Ui%1dVKET7j_@{4|~_gDgXcg literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-basic-combined/10.png b/tests_zemu/snapshots/sign-basic-combined/10.png new file mode 100644 index 0000000000000000000000000000000000000000..1e430afe4949126c0cf7e2855169f446ec26c4f6 GIT binary patch literal 713 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfdKvUzxavyHU=5CL*{+Pq7ZDhh@)XvY_e5PC|VBXTi2|tT~?EdQpC;phI z$1O~pASq>b@%l5SqmE5rna@4=44gO06K&BM9sDjtJzZ6 zB&FUd8JqqIJFM_oRfY+ad5r9*@fev; ztekO1ZRUSpU|?>W^TQ`?@)`M)K+k-R26{F&C1XM2gq;e`%)I5<5fde)GH$2<*^_yU zOfDFlh?%gkvCZ*WX39FjfH;!?bn3^_;zna32^m$j=;e3 zm6~vKb8ERG(98=EyW76hfB5tr5YR z6&7i(HYb9JESIl(l#la{WNA6c=4-X7##Q%>mR&yJ^Zl-5aZ4BQ9Kd>mp%56j- zm^e~^E5{pAs!-pg%x+$xlu#Kax*bdmpyK;umMJ|eNB0rl?OK#dn|G-V`OCgf51@cX z$1hq(EYEdV!C?@W?-lRIc}N^z5!Xa{I%2 zt4vBjQY<37hM>53?Vu6R%yuw*WSo>HVRYYxI|wZi2E`?z0B2F0p508&k3`_QH?uC)f-48X6A;1g z(}7cGP)ZA<9|}?lFEBFD@w`*4_oq_ywI>>Jw~q5}-@~}xTSA>eN-sREzQL%K;Bl}b zBkncEZTP_MFRNiGeuItDCJg>#2C)IxAhwn~8-{LZ0F8hs^vUL4aLGo|OS!CE#aoiz zHiNJyhBV@cW&hRnaC4+%qJ}xT774e^>-W4W?p%28kw+nQab?_@?zZ`ltSv%n2QAfB U7R`J^e9*LrsiL}kwCsG}KkAe#WB>pF literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-basic-combined/3.png b/tests_zemu/snapshots/sign-basic-combined/3.png new file mode 100644 index 0000000000000000000000000000000000000000..beeb6f3ead6ac0c4a7051342ec59f89d638765a1 GIT binary patch literal 493 zcmVdfLkhMVY&Y0$(C!?hV7tL~ zgLdb%+8t8R?z~%glj2PtxGEV^bSaSOWRO9I6kQ5rkhugIQoLJvBcl|^OfRIkj1(XR*~;0-9Hc|W0%m@U8X;=%>&=#hlElfjOn1;464Q*i> j+QKxng=uID)6jkc5)7-14~?b;00000NkvXXu0mjfC^XVW literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-basic-combined/4.png b/tests_zemu/snapshots/sign-basic-combined/4.png new file mode 100644 index 0000000000000000000000000000000000000000..07bd9d3a9ab421f0fa2503b0cc3d1adc303e9ad1 GIT binary patch literal 586 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfa*vx;TbZFupw)n0MQQ!R6p} zmf!!_-}T333h+B=f8%o7vTS;=YTe6Tl?;bxU!*2P&b!iZraHR*qHpvB%e`zV85X^5 z!IKw0+#4%YzW1VUzRE{sQ<)PN3{Lv+PCf%v3PcwQ6pT%Hc$?2?p1ja-X7=3OZM*M% zxC~Tqf`>Pl*$9aIC(e|b;Mm5N;^z+}-j%&e0cv|`s$hHqsNvHRwv>Bs_Do#JIP-FL z#J&ZLGwlm|K+j6g02+6Y8I13mz?1C>pZl$u}(QF}l2aZk#$ z|HqE`Uv>bxD--DO<)#XsbyG5CNKL4mIyXGq;o16$K=m`F{&|3`$oesfErksz@N;EZ z+)Boo(5sCBQVo N;OXk;vd$@?2>=~%|5*S4 literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-basic-combined/5.png b/tests_zemu/snapshots/sign-basic-combined/5.png new file mode 100644 index 0000000000000000000000000000000000000000..57c55af75f0b3e6740d41cbfae35fe3d5671e1b7 GIT binary patch literal 1067 zcmXX`4{Q@v9PZhqlF-VqbxZ}H;7Jaw1fpZ z)SJ4ov`jH4BV&|p!m0GfU+%uTR~r(W|Ehh>d&DHqo#s>{PcIE}Y9FU2gxG-(6-&}yMjjPny$!UC zm-1_c*u8A+;mqI#FD2vh)2Tiocc{yFIOsHL3wuGh7L|~YTG_n{;L&-x_Wcjv>1Mz(3VP`J{K^O zhj+hwn4Xw_yQWYczP;F`{hXeNYOP8V^TX(;xKs|6kn${>vn)JDt}j`=^)ul0Lx3|! z2d?~ho(B4INz87TT)N@ql;oIbKGkXxW^du9)K5je9x=(GsYX{`;-QpZh>ebo*#t%V zVIAY)XM{^gLZ=H7RXm+I|8#geV_1bs3O1Gy`tPT}s5r~1Crz?%x}r{B2l9%>shKFC z7+-2uEkJjw{%#djHG`NI&xl22n~`7lzWz=wt9z8+DGu*w{uz zv8)G0&;QiAZ_ZmvQTxD(*v)D?IMVJdisH%ddgMm-O`f(q!PCGm9}KM8;yQ5uOXu*d zsCEtg)u%c&wEJLJDmHo+tE~vxQ_gRfsu?gI{ag#V@*{R}eSypgR!%y{GrE4A*sWEM z2P2KvJwf{&U5HgpQy|Lt_ck={lslVTK^>zKl0o0lRJ?M}uYrPANEh@>G{p5x>BDrR zD~OUU)pPhvxNt?q?nt+y1#*Jl;k3;Ui%1dVKET7j_@{4|~_gDgXcg literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-basic-combined/6.png b/tests_zemu/snapshots/sign-basic-combined/6.png new file mode 100644 index 0000000000000000000000000000000000000000..9272431721bcb2a163059663e05df5b61d29d45a GIT binary patch literal 731 zcmX9+e@GKy7`}ORi1RWK)Lnsx=nO23+bNY{H&4k8r_z!von{>8=EyW76hfB5tr5YR z6&7i(HYb9JESIl(l#la{WNA6c=4-X7##Q%>mR&yJ^Zl-5aZ4BQ9Kd>mp%56j- zm^e~^E5{pAs!-pg%x+$xlu#Kax*bdmpyK;umMJ|eNB0rl?OK#dn|G-V`OCgf51@cX z$1hq(EYEdV!C?@W?-lRIc}N^z5!Xa{I%2 zt4vBjQY<37hM>53?Vu6R%yuw*WSo>HVRYYxI|wZi2E`?z0B2F0p508&k3`_QH?uC)f-48X6A;1g z(}7cGP)ZA<9|}?lFEBFD@w`*4_oq_ywI>>Jw~q5}-@~}xTSA>eN-sREzQL%K;Bl}b zBkncEZTP_MFRNiGeuItDCJg>#2C)IxAhwn~8-{LZ0F8hs^vUL4aLGo|OS!CE#aoiz zHiNJyhBV@cW&hRnaC4+%qJ}xT774e^>-W4W?p%28kw+nQab?_@?zZ`ltSv%n2QAfB U7R`J^e9*LrsiL}kwCsG}KkAe#WB>pF literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-basic-combined/7.png b/tests_zemu/snapshots/sign-basic-combined/7.png new file mode 100644 index 0000000000000000000000000000000000000000..610edeb1a7024e87430acf5c9f0dcd587144c1e8 GIT binary patch literal 499 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfi`&ba4!+V0?SfG4H7xgY&`T z$DaS?Kf~&tccjVm7iXHFbmy+sN8+m{u<)L+aXZ|5rbueS&$(3(7C7@ys+hu-vdx*< zD4tWJecOu*%_?$>8E0;n@-tfAHu=nA6NS&29~M4z=3ldjQ8GnOYJ%gli76RBzC3E~ zk(yAsq>X##%7<&)j%PVMyWH(>1XOKdlz(!M>Sqt$6E&tkyxQ8{HiE2b_>{tXV$aor zNtUK_Qs((M+?-~5W188GY2O|#H}Ns5U!6Efwr!MaQ_7|8;SFp105ng zGx)Gd@kN7^HOs~A7c$OV$vAU1TgoyFlqWS|CJa@sW|WkgV0qGq_e6||LhGxY}( zqHjIul*(A}u->t)EtuKJKJkN#(TP9VKs_lL6M29Ne~rAc}Vh+7q(l TbhQsKG8sHw{an^LB{Ts5^#SF` literal 0 HcmV?d00001 diff --git a/tests_zemu/snapshots/sign-basic-combined/8.png b/tests_zemu/snapshots/sign-basic-combined/8.png new file mode 100644 index 0000000000000000000000000000000000000000..3bfe48e647c2265d038e08ea82adb81aaa730150 GIT binary patch literal 400 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFffLDx;TbZFupzLm?vY%zp_e9O|xE(Jpb*soNWt{o=Qp1^W zrc+CGcYRUnJf7k3ES#5n=E{e@&kUs|{QPpz`>?nE8@Fd`6BR#O7?~)1{&cXrM`}Xl zo;J_4tRJDwM*TqT%MYqcO3C>@W0y=3GdgkMg9Ok9(;r@KZI8JnrSjG@&V0`@WqCHx z>a2(pKD_xx{;CUPZcE8TOUp$6esE6eUWyp+O}fDt^Zo#T71H9q0L|M26{9XmrU{ZO=JOFL%CeA$#lC%L`_H?b`~iw)9?Jm}|4Tv2AH%+eL@NFB{pU_FZlbpH#WB zP4V+%Q=XZw&vwUFENxTVZ1U#B1%ni~!xJQVozpTSq}q6RK>`^!CPqq4SeQ6blGoa3 zaiV+L@`nrC6rB%Gh?EkWxs*{7NT%oR2|hf*G0jB5SSrtiXQu2-SGJi;6W!S)fyVhU z8}Xl1*nC2v`Ai1LP@@wdPH{NM9&6qcHC~5fdF5j(rt$Ea=opzxO>1ihV*SaHQu8i> zTocP1f2|;#*{I&9jV)ywTZ-MT53(uumL;C(Y4e}FFflU%sHR`lnc2uh;q&U=@`*on zL6*BdyM038v!+qJTJiKV!6_LI&*t^+cTAgD>5*n~!QpUliW@U;`N}X7VBk6cLpo&| zP$qoW2R_~tf2N%Q3V{Or=EQ}Jpny!v1c!fQN*PeuQpTBc*;3wR0d+xCRZitzuVyUO lcILKejVH5__u>7|?5|%G_GmoX+y+ct44$rjF6*2UngDC22r2*o literal 0 HcmV?d00001 diff --git a/tests_zemu/tests/test.js b/tests_zemu/tests/test.js index 35200958..5c6828e5 100644 --- a/tests_zemu/tests/test.js +++ b/tests_zemu/tests/test.js @@ -81,6 +81,42 @@ const example_tx_str_expert = { "sequence": "106" }; +const example_tx_str_combined = { + "account_number": "108", + "chain_id": "cosmoshub-3", + "fee": { + "amount": [ + { + "amount": "600", + "denom": "uatom" + } + ], + "gas": "200000" + }, + "memo": "", + "msgs": [ + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "cosmos1w34k53py5v5xyluazqpq65agyajavep2rflq6h", + "validator_address": "cosmosvaloper1648ynlpdw7fqa2axt0w2yp3fk542junl7rsvq6" + } + }, + { + "type": "cosmos-sdk/MsgDelegate", + "value": { + "amount": { + "amount": "20139397", + "denom": "uatom" + }, + "delegator_address": "cosmos1w34k53py5v5xyluazqpq65agyajavep2rflq6h", + "validator_address": "cosmosvaloper1648ynlpdw7fqa2axt0w2yp3fk542junl7rsvq6", + } + } + ], + "sequence": "106" +}; + describe('Basic checks', function () { it('can start and stop container', async function () { const sim = new Zemu(APP_PATH); @@ -321,6 +357,62 @@ describe('Basic checks', function () { } }); + it('sign basic - combined tx', async function () { + const snapshotPrefixGolden = "snapshots/sign-basic-combined/"; + const snapshotPrefixTmp = "snapshots-tmp/sign-basic-combined/"; + let snapshotCount = 0; + + const sim = new Zemu(APP_PATH); + try { + await sim.start(sim_options); + const app = new CosmosApp(sim.getTransport()); + + const path = [44, 118, 0, 0, 0]; + let tx = JSON.stringify(example_tx_str_combined); + + // get address / publickey + const respPk = await app.getAddressAndPubKey(path, "cosmos"); + expect(respPk.return_code).toEqual(0x9000); + expect(respPk.error_message).toEqual("No errors"); + console.log(respPk) + + // do not wait here.. + const signatureRequest = app.sign(path, tx); + + await Zemu.sleep(2000); + + // Reference window + await sim.snapshot(`${snapshotPrefixTmp}${snapshotCount++}.png`); + for (let i = 0; i < 10; i++) { + await sim.clickRight(Resolve(`${snapshotPrefixTmp}${snapshotCount++}.png`)); + } + await sim.clickBoth(); + + let resp = await signatureRequest; + console.log(resp); + + compareSnapshots(snapshotPrefixTmp, snapshotPrefixGolden, snapshotCount); + + expect(resp.return_code).toEqual(0x9000); + expect(resp.error_message).toEqual("No errors"); + + // Now verify the signature + const hash = crypto.createHash("sha256"); + const msgHash = Uint8Array.from(hash.update(tx).digest()); + + const signatureDER = resp.signature; + const signature = secp256k1.signatureImport(Uint8Array.from(signatureDER)); + + const pk = Uint8Array.from(respPk.compressed_pk) + + const signatureOk = secp256k1.ecdsaVerify(signature, msgHash, pk); + expect(signatureOk).toEqual(true); + + } finally { + await sim.close(); + } + }); + it('show address and sign basic', async function () { const snapshotPrefixGolden = "snapshots/show-address-and-sign-basic/"; const snapshotPrefixTmp = "snapshots-tmp/show-address-and-sign-basic/"; diff --git a/tests_zemu/tools/debug.mjs b/tests_zemu/tools/debug.mjs index 3114fec3..7c1c6e42 100644 --- a/tests_zemu/tools/debug.mjs +++ b/tests_zemu/tools/debug.mjs @@ -29,15 +29,19 @@ const example_tx_str = { { "type": "cosmos-sdk/MsgWithdrawDelegationReward", "value": { - "delegator_address": "cosmos1w34k53py5v5xyluazqpq65agyajavep2rflq6h", - "validator_address": "cosmosvaloper1kn3wugetjuy4zetlq6wadchfhvu3x740ae6z6x" + "delegator_address": "cosmos19umvgcvk8cxsvzemy239nj9ngc2ltukantgyp3", + "validator_address": "cosmosvaloper1648ynlpdw7fqa2axt0w2yp3fk542junl7rsvq6" } }, { - "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "type": "cosmos-sdk/MsgDelegate", "value": { - "delegator_address": "cosmos1w34k53py5v5xyluazqpq65agyajavep2rflq6h", - "validator_address": "cosmosvaloper1sjllsnramtg3ewxqwwrwjxfgc4n4ef9u2lcnj0" + "amount": { + "amount": "20139397", + "denom": "uatom" + }, + "delegator_address": "cosmos19umvgcvk8cxsvzemy239nj9ngc2ltukantgyp3", + "validator_address": "cosmosvaloper1648ynlpdw7fqa2axt0w2yp3fk542junl7rsvq6", } } ], From 7e20150450381ca8281deb5d4a9df9e589e2eb8b Mon Sep 17 00:00:00 2001 From: hcleonis Date: Tue, 9 Jun 2020 17:35:58 +0200 Subject: [PATCH 73/78] Merged PR #5 into v2.14.0 --- app/Makefile | 9 +++++---- app/src/common/app_main.c | 12 ++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/app/Makefile b/app/Makefile index 2f2f48a6..48b6147d 100755 --- a/app/Makefile +++ b/app/Makefile @@ -93,12 +93,14 @@ DEFINES += HAVE_BAGL_FONT_OPEN_SANS_LIGHT_16PX DEFINES += HAVE_UX_FLOW -#SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl -SDK_SOURCE_PATH += lib_ux +DEFINES += HAVE_BLE +DEFINES += HAVE_BLE_APDU BLE_COMMAND_TIMEOUT_MS=2000 + +SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl else # Assume Nano S DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=128 -DEFINES += HAVE_BOLOS_UX COMPLIANCE_UX_160 HAVE_UX_LEGACY HAVE_UX_FLOW +DEFINES += COMPLIANCE_UX_160 HAVE_UX_LEGACY HAVE_UX_FLOW endif # X specific @@ -152,7 +154,6 @@ APP_SOURCE_PATH += $(MY_DIR)/../deps/jsmn/src SDK_SOURCE_PATH += lib_stusb lib_stusb_impl -#SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl SDK_SOURCE_PATH += lib_ux # Import generic rules from the SDK diff --git a/app/src/common/app_main.c b/app/src/common/app_main.c index fb6c0b42..4b31e3a1 100644 --- a/app/src/common/app_main.c +++ b/app/src/common/app_main.c @@ -168,6 +168,12 @@ void handle_generic_apdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32 void app_init() { io_seproxyhal_init(); + +#ifdef TARGET_NANOX + // grab the current plane mode setting + G_io_app.plane_mode = os_setting_get(OS_SETTING_PLANEMODE, NULL, 0); +#endif // TARGET_NANOX + USB_power(0); USB_power(1); @@ -177,6 +183,12 @@ void app_init() { } view_idle_show(0); + +#ifdef HAVE_BLE + // Enable Bluetooth + BLE_power(0, NULL); + BLE_power(1, "Nano X"); +#endif // HAVE_BLE } #pragma clang diagnostic push From a511b01b419669873567aef724bcfa1cf802d0e2 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sun, 14 Jun 2020 19:19:45 +0200 Subject: [PATCH 74/78] Upgrade to v2.15.0 (#4) * change main screen text * fix left arrow in first screen * removing webusb url * upgrading zxlib * disable getdeviceinfo APDU command * adjusting tx sign fixed options --- app/Makefile | 27 ++++---- app/src/coin.h | 2 +- app/src/common/app_main.c | 7 +- app/src/crypto.c | 2 +- deps/ledger-zxlib/app/common/view.c | 23 +++++++ deps/ledger-zxlib/app/common/view_internal.h | 4 ++ deps/ledger-zxlib/app/common/view_s.c | 11 +++- deps/ledger-zxlib/dockerized_build.mk | 4 +- deps/ledger-zxlib/include/bech32.h | 3 +- deps/ledger-zxlib/include/zxformat.h | 2 +- deps/ledger-zxlib/include/zxversion.h | 2 +- deps/ledger-zxlib/scripts/template.sh | 2 +- deps/ledger-zxlib/src/bech32.c | 8 ++- deps/ledger-zxlib/src/bignum.c | 4 +- deps/ledger-zxlib/tests/bech32.cpp | 20 ++++-- .../show-address-and-sign-basic/13.png | Bin 644 -> 443 bytes .../show-address-and-sign-basic/14.png | Bin 713 -> 448 bytes .../show-address-and-sign-basic/5.png | Bin 495 -> 555 bytes .../show-address-and-sign-basic/6.png | Bin 599 -> 582 bytes tests_zemu/snapshots/show-address-huge/7.png | Bin 495 -> 555 bytes tests_zemu/snapshots/show-address/5.png | Bin 495 -> 555 bytes .../snapshots/sign-basic-combined/0.png | Bin 599 -> 582 bytes .../snapshots/sign-basic-combined/10.png | Bin 713 -> 0 bytes .../snapshots/sign-basic-combined/9.png | Bin 644 -> 443 bytes tests_zemu/snapshots/sign-basic/0.png | Bin 599 -> 582 bytes tests_zemu/snapshots/sign-basic/7.png | Bin 644 -> 443 bytes tests_zemu/snapshots/sign-basic/8.png | Bin 713 -> 0 bytes tests_zemu/snapshots/sign-expert/0.png | Bin 509 -> 493 bytes tests_zemu/snapshots/sign-expert/14.png | Bin 644 -> 443 bytes tests_zemu/snapshots/sign-expert/15.png | Bin 713 -> 0 bytes tests_zemu/tests/test.js | 61 +++++++++--------- tests_zemu/tools/debug.mjs | 24 +++---- 32 files changed, 129 insertions(+), 77 deletions(-) delete mode 100644 tests_zemu/snapshots/sign-basic-combined/10.png delete mode 100644 tests_zemu/snapshots/sign-basic/8.png delete mode 100644 tests_zemu/snapshots/sign-expert/15.png diff --git a/app/Makefile b/app/Makefile index 48b6147d..07f89c85 100755 --- a/app/Makefile +++ b/app/Makefile @@ -27,7 +27,7 @@ include $(BOLOS_SDK)/Makefile.defines # Main app configuration APPNAME = "Cosmos" APPVERSION_M=2 -APPVERSION_N=14 +APPVERSION_N=15 APPVERSION_P=0 APPPATH = "44'/118'" @@ -62,24 +62,23 @@ all: default ############ # Platform -DEFINES += UNUSED\(x\)=\(void\)x -DEFINES += PRINTF\(...\)= +DEFINES += UNUSED\(x\)=\(void\)x +DEFINES += PRINTF\(...\)= APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) -DEFINES += APPVERSION=\"$(APPVERSION)\" +DEFINES += APPVERSION=\"$(APPVERSION)\" -DEFINES += OS_IO_SEPROXYHAL -DEFINES += HAVE_BAGL HAVE_SPRINTF -DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=7 IO_HID_EP_LENGTH=64 HAVE_USB_APDU +DEFINES += OS_IO_SEPROXYHAL +DEFINES += HAVE_BAGL HAVE_SPRINTF +DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=7 IO_HID_EP_LENGTH=64 HAVE_USB_APDU -DEFINES += LEDGER_MAJOR_VERSION=$(APPVERSION_M) LEDGER_MINOR_VERSION=$(APPVERSION_N) LEDGER_PATCH_VERSION=$(APPVERSION_P) +DEFINES += LEDGER_MAJOR_VERSION=$(APPVERSION_M) LEDGER_MINOR_VERSION=$(APPVERSION_N) LEDGER_PATCH_VERSION=$(APPVERSION_P) -DEFINES += USB_SEGMENT_SIZE=64 -DEFINES += HAVE_BOLOS_APP_STACK_CANARY -DEFINES += NDEBUG +DEFINES += USB_SEGMENT_SIZE=64 +DEFINES += HAVE_BOLOS_APP_STACK_CANARY +DEFINES += NDEBUG -WEBUSB_URL = www.ledgerwallet.com -DEFINES += HAVE_WEBUSB WEBUSB_URL_SIZE_B=$(shell echo -n $(WEBUSB_URL) | wc -c) WEBUSB_URL=$(shell echo -n $(WEBUSB_URL) | sed -e "s/./\\\'\0\\\',/g") +DEFINES += HAVE_WEBUSB WEBUSB_URL_SIZE_B=0 WEBUSB_URL="" ifeq ($(TARGET_NAME),TARGET_NANOX) DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=300 @@ -91,7 +90,7 @@ DEFINES += HAVE_BAGL_FONT_OPEN_SANS_REGULAR_11PX DEFINES += HAVE_BAGL_FONT_OPEN_SANS_EXTRABOLD_11PX DEFINES += HAVE_BAGL_FONT_OPEN_SANS_LIGHT_16PX -DEFINES += HAVE_UX_FLOW +DEFINES += HAVE_UX_FLOW DEFINES += HAVE_BLE DEFINES += HAVE_BLE_APDU BLE_COMMAND_TIMEOUT_MS=2000 diff --git a/app/src/coin.h b/app/src/coin.h index 54727608..0526aab1 100644 --- a/app/src/coin.h +++ b/app/src/coin.h @@ -43,7 +43,7 @@ typedef enum { #define VIEW_ADDRESS_LAST_PAGE_DEFAULT 0 #define MENU_MAIN_APP_LINE1 "Cosmos" -#define MENU_MAIN_APP_LINE2 "Hub" +#define MENU_MAIN_APP_LINE2 "ready" #define APPVERSION_LINE1 "Version:" #define APPVERSION_LINE2 ("v" APPVERSION) diff --git a/app/src/common/app_main.c b/app/src/common/app_main.c index 4b31e3a1..dacd345e 100644 --- a/app/src/common/app_main.c +++ b/app/src/common/app_main.c @@ -173,7 +173,7 @@ void app_init() { // grab the current plane mode setting G_io_app.plane_mode = os_setting_get(OS_SETTING_PLANEMODE, NULL, 0); #endif // TARGET_NANOX - + USB_power(0); USB_power(1); @@ -214,8 +214,9 @@ void app_main() { if (rx == 0) THROW(APDU_CODE_EMPTY_BUFFER); - handle_generic_apdu(&flags, &tx, rx); - CHECK_APP_CANARY() + // NOTE: Requested by Ledger +// handle_generic_apdu(&flags, &tx, rx); +// CHECK_APP_CANARY() handleApdu(&flags, &tx, rx); CHECK_APP_CANARY() diff --git a/app/src/crypto.c b/app/src/crypto.c index 5a4460cd..5722c51e 100644 --- a/app/src/crypto.c +++ b/app/src/crypto.c @@ -185,7 +185,7 @@ uint16_t crypto_fillAddress(uint8_t *buffer, uint16_t buffer_len) { ripemd160_32(hashed2_pk, hashed1_pk); char *addr = (char *) (buffer + PK_LEN_SECP256K1); - bech32EncodeFromBytes(addr, buffer_len - PK_LEN_SECP256K1, bech32_hrp, hashed2_pk, CX_RIPEMD160_SIZE); + bech32EncodeFromBytes(addr, buffer_len - PK_LEN_SECP256K1, bech32_hrp, hashed2_pk, CX_RIPEMD160_SIZE, 1); return PK_LEN_SECP256K1 + strlen(addr); } diff --git a/deps/ledger-zxlib/app/common/view.c b/deps/ledger-zxlib/app/common/view.c index 7cfba6da..c537f24c 100644 --- a/deps/ledger-zxlib/app/common/view.c +++ b/deps/ledger-zxlib/app/common/view.c @@ -69,6 +69,18 @@ void h_paging_init() { viewdata.pageCount = 1; } +uint8_t h_paging_can_increase() { + if (viewdata.pageIdx + 1 < viewdata.pageCount) { + return 1; + } else { + // passed page count, go to next index + if (viewdata.itemIdx + 1 < viewdata.itemCount) { + return 1; + } + } + return 0; +} + void h_paging_increase() { if (viewdata.pageIdx + 1 < viewdata.pageCount) { // increase page @@ -82,6 +94,17 @@ void h_paging_increase() { } } +uint8_t h_paging_can_decrease() { + if (viewdata.pageIdx != 0) { + return 1; + } else { + if (viewdata.itemIdx > 0) { + return 1; + } + } + return 0; +} + void h_paging_decrease() { if (viewdata.pageIdx != 0) { viewdata.pageIdx--; diff --git a/deps/ledger-zxlib/app/common/view_internal.h b/deps/ledger-zxlib/app/common/view_internal.h index 9df78069..6c530006 100644 --- a/deps/ledger-zxlib/app/common/view_internal.h +++ b/deps/ledger-zxlib/app/common/view_internal.h @@ -101,8 +101,12 @@ void h_sign_reject(unsigned int _); void h_paging_init(); +uint8_t h_paging_can_increase(); + void h_paging_increase(); +uint8_t h_paging_can_decrease(); + void h_paging_decrease(); void h_paging_set_page_count(uint8_t pageCount); diff --git a/deps/ledger-zxlib/app/common/view_s.c b/deps/ledger-zxlib/app/common/view_s.c index a25ed8f2..2b642d0f 100644 --- a/deps/ledger-zxlib/app/common/view_s.c +++ b/deps/ledger-zxlib/app/common/view_s.c @@ -87,8 +87,7 @@ UX_FLOW( void h_review(unsigned int _) { UNUSED(_); view_sign_show_impl(); } const ux_menu_entry_t menu_sign[] = { - {NULL, h_review, 0, NULL, "View transaction", NULL, 0, 0}, - {NULL, h_sign_accept, 0, NULL, "Sign transaction", NULL, 0, 0}, + {NULL, h_sign_accept, 0, NULL, "Approve", NULL, 0, 0}, {NULL, h_sign_reject, 0, NULL, "Reject", NULL, 0, 0}, UX_MENU_END }; @@ -164,7 +163,15 @@ static unsigned int view_review_button(unsigned int button_mask, unsigned int bu const bagl_element_t *view_prepro(const bagl_element_t *element) { switch (element->component.userid) { case UIID_ICONLEFT: + if (!h_paging_can_decrease()){ + return NULL; + } + UX_CALLBACK_SET_INTERVAL(2000); + break; case UIID_ICONRIGHT: + if (!h_paging_can_increase()){ + return NULL; + } UX_CALLBACK_SET_INTERVAL(2000); break; case UIID_LABELSCROLL: diff --git a/deps/ledger-zxlib/dockerized_build.mk b/deps/ledger-zxlib/dockerized_build.mk index 3fe7042b..52cebcc8 100644 --- a/deps/ledger-zxlib/dockerized_build.mk +++ b/deps/ledger-zxlib/dockerized_build.mk @@ -87,13 +87,13 @@ build: build_rust $(info Replacing app icon) @cp $(LEDGER_SRC)/nanos_icon.gif $(LEDGER_SRC)/glyphs/icon_app.gif $(info calling make inside docker) - $(call run_docker,$(DOCKER_BOLOS_SDK),make -C $(DOCKER_APP_SRC)) + $(call run_docker,$(DOCKER_BOLOS_SDK),make -j `nproc` -C $(DOCKER_APP_SRC)) .PHONY: buildX buildX: build_rust @cp $(LEDGER_SRC)/nanos_icon.gif $(LEDGER_SRC)/glyphs/icon_app.gif @convert $(LEDGER_SRC)/nanos_icon.gif -crop 14x14+1+1 +repage -negate $(LEDGER_SRC)/nanox_icon.gif - $(call run_docker,$(DOCKER_BOLOS_SDKX),make -C $(DOCKER_APP_SRC)) + $(call run_docker,$(DOCKER_BOLOS_SDKX),make -j `nproc` -C $(DOCKER_APP_SRC)) .PHONY: clean clean: diff --git a/deps/ledger-zxlib/include/bech32.h b/deps/ledger-zxlib/include/bech32.h index e3cb6ea7..bd9fd148 100644 --- a/deps/ledger-zxlib/include/bech32.h +++ b/deps/ledger-zxlib/include/bech32.h @@ -31,7 +31,8 @@ zxerr_t bech32EncodeFromBytes(char *out, size_t out_len, const char *hrp, const uint8_t *in, - size_t in_len); + size_t in_len, + uint8_t pad); #ifdef __cplusplus } diff --git a/deps/ledger-zxlib/include/zxformat.h b/deps/ledger-zxlib/include/zxformat.h index b5fbb4ba..00fec0c7 100644 --- a/deps/ledger-zxlib/include/zxformat.h +++ b/deps/ledger-zxlib/include/zxformat.h @@ -217,7 +217,7 @@ __Z_INLINE uint32_t array_to_hexstr(char *dst, uint16_t dstLen, const uint8_t *s return 0; } - const char hexchars[] = "0123456789ABCDEF"; + const char hexchars[] = "0123456789abcdef"; for (uint8_t i = 0; i < count; i++, src++) { *dst++ = hexchars[*src >> 4u]; *dst++ = hexchars[*src & 0x0Fu]; diff --git a/deps/ledger-zxlib/include/zxversion.h b/deps/ledger-zxlib/include/zxversion.h index d7d45776..5de79907 100644 --- a/deps/ledger-zxlib/include/zxversion.h +++ b/deps/ledger-zxlib/include/zxversion.h @@ -16,5 +16,5 @@ #pragma once #define ZXLIB_MAJOR 2 -#define ZXLIB_MINOR 0 +#define ZXLIB_MINOR 2 #define ZXLIB_PATCH 0 diff --git a/deps/ledger-zxlib/scripts/template.sh b/deps/ledger-zxlib/scripts/template.sh index 3816f5d8..8f0e7206 100755 --- a/deps/ledger-zxlib/scripts/template.sh +++ b/deps/ledger-zxlib/scripts/template.sh @@ -31,7 +31,7 @@ python3 -m ledgerblue.loadApp -h &>/dev/null; if [ $? -ne 0 ]; then echo echo "ERR: ledgerblue pip package not found." - echo "please install using 'pip3 install ledgerblue'" + echo "please install using 'pip install ledgerblue'" echo exit fi diff --git a/deps/ledger-zxlib/src/bech32.c b/deps/ledger-zxlib/src/bech32.c index 741b4d28..21fc639b 100644 --- a/deps/ledger-zxlib/src/bech32.c +++ b/deps/ledger-zxlib/src/bech32.c @@ -25,7 +25,8 @@ zxerr_t bech32EncodeFromBytes(char *out, size_t out_len, const char *hrp, const uint8_t *in, - size_t in_len) { + size_t in_len, + uint8_t pad) { MEMZERO(out, out_len); if (in_len > MAX_INPUT_SIZE) { @@ -34,15 +35,16 @@ zxerr_t bech32EncodeFromBytes(char *out, size_t hrplen = strlen(hrp); // We set a lower bound to ensure this is safe - if (out_len < hrplen + (in_len*2) + 7) { + if (out_len < hrplen + (in_len * 2) + 7) { return zxerr_buffer_too_small; } // Overestimate required size *2==(8/4) instead of *(8/5) uint8_t tmp_data[MAX_INPUT_SIZE * 2]; size_t tmp_size = 0; + MEMZERO(tmp_data, sizeof(tmp_data)); - convert_bits(tmp_data, &tmp_size, 5, in, in_len, 8, 0); + convert_bits(tmp_data, &tmp_size, 5, in, in_len, 8, pad); if (tmp_size >= out_len) { return zxerr_out_of_bounds; } diff --git a/deps/ledger-zxlib/src/bignum.c b/deps/ledger-zxlib/src/bignum.c index 59cb0459..b4c3e483 100644 --- a/deps/ledger-zxlib/src/bignum.c +++ b/deps/ledger-zxlib/src/bignum.c @@ -68,7 +68,7 @@ void bignumLittleEndian_to_bcd(uint8_t *bcdOut, uint16_t bcdOutLen, // get bit const uint16_t byteIdx = bitIdx >> 3u; - const uint8_t mask = 0x80u >> (bitIdx & 0x7u); + const uint8_t mask = 0x80u >> (bitIdx & 0b111u); carry = (binValue[binValueLen - byteIdx - 1] & mask) > 0; // Shift bcd @@ -134,7 +134,7 @@ void bignumBigEndian_to_bcd(uint8_t *bcdOut, uint16_t bcdOutLen, // get bit const uint16_t byteIdx = bitIdx >> 3u; - const uint8_t mask = 0x80u >> (bitIdx & 0x7u); + const uint8_t mask = 0x80u >> (bitIdx & 0b111u); carry = (binValue[byteIdx] & mask) > 0; // Shift bcd diff --git a/deps/ledger-zxlib/tests/bech32.cpp b/deps/ledger-zxlib/tests/bech32.cpp index 4d8f204e..4e791419 100644 --- a/deps/ledger-zxlib/tests/bech32.cpp +++ b/deps/ledger-zxlib/tests/bech32.cpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace { TEST(BECH32, hex_to_address) { @@ -25,15 +26,26 @@ namespace { uint8_t data1[] = {1, 3, 5}; uint8_t data2[] = {1, 3, 5, 7, 9, 11, 13}; - auto err = bech32EncodeFromBytes(addr_out, sizeof(addr_out), hrp, data1, sizeof(data1)); + auto err = bech32EncodeFromBytes(addr_out, sizeof(addr_out), hrp, data1, sizeof(data1), 0); ASSERT_EQ(err, zxerr_ok); std::cout << addr_out << std::endl; ASSERT_STREQ("zx1qypse825ac", addr_out); - err = bech32EncodeFromBytes(addr_out, sizeof(addr_out), hrp, data2, sizeof(data2)); + err = bech32EncodeFromBytes(addr_out, sizeof(addr_out), hrp, data2, sizeof(data2), 0); ASSERT_EQ(err, zxerr_ok); std::cout << addr_out << std::endl; ASSERT_STREQ("zx1qyps2pcfpvx20dk22", addr_out); + + /// + err = bech32EncodeFromBytes(addr_out, sizeof(addr_out), hrp, data1, sizeof(data1), 1); + ASSERT_EQ(err, zxerr_ok); + std::cout << addr_out << std::endl; + ASSERT_STREQ("zx1qyps2ucfnzd", addr_out); + + err = bech32EncodeFromBytes(addr_out, sizeof(addr_out), hrp, data2, sizeof(data2), 1); + ASSERT_EQ(err, zxerr_ok); + std::cout << addr_out << std::endl; + ASSERT_STREQ("zx1qyps2pcfpvxshamanz", addr_out); } TEST(BECH32, huge_input) { @@ -42,7 +54,7 @@ namespace { auto data = std::vector(1000, 0x55); - auto err = bech32EncodeFromBytes(addr_out, sizeof(addr_out), hrp, data.data(), data.size()); + auto err = bech32EncodeFromBytes(addr_out, sizeof(addr_out), hrp, data.data(), data.size(),0); ASSERT_EQ(err, zxerr_out_of_bounds); std::cout << addr_out << std::endl; @@ -59,7 +71,7 @@ namespace { // declare size to be smaller const size_t declared_size = 52; - auto err = bech32EncodeFromBytes(addr_out, declared_size, hrp, data.data(), data.size()); + auto err = bech32EncodeFromBytes(addr_out, declared_size, hrp, data.data(), data.size(), 0); ASSERT_EQ(err, zxerr_buffer_too_small); for (int i = declared_size; i < sizeof(addr_out); i++) { diff --git a/tests_zemu/snapshots/show-address-and-sign-basic/13.png b/tests_zemu/snapshots/show-address-and-sign-basic/13.png index dae18a50f0dcadb5df981183c70f20bbf47bfd71..4aa28b6bfdc9668cf49edfe790ea290941e99f83 100644 GIT binary patch delta 418 zcmZo+-OW5fxt_7f)5S5Qg7NLai>stP8JrI;KKAQB|Fvc#lf)f;Z;$FUR@{kIt$Xb# zbUW$_GrMfngd^Md7@uAc1GCx=^4o6Eb#=Ho&Gg1JGc>eg5}VYv{pkgwyvy6(zTfeY zS4!@L4=;bpGPabA87a%!CZD-taI$`ml-kUd5Btt6myU5f+}y@)q<_-pYQgfvNi#tb ziCG|(o@_uR^D`?}JzRcp>IsF<)7qv>y*r`cd^ni*|H_9~91h#7J0G6QmQuH?P?cBS z|5@^h3k^KHC;nu9cq!#)bYhNF+@wm+XPPsYr+|!{G&99+8c^ECcn8 literal 644 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfe(0x;TbZFupw)m?s@6;(YLM z+m8RNpPQ9qc;xO(6tM_Xk$$;O=>O}fDt^Zo#T71H9q0L|M26{9XmrU{ZO=JOFL%CeA$#lC%L`_H?b`~iw)9?Jm}|4Tv2AH%+eL@NFB{pU_FZlbpH#WB zP4V+%Q=XZw&vwUFENxTVZ1U#B1%ni~!xJQVozpTSq}q6RK>`^!CPqq4SeQ6blGoa3 zaiV+L@`nrC6rB%Gh?EkWxs*{7NT%oR2|hf*G0jB5SSrtiXQu2-SGJi;6W!S)fyVhU z8}Xl1*nC2v`Ai1LP@@wdPH{NM9&6qcHC~5fdF5j(rt$Ea=opzxO>1ihV*SaHQu8i> zTocP1f2|;#*{I&9jV)ywTZ-MT53(uumL;C(Y4e}FFflU%sHR`lnc2uh;q&U=@`*on zL6*BdyM038v!+qJTJiKV!6_LI&*t^+cTAgD>5*n~!QpUliW@U;`N}X7VBk6cLpo&| zP$qoW2R_~tf2N%Q3V{Or=EQ}Jpny!v1c!fQN*PeuQpTBc*;3wR0d+xCRZitzuVyUO lcILKejVH5__u>7|?5|%G_GmoX+y+ct44$rjF6*2UngDC22r2*o diff --git a/tests_zemu/snapshots/show-address-and-sign-basic/14.png b/tests_zemu/snapshots/show-address-and-sign-basic/14.png index 1e430afe4949126c0cf7e2855169f446ec26c4f6..a3a54e0001f8a5eb42df7d1a293a638209da292d 100644 GIT binary patch literal 448 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfg`zx;TbZFupw)*rj65;Bs*K zqR;=>&-IrmUOTjPbF(DRveipDK6>jtSzKjFiBLWdWHgm8i4KxA`qwYc7{FG`); z)i#^_S)Yb)P%}tBa;gaXT*){m5MKa zILY?^O2e669^QVX&&o#kC;d!eIwfrCV-$Q?Wi!}Qlh0f#PzEu5fF8Tdp<$ghBc;sD nVw>5GK%f$9bdSJ&1YyKKm!B1BpnB057&8o>u6{1-oD!M<(!R|_ literal 713 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfdKvUzxavyHU=5CL*{+Pq7ZDhh@)XvY_e5PC|VBXTi2|tT~?EdQpC;phI z$1O~pASq>b@%l5SqmE5rna@4=44gO06K&BM9sDjtJzZ6 zB&FUd8JqqIJFM_oRfY+ad5r9*@fev; ztekO1ZRUSpU|?>W^TQ`?@)`M)K+k-R26{F&C1XM2gq;e`%)I5<5fde)GH$2<*^_yU zOfDFlh?%gkvCZ*WX39FjfH;!?bn3^_;zna32^m$j=;e3 zm6~vKb8ERG(9Rake|ZKH|uze{sf z^qwzzcO&yUh@rUP&8IIebj~ZlR?p<5LYOF{A1Kuk|j@kVw({KVN#k>tPjR zQ=ODHwiKX9#*CC@ZJuda5hrS9_1<5QI6+eC-3f)w84hU~^$ySc4l5X&+?nY3OgL92 z_OLT=H=ESFOCJIbFKTS_FL@lvt3R<)TT*HnTgpABwzidwGyOLemKgD$^f5YN|I)`ADH#hACrN_H2{VC?@t?S`vCR=|r4-{+NnZYwI#NJu)n=Y9%-Pd?Mt0`u zkGXri(z5CoBwj4|$;iuZTC?>d*UXiP6CzK<)HUz`U6HcyRz*9=j2(XrB&C2p`Iq-N z7U+@FjwdcOoKcp3uV8G-W7K}Z;qc0nej2HTQe(JEUDSIKjhvU(NaOQ-5i9)z9ls)OZ7pOJh1E2sF97t@X@> z0(X#)eRwCInaQb-g-D6L$n4MdeIALc)Ly_g$-qR~{WBlIo3z=V6evx^Ymw}ss Ofx*+&&t;ucLK6T6_3DfO delta 469 zcmV;`0V@8h1n&coB!9L^L_t(|0qwz2t{O)GMA2IaZEHX>Ofy}2F{0SN+nW^E13^E4|$bU=?$lPd{WTu82GRS2!za4%sD`J=yp#+LO9L`5Aw$E`fXpWuygQk@WEL4@9wu{_ z%p!x#r@HViWN4sak(p#@pkb0(8V+P0B0~cWlg!d^AoFPilt(n&C3Db#%+#>RZ0}?6 z3Nkljw)Z!9mVeCBK*K@9_6-{DYWQmK44I{2k~xsszJUx42Mx%4HF$-FNoHx-zL^XS zQv))8H+U79rD1y$GBhA_&)^kgmWD~@Aq_Mv4K(~K4K!>&A@ehXXEYo%OfpNu_P#DW zL*_u{py6d2CK(zgnS+K!X8X0lD>U3__$>`I{1%z*F-{kr(XceUgv`|Nlm=wBe>T`o zZSbjNwqLvOD4B27fD8>c8n(v>DBEcbwo@Byr#9G5ZLpo%U^}%RtUk97XS+>;00000 LNkvXXu0mjfXl2u# diff --git a/tests_zemu/snapshots/show-address-and-sign-basic/6.png b/tests_zemu/snapshots/show-address-and-sign-basic/6.png index 5b34a92c9c97a52ed765e8ea42221e632c281456..871ae85965ca1ef17e915e17f9c842259d6bb100 100644 GIT binary patch delta 557 zcmcc4a*SnyNT9r6>wfgs zSNZtXbdTcZO5PKH{PsR@65l)FNN|Qj+IKHzqxFAhJpXBO-Z0hT=iIaD83}fulO9d2kNdDDB|}1Lf@I1B z$F{bGjFKtyns|8i`Ti(%-WBEbU(7gDoV`qbawQkCSKw8@f!f9(0o#~hcbn1l$9$pAh z19WtETPuuvq2Y`ZP{G7{NubxI@?IX;oV8R6HI^zs-2FRcqkG3}clOU6TE|fdl=zd|tgrCZiQvae3$DX`kaH7WR zaC=I|4spiR?UBN}eP`duy>%q-vRc_}vyHd6blzR&`^GF+EopluirhE(j-SsXS}s=W PF#v(5tDnm{r-UW|Zv+5y literal 599 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfhq^x;TbZFupx_*Y9=~gUdzR zyk}b%M^wJqIYW5OC(h5oUj70J)w&;}^Hm&~jqJk?a~tix-f*V)6|ecOm%S=x3mIn~ z7Un%+^EG?k^V2rxx9okf>}~42^twH#C7)?KGk=~smF=0fNrC0zr##b*&xD6b?U?hh zsO{&ee$)7cjXzH*3r zjexvKAiIo>W?yJH6D;h1^1_BlBNHHdor%KdJDtx{%2p;$kW9&tNU2kqd40|et(oR0 z_b7Z;o4IzD#hY0TMK{)VR%b?JE=XLOc=5xsw*A*Wyp+mV(AcJby})1iyOQ(a*-{fC zPw?>GpT}c#;=%@1^?gpvMkYE^Vn*F%?V1MR0OgH=>bNDPCM;wG6EpcUfyy?(xIi(G-P%S#&pRKs zoqJlrX7(o`=f|l1>AS{@;CO(L>gm^=g|lerO4QR{^R7IiQ9c=%md+b7q#G zyzoH}Y!EO|%8MUaXGEO%Gm(e4e(l3xVDO%(@i@FYCF6$<)2YPmYJ9uR3U6%fJiN|V trY!f-hHT;X*x43uZXHRyjw1Ee{&B(QDBH~zbAbtl!PC{xWt~$(696`102KfL diff --git a/tests_zemu/snapshots/show-address-huge/7.png b/tests_zemu/snapshots/show-address-huge/7.png index 766426dc9e323299a15b15d7c09b0080a8243ecd..1f186b3ef0980434a970b1190ed43225fd808cf5 100644 GIT binary patch delta 530 zcmaFQyqaZ#O8qBK7srqa#Rake|ZKH|uze{sf z^qwzzcO&yUh@rUP&8IIebj~ZlR?p<5LYOF{A1Kuk|j@kVw({KVN#k>tPjR zQ=ODHwiKX9#*CC@ZJuda5hrS9_1<5QI6+eC-3f)w84hU~^$ySc4l5X&+?nY3OgL92 z_OLT=H=ESFOCJIbFKTS_FL@lvt3R<)TT*HnTgpABwzidwGyOLemKgD$^f5YN|I)`ADH#hACrN_H2{VC?@t?S`vCR=|r4-{+NnZYwI#NJu)n=Y9%-Pd?Mt0`u zkGXri(z5CoBwj4|$;iuZTC?>d*UXiP6CzK<)HUz`U6HcyRz*9=j2(XrB&C2p`Iq-N z7U+@FjwdcOoKcp3uV8G-W7K}Z;qc0nej2HTQe(JEUDSIKjhvU(NaOQ-5i9)z9ls)OZ7pOJh1E2sF97t@X@> z0(X#)eRwCInaQb-g-D6L$n4MdeIALc)Ly_g$-qR~{WBlIo3z=V6evx^Ymw}ss Ofx*+&&t;ucLK6T6_3DfO delta 469 zcmV;`0V@8h1n&coB!9L^L_t(|0qwz2t{O)GMA2IaZEHX>Ofy}2F{0SN+nW^E13^E4|$bU=?$lPd{WTu82GRS2!za4%sD`J=yp#+LO9L`5Aw$E`fXpWuygQk@WEL4@9wu{_ z%p!x#r@HViWN4sak(p#@pkb0(8V+P0B0~cWlg!d^AoFPilt(n&C3Db#%+#>RZ0}?6 z3Nkljw)Z!9mVeCBK*K@9_6-{DYWQmK44I{2k~xsszJUx42Mx%4HF$-FNoHx-zL^XS zQv))8H+U79rD1y$GBhA_&)^kgmWD~@Aq_Mv4K(~K4K!>&A@ehXXEYo%OfpNu_P#DW zL*_u{py6d2CK(zgnS+K!X8X0lD>U3__$>`I{1%z*F-{kr(XceUgv`|Nlm=wBe>T`o zZSbjNwqLvOD4B27fD8>c8n(v>DBEcbwo@Byr#9G5ZLpo%U^}%RtUk97XS+>;00000 LNkvXXu0mjfXl2u# diff --git a/tests_zemu/snapshots/show-address/5.png b/tests_zemu/snapshots/show-address/5.png index 766426dc9e323299a15b15d7c09b0080a8243ecd..1f186b3ef0980434a970b1190ed43225fd808cf5 100644 GIT binary patch delta 530 zcmaFQyqaZ#O8qBK7srqa#Rake|ZKH|uze{sf z^qwzzcO&yUh@rUP&8IIebj~ZlR?p<5LYOF{A1Kuk|j@kVw({KVN#k>tPjR zQ=ODHwiKX9#*CC@ZJuda5hrS9_1<5QI6+eC-3f)w84hU~^$ySc4l5X&+?nY3OgL92 z_OLT=H=ESFOCJIbFKTS_FL@lvt3R<)TT*HnTgpABwzidwGyOLemKgD$^f5YN|I)`ADH#hACrN_H2{VC?@t?S`vCR=|r4-{+NnZYwI#NJu)n=Y9%-Pd?Mt0`u zkGXri(z5CoBwj4|$;iuZTC?>d*UXiP6CzK<)HUz`U6HcyRz*9=j2(XrB&C2p`Iq-N z7U+@FjwdcOoKcp3uV8G-W7K}Z;qc0nej2HTQe(JEUDSIKjhvU(NaOQ-5i9)z9ls)OZ7pOJh1E2sF97t@X@> z0(X#)eRwCInaQb-g-D6L$n4MdeIALc)Ly_g$-qR~{WBlIo3z=V6evx^Ymw}ss Ofx*+&&t;ucLK6T6_3DfO delta 469 zcmV;`0V@8h1n&coB!9L^L_t(|0qwz2t{O)GMA2IaZEHX>Ofy}2F{0SN+nW^E13^E4|$bU=?$lPd{WTu82GRS2!za4%sD`J=yp#+LO9L`5Aw$E`fXpWuygQk@WEL4@9wu{_ z%p!x#r@HViWN4sak(p#@pkb0(8V+P0B0~cWlg!d^AoFPilt(n&C3Db#%+#>RZ0}?6 z3Nkljw)Z!9mVeCBK*K@9_6-{DYWQmK44I{2k~xsszJUx42Mx%4HF$-FNoHx-zL^XS zQv))8H+U79rD1y$GBhA_&)^kgmWD~@Aq_Mv4K(~K4K!>&A@ehXXEYo%OfpNu_P#DW zL*_u{py6d2CK(zgnS+K!X8X0lD>U3__$>`I{1%z*F-{kr(XceUgv`|Nlm=wBe>T`o zZSbjNwqLvOD4B27fD8>c8n(v>DBEcbwo@Byr#9G5ZLpo%U^}%RtUk97XS+>;00000 LNkvXXu0mjfXl2u# diff --git a/tests_zemu/snapshots/sign-basic-combined/0.png b/tests_zemu/snapshots/sign-basic-combined/0.png index 5b34a92c9c97a52ed765e8ea42221e632c281456..871ae85965ca1ef17e915e17f9c842259d6bb100 100644 GIT binary patch delta 557 zcmcc4a*SnyNT9r6>wfgs zSNZtXbdTcZO5PKH{PsR@65l)FNN|Qj+IKHzqxFAhJpXBO-Z0hT=iIaD83}fulO9d2kNdDDB|}1Lf@I1B z$F{bGjFKtyns|8i`Ti(%-WBEbU(7gDoV`qbawQkCSKw8@f!f9(0o#~hcbn1l$9$pAh z19WtETPuuvq2Y`ZP{G7{NubxI@?IX;oV8R6HI^zs-2FRcqkG3}clOU6TE|fdl=zd|tgrCZiQvae3$DX`kaH7WR zaC=I|4spiR?UBN}eP`duy>%q-vRc_}vyHd6blzR&`^GF+EopluirhE(j-SsXS}s=W PF#v(5tDnm{r-UW|Zv+5y literal 599 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfhq^x;TbZFupx_*Y9=~gUdzR zyk}b%M^wJqIYW5OC(h5oUj70J)w&;}^Hm&~jqJk?a~tix-f*V)6|ecOm%S=x3mIn~ z7Un%+^EG?k^V2rxx9okf>}~42^twH#C7)?KGk=~smF=0fNrC0zr##b*&xD6b?U?hh zsO{&ee$)7cjXzH*3r zjexvKAiIo>W?yJH6D;h1^1_BlBNHHdor%KdJDtx{%2p;$kW9&tNU2kqd40|et(oR0 z_b7Z;o4IzD#hY0TMK{)VR%b?JE=XLOc=5xsw*A*Wyp+mV(AcJby})1iyOQ(a*-{fC zPw?>GpT}c#;=%@1^?gpvMkYE^Vn*F%?V1MR0OgH=>bNDPCM;wG6EpcUfyy?(xIi(G-P%S#&pRKs zoqJlrX7(o`=f|l1>AS{@;CO(L>gm^=g|lerO4QR{^R7IiQ9c=%md+b7q#G zyzoH}Y!EO|%8MUaXGEO%Gm(e4e(l3xVDO%(@i@FYCF6$<)2YPmYJ9uR3U6%fJiN|V trY!f-hHT;X*x43uZXHRyjw1Ee{&B(QDBH~zbAbtl!PC{xWt~$(696`102KfL diff --git a/tests_zemu/snapshots/sign-basic-combined/10.png b/tests_zemu/snapshots/sign-basic-combined/10.png deleted file mode 100644 index 1e430afe4949126c0cf7e2855169f446ec26c4f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 713 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfdKvUzxavyHU=5CL*{+Pq7ZDhh@)XvY_e5PC|VBXTi2|tT~?EdQpC;phI z$1O~pASq>b@%l5SqmE5rna@4=44gO06K&BM9sDjtJzZ6 zB&FUd8JqqIJFM_oRfY+ad5r9*@fev; ztekO1ZRUSpU|?>W^TQ`?@)`M)K+k-R26{F&C1XM2gq;e`%)I5<5fde)GH$2<*^_yU zOfDFlh?%gkvCZ*WX39FjfH;!?bn3^_;zna32^m$j=;e3 zm6~vKb8ERG(9stP8JrI;KKAQB|Fvc#lf)f;Z;$FUR@{kIt$Xb# zbUW$_GrMfngd^Md7@uAc1GCx=^4o6Eb#=Ho&Gg1JGc>eg5}VYv{pkgwyvy6(zTfeY zS4!@L4=;bpGPabA87a%!CZD-taI$`ml-kUd5Btt6myU5f+}y@)q<_-pYQgfvNi#tb ziCG|(o@_uR^D`?}JzRcp>IsF<)7qv>y*r`cd^ni*|H_9~91h#7J0G6QmQuH?P?cBS z|5@^h3k^KHC;nu9cq!#)bYhNF+@wm+XPPsYr+|!{G&99+8c^ECcn8 literal 644 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfe(0x;TbZFupw)m?s@6;(YLM z+m8RNpPQ9qc;xO(6tM_Xk$$;O=>O}fDt^Zo#T71H9q0L|M26{9XmrU{ZO=JOFL%CeA$#lC%L`_H?b`~iw)9?Jm}|4Tv2AH%+eL@NFB{pU_FZlbpH#WB zP4V+%Q=XZw&vwUFENxTVZ1U#B1%ni~!xJQVozpTSq}q6RK>`^!CPqq4SeQ6blGoa3 zaiV+L@`nrC6rB%Gh?EkWxs*{7NT%oR2|hf*G0jB5SSrtiXQu2-SGJi;6W!S)fyVhU z8}Xl1*nC2v`Ai1LP@@wdPH{NM9&6qcHC~5fdF5j(rt$Ea=opzxO>1ihV*SaHQu8i> zTocP1f2|;#*{I&9jV)ywTZ-MT53(uumL;C(Y4e}FFflU%sHR`lnc2uh;q&U=@`*on zL6*BdyM038v!+qJTJiKV!6_LI&*t^+cTAgD>5*n~!QpUliW@U;`N}X7VBk6cLpo&| zP$qoW2R_~tf2N%Q3V{Or=EQ}Jpny!v1c!fQN*PeuQpTBc*;3wR0d+xCRZitzuVyUO lcILKejVH5__u>7|?5|%G_GmoX+y+ct44$rjF6*2UngDC22r2*o diff --git a/tests_zemu/snapshots/sign-basic/0.png b/tests_zemu/snapshots/sign-basic/0.png index 5b34a92c9c97a52ed765e8ea42221e632c281456..871ae85965ca1ef17e915e17f9c842259d6bb100 100644 GIT binary patch delta 557 zcmcc4a*SnyNT9r6>wfgs zSNZtXbdTcZO5PKH{PsR@65l)FNN|Qj+IKHzqxFAhJpXBO-Z0hT=iIaD83}fulO9d2kNdDDB|}1Lf@I1B z$F{bGjFKtyns|8i`Ti(%-WBEbU(7gDoV`qbawQkCSKw8@f!f9(0o#~hcbn1l$9$pAh z19WtETPuuvq2Y`ZP{G7{NubxI@?IX;oV8R6HI^zs-2FRcqkG3}clOU6TE|fdl=zd|tgrCZiQvae3$DX`kaH7WR zaC=I|4spiR?UBN}eP`duy>%q-vRc_}vyHd6blzR&`^GF+EopluirhE(j-SsXS}s=W PF#v(5tDnm{r-UW|Zv+5y literal 599 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfhq^x;TbZFupx_*Y9=~gUdzR zyk}b%M^wJqIYW5OC(h5oUj70J)w&;}^Hm&~jqJk?a~tix-f*V)6|ecOm%S=x3mIn~ z7Un%+^EG?k^V2rxx9okf>}~42^twH#C7)?KGk=~smF=0fNrC0zr##b*&xD6b?U?hh zsO{&ee$)7cjXzH*3r zjexvKAiIo>W?yJH6D;h1^1_BlBNHHdor%KdJDtx{%2p;$kW9&tNU2kqd40|et(oR0 z_b7Z;o4IzD#hY0TMK{)VR%b?JE=XLOc=5xsw*A*Wyp+mV(AcJby})1iyOQ(a*-{fC zPw?>GpT}c#;=%@1^?gpvMkYE^Vn*F%?V1MR0OgH=>bNDPCM;wG6EpcUfyy?(xIi(G-P%S#&pRKs zoqJlrX7(o`=f|l1>AS{@;CO(L>gm^=g|lerO4QR{^R7IiQ9c=%md+b7q#G zyzoH}Y!EO|%8MUaXGEO%Gm(e4e(l3xVDO%(@i@FYCF6$<)2YPmYJ9uR3U6%fJiN|V trY!f-hHT;X*x43uZXHRyjw1Ee{&B(QDBH~zbAbtl!PC{xWt~$(696`102KfL diff --git a/tests_zemu/snapshots/sign-basic/7.png b/tests_zemu/snapshots/sign-basic/7.png index dae18a50f0dcadb5df981183c70f20bbf47bfd71..4aa28b6bfdc9668cf49edfe790ea290941e99f83 100644 GIT binary patch delta 418 zcmZo+-OW5fxt_7f)5S5Qg7NLai>stP8JrI;KKAQB|Fvc#lf)f;Z;$FUR@{kIt$Xb# zbUW$_GrMfngd^Md7@uAc1GCx=^4o6Eb#=Ho&Gg1JGc>eg5}VYv{pkgwyvy6(zTfeY zS4!@L4=;bpGPabA87a%!CZD-taI$`ml-kUd5Btt6myU5f+}y@)q<_-pYQgfvNi#tb ziCG|(o@_uR^D`?}JzRcp>IsF<)7qv>y*r`cd^ni*|H_9~91h#7J0G6QmQuH?P?cBS z|5@^h3k^KHC;nu9cq!#)bYhNF+@wm+XPPsYr+|!{G&99+8c^ECcn8 literal 644 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfe(0x;TbZFupw)m?s@6;(YLM z+m8RNpPQ9qc;xO(6tM_Xk$$;O=>O}fDt^Zo#T71H9q0L|M26{9XmrU{ZO=JOFL%CeA$#lC%L`_H?b`~iw)9?Jm}|4Tv2AH%+eL@NFB{pU_FZlbpH#WB zP4V+%Q=XZw&vwUFENxTVZ1U#B1%ni~!xJQVozpTSq}q6RK>`^!CPqq4SeQ6blGoa3 zaiV+L@`nrC6rB%Gh?EkWxs*{7NT%oR2|hf*G0jB5SSrtiXQu2-SGJi;6W!S)fyVhU z8}Xl1*nC2v`Ai1LP@@wdPH{NM9&6qcHC~5fdF5j(rt$Ea=opzxO>1ihV*SaHQu8i> zTocP1f2|;#*{I&9jV)ywTZ-MT53(uumL;C(Y4e}FFflU%sHR`lnc2uh;q&U=@`*on zL6*BdyM038v!+qJTJiKV!6_LI&*t^+cTAgD>5*n~!QpUliW@U;`N}X7VBk6cLpo&| zP$qoW2R_~tf2N%Q3V{Or=EQ}Jpny!v1c!fQN*PeuQpTBc*;3wR0d+xCRZitzuVyUO lcILKejVH5__u>7|?5|%G_GmoX+y+ct44$rjF6*2UngDC22r2*o diff --git a/tests_zemu/snapshots/sign-basic/8.png b/tests_zemu/snapshots/sign-basic/8.png deleted file mode 100644 index 1e430afe4949126c0cf7e2855169f446ec26c4f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 713 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfdKvUzxavyHU=5CL*{+Pq7ZDhh@)XvY_e5PC|VBXTi2|tT~?EdQpC;phI z$1O~pASq>b@%l5SqmE5rna@4=44gO06K&BM9sDjtJzZ6 zB&FUd8JqqIJFM_oRfY+ad5r9*@fev; ztekO1ZRUSpU|?>W^TQ`?@)`M)K+k-R26{F&C1XM2gq;e`%)I5<5fde)GH$2<*^_yU zOfDFlh?%gkvCZ*WX39FjfH;!?bn3^_;zna32^m$j=;e3 zm6~vKb8ERG(9Aixn^uY|#(nV; z;$J^8P^{j6dR*bs)p4GW5>4-%sNtOX-}jjxvk||08(T`;ZjVRP<6f=`pHvZH^gc_% zXz|0oGrr75=Dg)8buyAt6Mnuuq`mji%#%HADS0Vwhj*vAF&o+U@$jDbQ}nP^rEX2z zTUTbI{krFz*k%T2J3QNZbaUp7e@@Is^}9a!q+~2eJW=COe|USw4&~1iQvUTeY?^y5 z$$IP2vaIg6Yuol;`jE(La-m=e_iaOvx$Zj(mb57rUp4?*G+}4v4rS-V6C`<^(@c0~ zUIdCU8!cu8G34&HE;dmpUJm3moS9Gwlve=ikpe0`!86kwq)uMRSZYFLN_YCD54uu( z{+Aoh1d5$^Vw05GH-*Q@q-Hgn)H|S_B}X?;T=?+x$qOHHQ$U(!-X01}$&fHQaRKar zpCXb{ZD)Xd`AZ+xwJCm{59A55OnJLy@;OFHC9&Hpb+-ja=PciL!|$#9lKZPV-?q4~ PWB>wBS3j3^P6z;cxEDM(4%Or)2!tnliz1D_crj)E*V*!_L_^{y8xl)vu4O@O_qe@`A(R2|p*a z121h~19ntzY`^uvEr^haTeFl27!orSug%^x$=Vwk+{P zPn%-#n|il2e<4nRF73dT|sB#rVx z>g-}&(o7USpAfV6JiI-_!iU+&J@Su;(TNKmmZnTt_%Pb^LIE%|ly&Q*B&GJv;o-gS z#s>1@MLii;pq0%$yv}JRJTv_#E(E*evm)>NED59kD-%!1I6B<)%bvWSQ9?O)|RI`NzoVxTW`93in3_AnstP8JrI;KKAQB|Fvc#lf)f;Z;$FUR@{kIt$Xb# zbUW$_GrMfngd^Md7@uAc1GCx=^4o6Eb#=Ho&Gg1JGc>eg5}VYv{pkgwyvy6(zTfeY zS4!@L4=;bpGPabA87a%!CZD-taI$`ml-kUd5Btt6myU5f+}y@)q<_-pYQgfvNi#tb ziCG|(o@_uR^D`?}JzRcp>IsF<)7qv>y*r`cd^ni*|H_9~91h#7J0G6QmQuH?P?cBS z|5@^h3k^KHC;nu9cq!#)bYhNF+@wm+XPPsYr+|!{G&99+8c^ECcn8 literal 644 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfe(0x;TbZFupw)m?s@6;(YLM z+m8RNpPQ9qc;xO(6tM_Xk$$;O=>O}fDt^Zo#T71H9q0L|M26{9XmrU{ZO=JOFL%CeA$#lC%L`_H?b`~iw)9?Jm}|4Tv2AH%+eL@NFB{pU_FZlbpH#WB zP4V+%Q=XZw&vwUFENxTVZ1U#B1%ni~!xJQVozpTSq}q6RK>`^!CPqq4SeQ6blGoa3 zaiV+L@`nrC6rB%Gh?EkWxs*{7NT%oR2|hf*G0jB5SSrtiXQu2-SGJi;6W!S)fyVhU z8}Xl1*nC2v`Ai1LP@@wdPH{NM9&6qcHC~5fdF5j(rt$Ea=opzxO>1ihV*SaHQu8i> zTocP1f2|;#*{I&9jV)ywTZ-MT53(uumL;C(Y4e}FFflU%sHR`lnc2uh;q&U=@`*on zL6*BdyM038v!+qJTJiKV!6_LI&*t^+cTAgD>5*n~!QpUliW@U;`N}X7VBk6cLpo&| zP$qoW2R_~tf2N%Q3V{Or=EQ}Jpny!v1c!fQN*PeuQpTBc*;3wR0d+xCRZitzuVyUO lcILKejVH5__u>7|?5|%G_GmoX+y+ct44$rjF6*2UngDC22r2*o diff --git a/tests_zemu/snapshots/sign-expert/15.png b/tests_zemu/snapshots/sign-expert/15.png deleted file mode 100644 index 1e430afe4949126c0cf7e2855169f446ec26c4f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 713 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfdKvUzxavyHU=5CL*{+Pq7ZDhh@)XvY_e5PC|VBXTi2|tT~?EdQpC;phI z$1O~pASq>b@%l5SqmE5rna@4=44gO06K&BM9sDjtJzZ6 zB&FUd8JqqIJFM_oRfY+ad5r9*@fev; ztekO1ZRUSpU|?>W^TQ`?@)`M)K+k-R26{F&C1XM2gq;e`%)I5<5fde)GH$2<*^_yU zOfDFlh?%gkvCZ*WX39FjfH;!?bn3^_;zna32^m$j=;e3 zm6~vKb8ERG(9 Date: Tue, 16 Jun 2020 01:09:34 +0200 Subject: [PATCH 75/78] Feature/hdpath expert (#5) * hdpath (only expert mode) * limit hdpath in simple mode * adjusting code * adding missing files * bump version number * update zemu tests --- CMakeLists.txt | 3 ++- app/Makefile | 2 +- app/src/coin.h | 1 - app/src/common/app_main.c | 8 ++++++ app/src/tx_parser.c | 4 +-- .../ledger-zxlib/app}/common/app_mode.c | 4 +++ .../ledger-zxlib/app}/common/app_mode.h | 4 --- deps/ledger-zxlib/app/common/view.c | 21 ++++++++++----- deps/ledger-zxlib/app/common/view_s.c | 16 +++++++++--- deps/ledger-zxlib/include/zxversion.h | 2 +- deps/ledger-zxlib/src/bignum.c | 4 +-- tests/ui_output.cpp | 2 +- .../show-address-and-sign-basic/10.png | Bin 733 -> 499 bytes .../show-address-and-sign-basic/11.png | Bin 499 -> 400 bytes .../show-address-and-sign-basic/12.png | Bin 400 -> 443 bytes .../show-address-and-sign-basic/13.png | Bin 443 -> 0 bytes .../show-address-and-sign-basic/14.png | Bin 448 -> 0 bytes .../show-address-and-sign-basic/3.png | Bin 569 -> 303 bytes .../show-address-and-sign-basic/4.png | Bin 303 -> 555 bytes .../show-address-and-sign-basic/5.png | Bin 555 -> 582 bytes .../show-address-and-sign-basic/6.png | Bin 582 -> 1033 bytes .../show-address-and-sign-basic/7.png | Bin 1033 -> 748 bytes .../show-address-and-sign-basic/8.png | Bin 748 -> 1011 bytes .../show-address-and-sign-basic/9.png | Bin 1011 -> 733 bytes tests_zemu/snapshots/show-address-huge/0.png | Bin 830 -> 555 bytes tests_zemu/snapshots/show-address-huge/1.png | Bin 860 -> 686 bytes tests_zemu/snapshots/show-address-huge/2.png | Bin 781 -> 830 bytes tests_zemu/snapshots/show-address-huge/3.png | Bin 724 -> 860 bytes tests_zemu/snapshots/show-address-huge/4.png | Bin 766 -> 781 bytes tests_zemu/snapshots/show-address-huge/5.png | Bin 461 -> 724 bytes tests_zemu/snapshots/show-address-huge/6.png | Bin 303 -> 766 bytes tests_zemu/snapshots/show-address-huge/7.png | Bin 555 -> 461 bytes tests_zemu/snapshots/show-address-huge/8.png | Bin 0 -> 303 bytes .../5.png => show-address-huge/9.png} | Bin tests_zemu/snapshots/show-address/3.png | Bin 575 -> 303 bytes tests_zemu/snapshots/show-address/4.png | Bin 303 -> 555 bytes tests_zemu/tests/test.js | 24 ++++++++++++++++-- 37 files changed, 71 insertions(+), 24 deletions(-) rename {app/src => deps/ledger-zxlib/app}/common/app_mode.c (97%) rename {app/src => deps/ledger-zxlib/app}/common/app_mode.h (94%) delete mode 100644 tests_zemu/snapshots/show-address-and-sign-basic/13.png delete mode 100644 tests_zemu/snapshots/show-address-and-sign-basic/14.png create mode 100644 tests_zemu/snapshots/show-address-huge/8.png rename tests_zemu/snapshots/{show-address/5.png => show-address-huge/9.png} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 92c46bc1..d28d6388 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,7 @@ file(GLOB_RECURSE LIB_SRC app/src/tx_validate.c app/src/parser.c app/src/parser_impl.c - app/src/common/app_mode.c + deps/ledger-zxlib/app/common/app_mode.c ) add_library(app_lib STATIC @@ -53,6 +53,7 @@ target_include_directories(app_lib PUBLIC deps/jsmn/src app/src app/src/common + deps/ledger-zxlib/app/common ) ############################################################## diff --git a/app/Makefile b/app/Makefile index 07f89c85..18255b37 100755 --- a/app/Makefile +++ b/app/Makefile @@ -27,7 +27,7 @@ include $(BOLOS_SDK)/Makefile.defines # Main app configuration APPNAME = "Cosmos" APPVERSION_M=2 -APPVERSION_N=15 +APPVERSION_N=16 APPVERSION_P=0 APPPATH = "44'/118'" diff --git a/app/src/coin.h b/app/src/coin.h index 0526aab1..45cfa261 100644 --- a/app/src/coin.h +++ b/app/src/coin.h @@ -39,7 +39,6 @@ typedef enum { } address_kind_e; #define VIEW_ADDRESS_OFFSET_SECP256K1 PK_LEN_SECP256K1 -#define VIEW_ADDRESS_ITEM_COUNT 2 #define VIEW_ADDRESS_LAST_PAGE_DEFAULT 0 #define MENU_MAIN_APP_LINE1 "Cosmos" diff --git a/app/src/common/app_main.c b/app/src/common/app_main.c index dacd345e..e31ae9e7 100644 --- a/app/src/common/app_main.c +++ b/app/src/common/app_main.c @@ -104,6 +104,14 @@ void extractHDPath(uint32_t rx, uint32_t offset) { hdPath[3] != HDPATH_3_DEFAULT) { THROW(APDU_CODE_DATA_INVALID); } + + // Limit values unless the app is running in expert mode + if (!app_mode_expert()) { + for(int i=2; i < HDPATH_LEN_DEFAULT; i++) { + // hardened or unhardened values should be below 20 + if ( (hdPath[i] & 0x7FFFFFFF) > 100) THROW(APDU_CODE_CONDITIONS_NOT_SATISFIED); + } + } } bool process_chunk(volatile uint32_t *tx, uint32_t rx) { diff --git a/app/src/tx_parser.c b/app/src/tx_parser.c index edcdd6bf..519be032 100644 --- a/app/src/tx_parser.c +++ b/app/src/tx_parser.c @@ -23,7 +23,7 @@ // strcat but source does not need to be terminated (a chunk from a bigger string is concatenated) // dst_max is measured in bytes including the space for NULL termination // src_size does not include NULL termination -__always_inline void strcat_chunk_s(char *dst, uint16_t dst_max, const char *src_chunk, size_t src_chunk_size) { +__Z_INLINE void strcat_chunk_s(char *dst, uint16_t dst_max, const char *src_chunk, size_t src_chunk_size) { *(dst + dst_max - 1) = 0; // last character terminates with zero in case we go beyond bounds const size_t prev_size = strlen(dst); @@ -102,7 +102,7 @@ parser_error_t tx_getToken(uint16_t token_index, return parser_ok; } -__always_inline void append_key_item(int16_t token_index) { +__Z_INLINE void append_key_item(int16_t token_index) { if (*parser_tx_obj.query.out_key > 0) { // There is already something there, add separator strcat_chunk_s(parser_tx_obj.query.out_key, diff --git a/app/src/common/app_mode.c b/deps/ledger-zxlib/app/common/app_mode.c similarity index 97% rename from app/src/common/app_mode.c rename to deps/ledger-zxlib/app/common/app_mode.c index cb37b5ba..d7924f14 100644 --- a/app/src/common/app_mode.c +++ b/deps/ledger-zxlib/app/common/app_mode.c @@ -16,6 +16,10 @@ #include "app_mode.h" +typedef struct { + uint32_t expert; +} app_mode_t; + #if defined(TARGET_NANOS) || defined(TARGET_NANOX) ////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////// diff --git a/app/src/common/app_mode.h b/deps/ledger-zxlib/app/common/app_mode.h similarity index 94% rename from app/src/common/app_mode.h rename to deps/ledger-zxlib/app/common/app_mode.h index 311aef96..1142dd54 100644 --- a/app/src/common/app_mode.h +++ b/deps/ledger-zxlib/app/common/app_mode.h @@ -22,10 +22,6 @@ extern "C" { #endif -typedef struct { - uint32_t expert; -} app_mode_t; - void app_mode_reset(); bool app_mode_expert(); diff --git a/deps/ledger-zxlib/app/common/view.c b/deps/ledger-zxlib/app/common/view.c index c537f24c..258d2604 100644 --- a/deps/ledger-zxlib/app/common/view.c +++ b/deps/ledger-zxlib/app/common/view.c @@ -27,6 +27,7 @@ #include "zxmacros.h" #include "view_templates.h" #include "tx.h" +#include "app_mode.h" #include #include @@ -164,12 +165,15 @@ view_error_t h_review_update_data() { view_error_t h_addr_update_item(uint8_t idx) { MEMZERO(viewdata.value, MAX_CHARS_PER_VALUE1_LINE); - switch (idx) { - case 0: return view_printAddr(); - case 1: return view_printPath(); - default: - return view_error_detected; + if (idx == 0) { + return view_printAddr(); + } + + if (idx == 1 && app_mode_expert()) { + return view_printPath(); } + + return view_error_detected; } void io_seproxyhal_display(const bagl_element_t *element) { @@ -186,7 +190,12 @@ void view_idle_show(uint8_t item_idx) { void view_address_show(address_kind_e addressKind) { viewdata.addrKind = addressKind; - viewdata.itemCount = VIEW_ADDRESS_ITEM_COUNT; // Address, path, etc. + + viewdata.itemCount = 1; + if (app_mode_expert()) { + viewdata.itemCount = 2; + } + view_address_show_impl(); } diff --git a/deps/ledger-zxlib/app/common/view_s.c b/deps/ledger-zxlib/app/common/view_s.c index 2b642d0f..6e51367f 100644 --- a/deps/ledger-zxlib/app/common/view_s.c +++ b/deps/ledger-zxlib/app/common/view_s.c @@ -76,12 +76,17 @@ UX_STEP_NOCB_INIT(ux_addr_flow_2_step, paging, UX_STEP_VALID(ux_addr_flow_3_step, pb, h_address_accept(0), { &C_icon_validate_14, "Ok"}); UX_FLOW( - ux_addr_flow, + ux_addr_flow_no_path, &ux_addr_flow_1_step, - &ux_addr_flow_2_step, &ux_addr_flow_3_step ); +UX_FLOW( + ux_addr_flow_with_path, + &ux_addr_flow_1_step, + &ux_addr_flow_2_step, + &ux_addr_flow_3_step +); #endif void h_review(unsigned int _) { UNUSED(_); view_sign_show_impl(); } @@ -263,7 +268,12 @@ void view_address_show_impl() { if(G_ux.stack_count == 0) { ux_stack_push(); } - ux_flow_init(0, ux_addr_flow, NULL); + + if (app_mode_expert()) { + ux_flow_init(0, ux_addr_flow_with_path, NULL); + } else { + ux_flow_init(0, ux_addr_flow_no_path, NULL); + } #endif } diff --git a/deps/ledger-zxlib/include/zxversion.h b/deps/ledger-zxlib/include/zxversion.h index 5de79907..1926b003 100644 --- a/deps/ledger-zxlib/include/zxversion.h +++ b/deps/ledger-zxlib/include/zxversion.h @@ -16,5 +16,5 @@ #pragma once #define ZXLIB_MAJOR 2 -#define ZXLIB_MINOR 2 +#define ZXLIB_MINOR 3 #define ZXLIB_PATCH 0 diff --git a/deps/ledger-zxlib/src/bignum.c b/deps/ledger-zxlib/src/bignum.c index b4c3e483..59cb0459 100644 --- a/deps/ledger-zxlib/src/bignum.c +++ b/deps/ledger-zxlib/src/bignum.c @@ -68,7 +68,7 @@ void bignumLittleEndian_to_bcd(uint8_t *bcdOut, uint16_t bcdOutLen, // get bit const uint16_t byteIdx = bitIdx >> 3u; - const uint8_t mask = 0x80u >> (bitIdx & 0b111u); + const uint8_t mask = 0x80u >> (bitIdx & 0x7u); carry = (binValue[binValueLen - byteIdx - 1] & mask) > 0; // Shift bcd @@ -134,7 +134,7 @@ void bignumBigEndian_to_bcd(uint8_t *bcdOut, uint16_t bcdOutLen, // get bit const uint16_t byteIdx = bitIdx >> 3u; - const uint8_t mask = 0x80u >> (bitIdx & 0b111u); + const uint8_t mask = 0x80u >> (bitIdx & 0x7u); carry = (binValue[byteIdx] & mask) > 0; // Shift bcd diff --git a/tests/ui_output.cpp b/tests/ui_output.cpp index 131622c9..0425eb6e 100644 --- a/tests/ui_output.cpp +++ b/tests/ui_output.cpp @@ -21,7 +21,7 @@ #include #include "common/parser.h" #include "util/common.h" -#include "common/app_mode.h" +#include "app_mode.h" using ::testing::TestWithParam; using ::testing::Values; diff --git a/tests_zemu/snapshots/show-address-and-sign-basic/10.png b/tests_zemu/snapshots/show-address-and-sign-basic/10.png index ae789b14ff4b41f0bb47a722fdf6c41f6ba1502e..610edeb1a7024e87430acf5c9f0dcd587144c1e8 100644 GIT binary patch literal 499 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfi`&ba4!+V0?SfG4H7xgY&`T z$DaS?Kf~&tccjVm7iXHFbmy+sN8+m{u<)L+aXZ|5rbueS&$(3(7C7@ys+hu-vdx*< zD4tWJecOu*%_?$>8E0;n@-tfAHu=nA6NS&29~M4z=3ldjQ8GnOYJ%gli76RBzC3E~ zk(yAsq>X##%7<&)j%PVMyWH(>1XOKdlz(!M>Sqt$6E&tkyxQ8{HiE2b_>{tXV$aor zNtUK_Qs((M+?-~5W188GY2O|#H}Ns5U!6Efwr!MaQ_7|8;SFp105ng zGx)Gd@kN7^HOs~A7c$OV$vAU1TgoyFlqWS|CJa@sW|WkgV0qGq_e6||LhGxY}( zqHjIul*(A}u->t)EtuKJKJkN#(TP9VKs_lL6M29Ne~rAc}Vh+7q(l TbhQsKG8sHw{an^LB{Ts5^#SF` literal 733 zcmXYve@GKy7{_lejg)gml&}h=fXvDGTR@CQsUf-gR(oQU^*sz(H3l&YwmtOVcu(FkGk6-hRxJ@1n;P$ zTu?*SUKip_e9O|xE(Jpb*soNWt{o=Qp1^W zrc+CGcYRUnJf7k3ES#5n=E{e@&kUs|{QPpz`>?nE8@Fd`6BR#O7?~)1{&cXrM`}Xl zo;J_4tRJDwM*TqT%MYqcO3C>@W0y=3GdgkMg9Ok9(;r@KZI8JnrSjG@&V0`@WqCHx z>a2(pKD_xx{;CUPZcE8TOUp$6esE6eUWyp+8E0;n@-tfAHu=nA6NS&29~M4z=3ldjQ8GnOYJ%gli76RBzC3E~ zk(yAsq>X##%7<&)j%PVMyWH(>1XOKdlz(!M>Sqt$6E&tkyxQ8{HiE2b_>{tXV$aor zNtUK_Qs((M+?-~5W188GY2O|#H}Ns5U!6Efwr!MaQ_7|8;SFp105ng zGx)Gd@kN7^HOs~A7c$OV$vAU1TgoyFlqWS|CJa@sW|WkgV0qGq_e6||LhGxY}( zqHjIul*(A}u->t)EtuKJKJkN#(TP9VKs_lL6M29Ne~rAc}Vh+7q(l TbhQsKG8sHw{an^LB{Ts5^#SF` diff --git a/tests_zemu/snapshots/show-address-and-sign-basic/12.png b/tests_zemu/snapshots/show-address-and-sign-basic/12.png index 3bfe48e647c2265d038e08ea82adb81aaa730150..4aa28b6bfdc9668cf49edfe790ea290941e99f83 100644 GIT binary patch delta 417 zcmbQhyqkG~N_~^3i(^Oy4N4D`XKD{6YX0;vUx80!Y>Tq+K>5XY-XlTbIHmPm<(+fm-m$$urzvCmX zl-vm)UjCG2Y$+KtQkJz%K6Az3ysXI89wxcuPM6AGWFwN00LcS6DWa4_%xl@G5t9JW_?K0KE#rEXWDDzCi% zv*Z&O8hCh5{K@?AQp(Th#2l%(NtK?@G-obP0U0@IW{TZ3ptOzg4z8J#>(2!9>YMIy zO#|9=Ps!Nyg2UnC6DnODcJ6BPeCBvqWwWhRj<Anp_e9O|xE(Jpb*soNWt{o=Qp1^W zrc+CGcYRUnJf7k3ES#5n=E{e@&kUs|{QPpz`>?nE8@Fd`6BR#O7?~)1{&cXrM`}Xl zo;J_4tRJDwM*TqT%MYqcO3C>@W0y=3GdgkMg9Ok9(;r@KZI8JnrSjG@&V0`@WqCHx z>a2(pKD_xx{;CUPZcE8TOUp$6esE6eUWyp+2fe%lSYt`0Y+ znckRYhK6=bVw2joKfOSdcX`{}_d7oFO39t@;pIUI^X^2+-^OFnU-frt0RpUe+0rTmOe%#n(lRO$IlbLR3C zkdc#Srr1pbO4}Ik;F>x4Ofav$=^ocKphfqTj7={%96mmw($!(-t~SqSj)zq?+e+nl zOXqkC2W(5_-L@2t?*3roEl;_3so~5iw);+Ok|`M#-Dmz^`k?^21W{l Mr>mdKI;Vst03N8v5&!@I diff --git a/tests_zemu/snapshots/show-address-and-sign-basic/14.png b/tests_zemu/snapshots/show-address-and-sign-basic/14.png deleted file mode 100644 index a3a54e0001f8a5eb42df7d1a293a638209da292d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 448 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfg`zx;TbZFupw)*rj65;Bs*K zqR;=>&-IrmUOTjPbF(DRveipDK6>jtSzKjFiBLWdWHgm8i4KxA`qwYc7{FG`); z)i#^_S)Yb)P%}tBa;gaXT*){m5MKa zILY?^O2e669^QVX&&o#kC;d!eIwfrCV-$Q?Wi!}Qlh0f#PzEu5fF8Tdp<$ghBc;sD nVw>5GK%f$9bdSJ&1YyKKm!B1BpnB057&8o>u6{1-oD!M<(!R|_ diff --git a/tests_zemu/snapshots/show-address-and-sign-basic/3.png b/tests_zemu/snapshots/show-address-and-sign-basic/3.png index 637c1acf473228fb7d38b3aae12b69556c5a2ead..9794a2c11ed34beb917ada3db1306974c3a22cff 100644 GIT binary patch delta 277 zcmdnVvYu&zay`R0PZ!6K3dXkw9gCy`d7KZjhgJM%E@u2c%SHHfP!U7p-ADZLlOiIY zG&{HRd#;gTI+G|ZYp1f&NaX&H1Xsa?ur*1xpBvgmj{Z9BJS*w%vxm#nTqho2miu?| zu&?hVrOtM7xxB1PBAi$5*Z|=b6`_QniVAPvRgYx(R^xN-XX)c{{sUMc)I$ztaD0e0sswCc02$8 delta 545 zcmZ3_w3B6may=7+r;B4q1>@U;(Rp_`M4S&gAH4Iw>E}Tcy<<&Rv)CVhQ|?@%^>O|^ zk4Hs%uXxROz3f%dd#!+Sse&Ww+v43U2pv#095CC+r*Wn32?*m=zAN_-m zcX8s?4OtSrtJ_YWuYPC^cDGmBRfCj!s-G99*ttGCobs=wb${gH=|Beo&79lTp5n&L z>zuayq2kPo4u^rF{3mq6wWbzxetq^vbd)Q|7Lxcb;G&%V&e-2 OAnfH&2%^419^%)M&{0=J^o7|b`_)Iug zCibv1Z#SFNyh|Sf4linK^DlWE$*VuHQd?4L8C%Ldr?$40j5GZ=6_yzBpY$<0VdMKu zai%BRO#jly8YvkI5+_N5$O$unj`5$ku(8b%Y^45sX4ywd8k79?IQ_{qr2Z(6hUBiGE8i4!7E#MCwL09}!??^Z=S$c!C-3?!w1KKYmT zI2P!U(~c)DG@MbEey?C`%45`i!Qt@AlYb`nhMVX}vCUlhuqehnPI2>uNRygqW+M|G zBj>|TX&DhGbfn$^Il+e&iru8&Unp4qFq5~t&9Rj2HTQe(JEUDSIKjhvU(NaOQ-5i9)z9ls)OZ7pOJh1E2sF97t@X@> z0(X#)eRwCInaQb-g-D6L$n4MdeIALc)Ly_g$-qR~{WBlIo3z=V6evx^Ymxloe NJYD@<);T3K0RV%f>P!Ft delta 277 zcmZ3@vYu&zay`R0PZ!6K3dXkw9gCy`d7KZjhgJM%E@u2c%SHHfP!U7p-ADZLlOiIY zG&{HRd#;gTI+G|ZYp1f&NaX&H1Xsa?ur*1xpBvgmj{Z9BJS*w%vxm#nTqho2miu?| zu&?hVrOtM7xxB1PBAi$5*Z|=b6`_QniVAPvRgYx(R^xN-XX)c{{sUMc)I$ztaD0e0s#IUb}j$_ diff --git a/tests_zemu/snapshots/show-address-and-sign-basic/5.png b/tests_zemu/snapshots/show-address-and-sign-basic/5.png index 1f186b3ef0980434a970b1190ed43225fd808cf5..871ae85965ca1ef17e915e17f9c842259d6bb100 100644 GIT binary patch delta 558 zcmZ3@a*Snyay=8Tr;B4q1>@U;7xQjKF}Pf`)%$##qx<~4_MInk`yQ#8`08u0RO^29 z)>rxX)^v~J=1SfZfBg17a1!4;;Ye_XL)v#QW~23gXFUIDa^5)Ce^*BJtmiZH-0=_^FXlrvl?38xV;KUk}J*yKZEKGE5 zV@rV`yQM%r<4iD;lnP4`<83~(=wTe+9cN~vZnl(rsz6%X2*PP=6P@Xp26XC$1|D7r zQ3G^zcUvord!gZs6Hr0@L`k66rSe`L*qpUvW}9Q$(uea+Y?_$Q`=nj`u>XXP)V&Em z(}DWd8x?vRnJ5?o%{t=@at6qt8jrR%{*xe+fG(6f+vt8_!-Sv8l2ZSo4#%FnU~ru`HY#tw1D)a{YNyM1Th$h~zW@3LCiY_pBGw{+fJ=KID>JvV84CW_oQ`Hr8@BU&z2 Q>oEXmdKI;Vst028?YNdN!< delta 531 zcmX@cvYKUray{cGPZ!6K3dXkw1M?2MF*qM|Ke*?AfH&2%^419^%)M&{0=J^o7|b`_)Iug zCibv1Z#SFNyh|Sf4linK^DlWE$*VuHQd?4L8C%Ldr?$40j5GZ=6_yzBpY$<0VdMKu zai%BRO#jly8YvkI5+_N5$O$unj`5$ku(8b%Y^45sX4ywd8k79?IQ_{qr2Z(6hUBiGE8i4!7E#MCwL09}!??^Z=S$c!C-3?!w1KKYmT zI2P!U(~c)DG@MbEey?C`%45`i!Qt@AlYb`nhMVX}vCUlhuqehnPI2>uNRygqW+M|G zBj>|TX&DhGbfn$^Il+e&iru8&Unp4qFq5~t&9Rj2HTQe(JEUDSIKjhvU(NaOQ-5i9)z9ls)OZ7pOJh1E2sF97t@X@> z0(X#)eRwCInaQb-g-D6L$n4MdeIALc)Ly_g$-qR~{WBlIo3z=V6evx^Ymxloe NJYD@<);T3K0RXps>SX`` diff --git a/tests_zemu/snapshots/show-address-and-sign-basic/6.png b/tests_zemu/snapshots/show-address-and-sign-basic/6.png index 871ae85965ca1ef17e915e17f9c842259d6bb100..bcea849f4df186a3f2ceaab80fc8cd232e1b2afb 100644 GIT binary patch literal 1033 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 NJYD@<);T3K0RXEB>R$i= literal 830 zcmXAoe@GKy7{_O`t@O?;Bi$7_H4ADuJ8zPp=9*L2{9U5KD36YLo0f{AlA+VlfoV7` z%we;cWPzl@q7*cd>yI;rEX_(2!x;l3rThUCgnDkl-TS=o-ur#N&*#gvL@0&E_?tVpPjo5YVgK3XLy8)E#3EFZk0~)JPrYZ409tpF{w*LHNcgVp933#*489(f*T(sxjjO@ zSB*YR(;Mpi<+?eZMXPg`FPtluqKl|5My)q|<=U7*EFLFh{8Mfm&Co+@;LuOP2@LDo zxl9PE0{?nmyT;wgM3{Mc@P$wUWVkjF6$H-d3qS)v+DT3thgrrlJS=t0n#HEUJpz$X zX{?g9K!9LD7roULG7K|Usy%hW9Wz zQ4WJQnCI@fLLk=3sg^HPOy^hz-&c=^PD23WT2B@vN-U*{tgX;A(!og`oD6S8Al)n~ z`R6^YkG~S)V0a{BEeHx)&&jB*(-I8O(4#N?coQ+kB5KH(;@QBKBkhS3uSf`2<@r_` yh7a57?$GkYi5$>~$1mDUo7*Fh#p{rqL+jz}X7QUJ821bQ3xP(RojP=>xcMI<4PNp9 diff --git a/tests_zemu/snapshots/show-address-huge/1.png b/tests_zemu/snapshots/show-address-huge/1.png index bf520f458450d66dbd6de46a25a570c49ca95dd6..6ee7de3235d937a2ad3db368f9413cf54c81b973 100644 GIT binary patch delta 663 zcmcb^wvKgzay?V2r;B4q1>@U;fq9Rj7+els7Tf!u^}D-$$=4FmeqKo?PlmJ%0rSmE<*KO53Jkzr3XQYHl>4D|gX1YgLnDLtM z80}68OEHsTlT2xATbVd%X3B({R~yb;XgK3`Si#uDhqoETXg+h{!(EU~qLNY>5-IDl zA~JV)JX7ayUqEzQTe#ixQ`w@nSY| zKCJTD0O*E?*fz#6Tiz2F3Z|a9VsK*3i5h9i z6h7;ejEGE!XNz0yO%;q!T==lE%`q*@;*U|iq*NPQ%D*ii0-24pjZA91fTA{>z<|2B z`NPCBdNWt2ENgRoc3LW9hE&E5Z)V=zKq``%*Wdryq}FPaHNe0MK72Q2f~C#9&T61t zYfSDeNel-C(;A>}wT+~CC!cWz(!qyUM^v=5^|s9h>Ztc+o9PJ-UEZ1X{-!)eK&8&W zh~x6Rvp6y9$EG&@ObKAn&o)ume96Fcjme%_!vB>drIrEXD`Us(GhT;RM^>;InNN0n zre`E=Bz_VozkSkAF(VU@w?tB-k zl7s!2h8o_%NTpfGF*wE%b6Kc~u^>xK+wzABLcJ&ZalYr{{oeC_&-*;@*O;1e1#`pQ z1Ofq;m=Ko+SOcHzlnB-}|B^s~K&VKJ)7~s|*_pkOb}P{hvMPHGg84r zaa(x?^X2EF1RO~X*x20b&QcAf#Sm1Ho< z|B+gSgl-_!N3w+my%1w7J0&?iwdfV46-f<<>xVdAWOIx~KMcW|#6*&i$egz}aoq+g ziqdXQvcBxmf@?>G$%p7Z=9a|hbD?N54^WcP^R$|gPzp`;^Not>3Xjyg`I=389g^?a zA>2@)+3b^qgvs_w9&-b2hhBur8emaF)P4?@)kY)6sw+zgmHMLd8bS@lqb{^jSrw>b zn23)x+?Z=e7fwwNlI(gs==unhXoagcmdAm6IPuF?!zC$$&C`Q+yNFhwTE-7ojEqutmz}i0P$qGZ<8w0S)CvQlL1bQHKw(lf-1C(+>j$ zn$Z&fCrC!OaQ>lC33}(HhcKl*wsGnz=&5IFxp)i?bW zY<<|?iz4QmML1QS4y`CK*7|L31P9X`awt1c0Nz@mQQ6)WzPmFw!k0Jm-cnMS2H2N( za{l$e!Em{g8!#Wj8m37c=0aMubFe5!A zjE;^!rR&kYXX@)%m>ak?W&o7E!%*d868D%jNlrYZgcI)j3XA$EOX*kjf)7`3D~rd^ TevbYPuSSpyI;rEX_(2!x;l3rThUCgnDkl-TS=o-ur#N&*#gvL@0&E_?tVpPjo5YVgK3XLy8)E#3EFZk0~)JPrYZ409tpF{w*LHNcgVp933#*489(f*T(sxjjO@ zSB*YR(;Mpi<+?eZMXPg`FPtluqKl|5My)q|<=U7*EFLFh{8Mfm&Co+@;LuOP2@LDo zxl9PE0{?nmyT;wgM3{Mc@P$wUWVkjF6$H-d3qS)v+DT3thgrrlJS=t0n#HEUJpz$X zX{?g9K!9LD7roULG7K|Usy%hW9Wz zQ4WJQnCI@fLLk=3sg^HPOy^hz-&c=^PD23WT2B@vN-U*{tgX;A(!og`oD6S8Al)n~ z`R6^YkG~S)V0a{BEeHx)&&jB*(-I8O(4#N?coQ+kB5KH(;@QBKBkhS3uSf`2<@r_` yh7a57?$GkYi5$>~$1mDUo7*Fh#p{rqL+jz}X7QUJ821bQ3xP(RojP=>xcMI<4PNp9 literal 781 zcmX|*jXzL1)dN{O@Kl}+(h zY8-@X#vBNWF!QBMkTW>r!1;nnM6~`GNtB~WriAs(Klto7JY8I}>t{+5}$0;7g_`2dw zq)v5JJ(s-=e?u;A-7QX@@o1Cd8}~0A?YB;S9gci_u|1rhwROx@uT{2965_xpA#QRS zn0pU-xQ^EO@@Udwkh^kyzH>np{KXHmq)zUku-%!O_R|` z47D<03zm)zKNXvyrw0fSEU%8j(iC+lA;ex7q!#v6G(IjDQUzQ@?VCnTQJ9rAwf0;3 zL>4wERxhy#k$f(QdK#RaMA{P=nS`i~&}sUe!;f=x5)VifFB&J<5d%|)&cCu`SC`^s z8A`SduyMA%3!u-4n$j>kW&SCE&b5?aAY=6=(?f{ehozS_Z5|o}M=~_11WAQdc0^G{ zjndZ)X=YyojuwQOYxe88n5%@DJ5RNU)pUNyJ2ww6tSseX%V9$bQPchx+|+u={YGh*;Um+9<4LB?Uhy<6_B~jaJqLb3JAj+#lr7 zi8Cl+$3Fby;>L zZbl?SgLs|ZydYq^qKAi?W>(Ft&S+B^*Zk+GULnRLVKLP1ZGQwhNR%vz{x@Yw0P+m1 z(SHMaZDF7CX)?XNg1@chQN1i2N&+{75Ci#eyfW0943V;GH(?6|TYxa03C5$OC4mG? z4pwi~D1kZb&dN#!iM!x;5RmRW;EGLJ)~0_gW-h_sI^RzE@kCesx6eCcvCrmT{^eu{ V)Z@L_@F-k!POi34Gk&@1{y#S>Qa%6x diff --git a/tests_zemu/snapshots/show-address-huge/3.png b/tests_zemu/snapshots/show-address-huge/3.png index f345cffa1fe6b817bf7280478954b4be530700f6..bf520f458450d66dbd6de46a25a570c49ca95dd6 100644 GIT binary patch literal 860 zcmXX_Ye-XJ7@kd5*bbsIb0?9_DeH%59Ot!_o71J4W}2vUu|vbnOKR8;Me$0Xh>B-k zl7s!2h8o_%NTpfGF*wE%b6Kc~u^>xK+wzABLcJ&ZalYr{{oeC_&-*;@*O;1e1#`pQ z1Ofq;m=Ko+SOcHzlnB-}|B^s~K&VKJ)7~s|*_pkOb}P{hvMPHGg84r zaa(x?^X2EF1RO~X*x20b&QcAf#Sm1Ho< z|B+gSgl-_!N3w+my%1w7J0&?iwdfV46-f<<>xVdAWOIx~KMcW|#6*&i$egz}aoq+g ziqdXQvcBxmf@?>G$%p7Z=9a|hbD?N54^WcP^R$|gPzp`;^Not>3Xjyg`I=389g^?a zA>2@)+3b^qgvs_w9&-b2hhBur8emaF)P4?@)kY)6sw+zgmHMLd8bS@lqb{^jSrw>b zn23)x+?Z=e7fwwNlI(gs==unhXoagcmdAm6IPuF?!zC$$&C`Q+yNFhwTE-7ojEqutmz}i0P$qGZ<8w0S)CvQlL1bQHKw(lf-1C(+>j$ zn$Z&fCrC!OaQ>lC33}(HhcKl*wsGnz=&5IFxp)i?bW zY<<|?iz4QmML1QS4y`CK*7|L31P9X`awt1c0Nz@mQQ6)WzPmFw!k0Jm-cnMS2H2N( za{l$e!Em{g8!#Wj8m37c=0aMubFe5!A zjE;^!rR&kYXX@)%m>ak?W&o7E!%*d868D%jNlrYZgcI)j3XA$EOX*kjf)7`3D~rd^ TevbYPuSSp$q!x*9(9Nb#k}T{1|iTg5nYZkyuf%LXYKHzr0(O{jE}KJWKzy2*tP6WW^F{7>>o zrc99JwdOtlqIZAb;l48&5mIeuE^RpRC*-g%@BM?v?t8FFrnnuR5ScRJ=ER*V6Hn+E zow&2M5oqLzp0@PQ$2MnMWc*kR)C1JIfN^H}_hXwU-^`RSTKw?Kj^4}3Df^mx_xA#& z9y+9%TqrP~dGW)_w$)M@3xEdkIzPL~p&@Uo@VN-6e|naLQN0hdk$i5=m4-9klYZVm z@h8y8q()`Io@|F_UwVPc@*c-B8!gZGYilc)exH(IQCrM=V$ba#6M1--=L7vI&j~WD zczc^-aVXHiAb%L7OoMoSTAS?5rC>YLOmvLcfmVZEzz$-jEl+e$%e*o1X9&pl7-gUd z{-$?SiWN4S#9S@7U;qvvBX*<3i9j#UZJRB%ZjvM0OdthxF;K)696-xc)>S@^GfYZQf`MYZ7}I+Tl#RN z>7Pk$7dM#PS(<2~!@K{sbpIrf$CWml@XVZ@Ql||h-hny0!QowA{n$VIhmeuUg$*ZU zfKJ^2RQ7*O;)y>(l2REFz`&B7>CU0CeEQbHs!ZwS)3+h0{|q`m4bAMf8E^tqD}$%2 KpUXO@geCy}-$HNz diff --git a/tests_zemu/snapshots/show-address-huge/4.png b/tests_zemu/snapshots/show-address-huge/4.png index 159079fd581378f28e79eed49c7e6b8116a52361..305185c080cdf860632125e5e0beefc9d7727b08 100644 GIT binary patch literal 781 zcmX|*jXzL1)dN{O@Kl}+(h zY8-@X#vBNWF!QBMkTW>r!1;nnM6~`GNtB~WriAs(Klto7JY8I}>t{+5}$0;7g_`2dw zq)v5JJ(s-=e?u;A-7QX@@o1Cd8}~0A?YB;S9gci_u|1rhwROx@uT{2965_xpA#QRS zn0pU-xQ^EO@@Udwkh^kyzH>np{KXHmq)zUku-%!O_R|` z47D<03zm)zKNXvyrw0fSEU%8j(iC+lA;ex7q!#v6G(IjDQUzQ@?VCnTQJ9rAwf0;3 zL>4wERxhy#k$f(QdK#RaMA{P=nS`i~&}sUe!;f=x5)VifFB&J<5d%|)&cCu`SC`^s z8A`SduyMA%3!u-4n$j>kW&SCE&b5?aAY=6=(?f{ehozS_Z5|o}M=~_11WAQdc0^G{ zjndZ)X=YyojuwQOYxe88n5%@DJ5RNU)pUNyJ2ww6tSseX%V9$bQPchx+|+u={YGh*;Um+9<4LB?Uhy<6_B~jaJqLb3JAj+#lr7 zi8Cl+$3Fby;>L zZbl?SgLs|ZydYq^qKAi?W>(Ft&S+B^*Zk+GULnRLVKLP1ZGQwhNR%vz{x@Yw0P+m1 z(SHMaZDF7CX)?XNg1@chQN1i2N&+{75Ci#eyfW0943V;GH(?6|TYxa03C5$OC4mG? z4pwi~D1kZb&dN#!iM!x;5RmRW;EGLJ)~0_gW-h_sI^RzE@kCesx6eCcvCrmT{^eu{ V)Z@L_@F-k!POi34Gk&@1{y#S>Qa%6x literal 766 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW(PFfbkUba4!+V0?QpFi1I4#QET2 zt}XvrUpqhEVYuhTqOQ!@3p1PNYW};*?P+5x_O*#S_KTB{f4{TC&WvuoD#gy_$2WZJ zj{EQ>fMrVcW17hulRJyW_swpToq2JC3D3->i4%Y7&isGngK$obxzPy)W1w24<}-a~b^{sz zFF72Z5Sik3cyHyyd8RTZH7nZ`Hv=^R^*S&ceSgW9m@Hh<@xX*}b< z;p5yBJ#12ZDQ?WX;gcnKy$=IT$*{QUz|8BMmMLNM-OZtLHRH_oO9m-+v4R>M8{}3(2su1M)|pOCR$!KNSR=HwZMx_GR5zt3{byfGdR#dVRs|5BK)wXWXgof z@Wa3$07^ta!;%N6+H{ZivoqY=C;faZm3M7}^5=DJ`JT@<=fPp||kn!haZM9kVm zP^^@tOpxSdHv+or{DzN_Qf`MYeF!(bvn0_(hc`S^0vO2CGeD$i+z%ZiZ(yhj%$$7^ z=q@b~XJ^+LU|{cS0g>xI9@?6cu^@5cPEVj6K>xO}0fXpb!Ky@H)clVID(>Xr1qO{b zFcm2|GxK(zNf!v1CwJ}GW#`)!RT$q!x*9(9Nb#k}T{1|iTg5nYZkyuf%LXYKHzr0(O{jE}KJWKzy2*tP6WW^F{7>>o zrc99JwdOtlqIZAb;l48&5mIeuE^RpRC*-g%@BM?v?t8FFrnnuR5ScRJ=ER*V6Hn+E zow&2M5oqLzp0@PQ$2MnMWc*kR)C1JIfN^H}_hXwU-^`RSTKw?Kj^4}3Df^mx_xA#& z9y+9%TqrP~dGW)_w$)M@3xEdkIzPL~p&@Uo@VN-6e|naLQN0hdk$i5=m4-9klYZVm z@h8y8q()`Io@|F_UwVPc@*c-B8!gZGYilc)exH(IQCrM=V$ba#6M1--=L7vI&j~WD zczc^-aVXHiAb%L7OoMoSTAS?5rC>YLOmvLcfmVZEzz$-jEl+e$%e*o1X9&pl7-gUd z{-$?SiWN4S#9S@7U;qvvBX*<3i9j#UZJRB%ZjvM0OdthxF;K)696-xc)>S@^GfYZQf`MYZ7}I+Tl#RN z>7Pk$7dM#PS(<2~!@K{sbpIrf$CWml@XVZ@Ql||h-hny0!QowA{n$VIhmeuUg$*ZU zfKJ^2RQ7*O;)y>(l2REFz`&B7>CU0CeEQbHs!ZwS)3+h0{|q`m4bAMf8E^tqD}$%2 KpUXO@geCy}-$HNz delta 436 zcmcb@dX{;Day{b|PZ!6K3dXkw1M{Q}8JrKgEAIK<`17D%%dNAwG%u;~zKWP7X#Z8A zGgtRjV>6$vM^bcqLs3RIn05HURJj||lor^`YO^+yzhrQ7&uYe*D<8T%^E<5Ke7N&W zD6>(2#)lIr|Gb!a8goQyGzIaw2R z*jS>*+~|aYv8j(yxY3C@C;o&TRw=&Za9HK@IyNb_nc6_}FY?Gm9-hl4mGPs<=!6e% zIPc^$ogkCTKO9n?Y?(PDWm((XVz%4IPuMhz?-Q1k%BYyk!)xMWRPWNpCIzw?sKDHG z&WRXPpL(NyH;0{98_om)&8}aUIO%7;)P$X;JAfiZDf_0JsaJJAtjqXx5#!9aou&%K zhC4DG(yEJ$Ok#NSQZjDLi(xhrZ@vyfMrVcW17hulRJyW_swpToq2JC3D3->i4%Y7&isGngK$obxzPy)W1w24<}-a~b^{sz zFF72Z5Sik3cyHyyd8RTZH7nZ`Hv=^R^*S&ceSgW9m@Hh<@xX*}b< z;p5yBJ#12ZDQ?WX;gcnKy$=IT$*{QUz|8BMmMLNM-OZtLHRH_oO9m-+v4R>M8{}3(2su1M)|pOCR$!KNSR=HwZMx_GR5zt3{byfGdR#dVRs|5BK)wXWXgof z@Wa3$07^ta!;%N6+H{ZivoqY=C;faZm3M7}^5=DJ`JT@<=fPp||kn!haZM9kVm zP^^@tOpxSdHv+or{DzN_Qf`MYeF!(bvn0_(hc`S^0vO2CGeD$i+z%ZiZ(yhj%$$7^ z=q@b~XJ^+LU|{cS0g>xI9@?6cu^@5cPEVj6K>xO}0fXpb!Ky@H)clVID(>Xr1qO{b zFcm2|GxK(zNf!v1CwJ}GW#`)!RTR^xN-XX)c{{sUMc)I$ztaD0e0svVLc7gx^ diff --git a/tests_zemu/snapshots/show-address-huge/7.png b/tests_zemu/snapshots/show-address-huge/7.png index 1f186b3ef0980434a970b1190ed43225fd808cf5..b685970b8888746406533e3b5f21eef416a704be 100644 GIT binary patch delta 435 zcmZ3@a+Z05O8pd17srqa#oNYQ~u>AG$pAJFMb-xbsXX zvr&J>hZ8CPyqJ0AFBK>?^B4smo}Hp5nKEIfl%JH`Meh6czCd+X3a%QQj4_QlSrc{G zSfa+<=!Al?sgF^((TO=H{)8P?DZb=zSmpCNHYv54+CcL!^2kLVp35ec@uSG-gb!~x z@8mO`Ad|~K98#WanK>h6S=-xUw%f-~*ffjp6PA?9sF=*dYvN;6@6yI51+p2az}$4s zi5Q?A_5E%RJFhmJ2?Cm3zbU>z2@#!MQnQuEy6^ada zWH_W%7a5tv@aUyv+?W@`Y$V=%9mEP@Hd@cAv0Z9Hr5A5G(0tElFHIDRy&WP$533lP zDr}x~aNCxH-@4Cav8~RUkrHNhV;TrW1nL}C*=)s_8htGx`W7Rake|ZKH|uze{sf z^qwzzcO&yUh@rUP&8IIebj~ZlR?p<5LYOF{A1Kuk|j@kVw({KVN#k>tPjR zQ=ODHwiKX9#*CC@ZJuda5hrS9_1<5QI6+eC-3f)w84hU~^$ySc4l5X&+?nY3OgL92 z_OLT=H=ESFOCJIbFKTS_FL@lvt3R<)TT*HnTgpABwzidwGyOLemKgD$^f5YN|I)`ADH#hACrN_H2{VC?@t?S`vCR=|r4-{+NnZYwI#NJu)n=Y9%-Pd?Mt0`u zkGXri(z5CoBwj4|$;iuZTC?>d*UXiP6CzK<)HUz`U6HcyRz*9=j2(XrB&C2p`Iq-N z7U+@FjwdcOoKcp3uV8G-W7K}Z;qc0nej2HTQe(JEUDSIKjhvU(NaOQ-5i9)z9ls)OZ7pOJh1E2sF97t@X@> z0(X#)eRwCInaQb-g-D6L$n4MdeIALc)Ly_g$-qR~{WBlIo3z=V6evx^Ymw}ss Ofx*+&&t;ucLK6VMg6e7j diff --git a/tests_zemu/snapshots/show-address-huge/8.png b/tests_zemu/snapshots/show-address-huge/8.png new file mode 100644 index 0000000000000000000000000000000000000000..9794a2c11ed34beb917ada3db1306974c3a22cff GIT binary patch literal 303 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3HF6DHW&!sc)Vxjv*C{Zx1>aNeA*cA7l@! z_|II-_L_TSDZs+%0Bg1qiQCikcWuuYE{T~Ugf(c=3l59UW zw2K`5b=rAW(%)wfm#MiR^xN-XX)c{{sUMc)I$ztaD0e0ss@xc0vFE delta 551 zcmZ3_w4Y^yay=89r;B4q1>@U;fqAzhL|hJDZmIsy{jR^Sz_89h_oj!8_RI-ug#N$m zRndFC=w89%+4npi70EH3@{KOJEbSR{F7a{iy#h;7#-|=q?@rWs9-d&is(10EpQ^kj zbIx+7U)rE-EH$l7akGhzk^81XHKP*>#!_u(l$y`$&NjV218?;We=JrTp_*4Fmj>|U0`v!VbalRaBM zt^!IQJbB?m*5j!>ywjVr9iCl17Hwn_!v~bu&NAh@)P$dAyw1-Sg51C}b7|rU9bV_O zxUR`{G|`4rT$Ip zO$WNynfHW^%4bbUpdN){_cW6W1}S1Q=ht6yIIQ$pgcsgb~oxc1SQS z2U7k(5o?fG`pt@IZ5KC~==|bJ+z5}?)x69A1fH&bF6*2UngHA~{Dc4i diff --git a/tests_zemu/snapshots/show-address/4.png b/tests_zemu/snapshots/show-address/4.png index 9794a2c11ed34beb917ada3db1306974c3a22cff..1f186b3ef0980434a970b1190ed43225fd808cf5 100644 GIT binary patch delta 531 zcmZ3_w3=mtay{cGPZ!6K3dXkw1M?2MF*qM|Ke*?AfH&2%^419^%)M&{0=J^o7|b`_)Iug zCibv1Z#SFNyh|Sf4linK^DlWE$*VuHQd?4L8C%Ldr?$40j5GZ=6_yzBpY$<0VdMKu zai%BRO#jly8YvkI5+_N5$O$unj`5$ku(8b%Y^45sX4ywd8k79?IQ_{qr2Z(6hUBiGE8i4!7E#MCwL09}!??^Z=S$c!C-3?!w1KKYmT zI2P!U(~c)DG@MbEey?C`%45`i!Qt@AlYb`nhMVX}vCUlhuqehnPI2>uNRygqW+M|G zBj>|TX&DhGbfn$^Il+e&iru8&Unp4qFq5~t&9Rj2HTQe(JEUDSIKjhvU(NaOQ-5i9)z9ls)OZ7pOJh1E2sF97t@X@> z0(X#)eRwCInaQb-g-D6L$n4MdeIALc)Ly_g$-qR~{WBlIo3z=V6evx^Ymxloe NJYD@<);T3K0RV%f>P!Ft delta 277 zcmZ3@vYu&zay`R0PZ!6K3dXkw9gCy`d7KZjhgJM%E@u2c%SHHfP!U7p-ADZLlOiIY zG&{HRd#;gTI+G|ZYp1f&NaX&H1Xsa?ur*1xpBvgmj{Z9BJS*w%vxm#nTqho2miu?| zu&?hVrOtM7xxB1PBAi$5*Z|=b6`_QniVAPvRgYx(R^xN-XX)c{{sUMc)I$ztaD0e0s#IUb}j$_ diff --git a/tests_zemu/tests/test.js b/tests_zemu/tests/test.js index 8f9cdabf..4899e125 100644 --- a/tests_zemu/tests/test.js +++ b/tests_zemu/tests/test.js @@ -236,7 +236,6 @@ describe('Basic checks', function () { await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); - await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); await sim.clickBoth(`${snapshotPrefixTmp}${snapshotCount++}.png`); const resp = await respRequest; @@ -258,6 +257,24 @@ describe('Basic checks', function () { }); it('show address - HUGE', async function () { + const sim = new Zemu(APP_PATH); + try { + await sim.start(sim_options); + const app = new CosmosApp(sim.getTransport()); + + // Derivation path. First 3 items are automatically hardened! + const path = [44, 118, 2147483647, 0, 4294967295]; + const resp = await app.showAddressAndPubKey(path, "cosmos"); + console.log(resp); + + expect(resp.return_code).toEqual(0x6985); + expect(resp.error_message).toEqual("Conditions not satisfied"); + } finally { + await sim.close(); + } + }); + + it('show address - HUGE - expert', async function () { const snapshotPrefixGolden = "snapshots/show-address-huge/"; const snapshotPrefixTmp = "snapshots-tmp/show-address-huge/"; let snapshotCount = 0; @@ -267,6 +284,10 @@ describe('Basic checks', function () { await sim.start(sim_options); const app = new CosmosApp(sim.getTransport()); + // Activate expert mode + await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); + await sim.clickBoth(`${snapshotPrefixTmp}${snapshotCount++}.png`); + // Derivation path. First 3 items are automatically hardened! const path = [44, 118, 2147483647, 0, 4294967295]; const respRequest = app.showAddressAndPubKey(path, "cosmos"); @@ -438,7 +459,6 @@ describe('Basic checks', function () { await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); - await sim.clickRight(`${snapshotPrefixTmp}${snapshotCount++}.png`); await sim.clickBoth(`${snapshotPrefixTmp}${snapshotCount++}.png`); const respPk = await respRequest; From 2755e74ac1accf7e642ae6e069396b54d1e4071e Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 16 Jun 2020 11:58:02 +0200 Subject: [PATCH 76/78] do not show gas in simple mode (#6) --- app/src/tx_display.c | 3 ++ tests/testcases/manual.json | 27 ++++++------------ .../show-address-and-sign-basic/11.png | Bin 400 -> 443 bytes .../snapshots/sign-basic-combined/8.png | Bin 400 -> 443 bytes .../snapshots/sign-basic-combined/9.png | Bin 443 -> 0 bytes tests_zemu/snapshots/sign-basic/6.png | Bin 400 -> 443 bytes tests_zemu/tests/test.js | 6 ++-- 7 files changed, 15 insertions(+), 21 deletions(-) delete mode 100644 tests_zemu/snapshots/sign-basic-combined/9.png diff --git a/app/src/tx_display.c b/app/src/tx_display.c index 04e448e4..9dc12fba 100644 --- a/app/src/tx_display.c +++ b/app/src/tx_display.c @@ -308,6 +308,9 @@ __Z_INLINE uint8_t get_subitem_count(root_item_e root_item) { case root_item_memo: break; case root_item_fee: + if (!tx_is_expert_mode()) { + tmp_num_items -= 1; // Hide Gas field + } default: break; } diff --git a/tests/testcases/manual.json b/tests/testcases/manual.json index 872887a9..32d27a06 100644 --- a/tests/testcases/manual.json +++ b/tests/testcases/manual.json @@ -195,8 +195,7 @@ "2 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", "3 | Dest Coins : 10 atom", "4 | Memo : testmemo", - "5 | Fee : 5 photon", - "6 | Gas : 10000" + "5 | Fee : 5 photon" ], "expert": false }, @@ -306,8 +305,7 @@ "1 | Source Coins : 10 atom", "2 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", "3 | Dest Coins : 10 atom", - "4 | Fee : 5 photon", - "5 | Gas : 10000" + "4 | Fee : 5 photon" ], "expert": false }, @@ -419,8 +417,7 @@ "1 | Source Coins : 10 atom", "2 | Dest Address : cosmosaccaddr1da6hgur4wse3jx32", "3 | Dest Coins : 10 atom", - "4 | Fee : 5 photon", - "5 | Gas : 10000" + "4 | Fee : 5 photon" ], "expert": false }, @@ -590,8 +587,7 @@ "6 | Dest Address : test3", "7 | Dest Coins : 50 ripple", "8 | Memo : testmemo", - "9 | Fee : 5 photon", - "10 | Gas : 10000" + "9 | Fee : 5 photon" ], "expert": false }, @@ -731,8 +727,7 @@ "3 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", "3 | Validator [2/2] : 9m5s03xfytvz7", "4 | Memo : Zondax.ch", - "5 | Fee : 0.005000 ATOM", - "6 | Gas : 200000" + "5 | Fee : 0.005000 ATOM" ], "expert": false }, @@ -818,8 +813,7 @@ "4 | msgs/proposer [2/2] : vwxyz0", "5 | msgs/title : test", "6 | Memo : abc", - "7 | Fee : Empty", - "8 | Gas : 500000" + "7 | Fee : Empty" ], "expert": false }, @@ -911,8 +905,7 @@ "3 | Validator [1/2] : cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt", "3 | Validator [2/2] : 9m5s03xfytvz7", "4 | Memo : Delegated with Ledger from union.market", - "5 | Fee : 0.005000 ATOM", - "6 | Gas : 200000" + "5 | Fee : 0.005000 ATOM" ], "expert": false }, @@ -1111,8 +1104,7 @@ "2 | Validator [2/2] : vjhennyh568ys", "3 | Validator [1/2] : cosmosvaloper1x88j7vp2xnw3zec8ur3g4waxy", "3 | Validator [2/2] : cyz7m0mahdv3p", - "4 | Fee : 0.000600 ATOM", - "5 | Gas : 200000" + "4 | Fee : 0.000600 ATOM" ], "expert": false }, @@ -1522,8 +1514,7 @@ "4 | Amount : 20.139397 ATOM", "5 | Validator [1/2] : cosmosvaloper1648ynlpdw7fqa2axt0w2yp3fk", "5 | Validator [2/2] : 542junl7rsvq6", - "6 | Fee : 0.000600 ATOM", - "7 | Gas : 200000" + "6 | Fee : 0.000600 ATOM" ], "expert": false } diff --git a/tests_zemu/snapshots/show-address-and-sign-basic/11.png b/tests_zemu/snapshots/show-address-and-sign-basic/11.png index 3bfe48e647c2265d038e08ea82adb81aaa730150..4aa28b6bfdc9668cf49edfe790ea290941e99f83 100644 GIT binary patch delta 417 zcmbQhyqkG~N_~^3i(^Oy4N4D`XKD{6YX0;vUx80!Y>Tq+K>5XY-XlTbIHmPm<(+fm-m$$urzvCmX zl-vm)UjCG2Y$+KtQkJz%K6Az3ysXI89wxcuPM6AGWFwN00LcS6DWa4_%xl@G5t9JW_?K0KE#rEXWDDzCi% zv*Z&O8hCh5{K@?AQp(Th#2l%(NtK?@G-obP0U0@IW{TZ3ptOzg4z8J#>(2!9>YMIy zO#|9=Ps!Nyg2UnC6DnODcJ6BPeCBvqWwWhRj<Anp_e9O|xE(Jpb*soNWt{o=Qp1^W zrc+CGcYRUnJf7k3ES#5n=E{e@&kUs|{QPpz`>?nE8@Fd`6BR#O7?~)1{&cXrM`}Xl zo;J_4tRJDwM*TqT%MYqcO3C>@W0y=3GdgkMg9Ok9(;r@KZI8JnrSjG@&V0`@WqCHx z>a2(pKD_xx{;CUPZcE8TOUp$6esE6eUWyp+4N4D`XKD{6YX0;vUx80!Y>Tq+K>5XY-XlTbIHmPm<(+fm-m$$urzvCmX zl-vm)UjCG2Y$+KtQkJz%K6Az3ysXI89wxcuPM6AGWFwN00LcS6DWa4_%xl@G5t9JW_?K0KE#rEXWDDzCi% zv*Z&O8hCh5{K@?AQp(Th#2l%(NtK?@G-obP0U0@IW{TZ3ptOzg4z8J#>(2!9>YMIy zO#|9=Ps!Nyg2UnC6DnODcJ6BPeCBvqWwWhRj<Anp_e9O|xE(Jpb*soNWt{o=Qp1^W zrc+CGcYRUnJf7k3ES#5n=E{e@&kUs|{QPpz`>?nE8@Fd`6BR#O7?~)1{&cXrM`}Xl zo;J_4tRJDwM*TqT%MYqcO3C>@W0y=3GdgkMg9Ok9(;r@KZI8JnrSjG@&V0`@WqCHx z>a2(pKD_xx{;CUPZcE8TOUp$6esE6eUWyp+2fe%lSYt`0Y+ znckRYhK6=bVw2joKfOSdcX`{}_d7oFO39t@;pIUI^X^2+-^OFnU-frt0RpUe+0rTmOe%#n(lRO$IlbLR3C zkdc#Srr1pbO4}Ik;F>x4Ofav$=^ocKphfqTj7={%96mmw($!(-t~SqSj)zq?+e+nl zOXqkC2W(5_-L@2t?*3roEl;_3so~5iw);+Ok|`M#-Dmz^`k?^21W{l Mr>mdKI;Vst03N8v5&!@I diff --git a/tests_zemu/snapshots/sign-basic/6.png b/tests_zemu/snapshots/sign-basic/6.png index 3bfe48e647c2265d038e08ea82adb81aaa730150..4aa28b6bfdc9668cf49edfe790ea290941e99f83 100644 GIT binary patch delta 417 zcmbQhyqkG~N_~^3i(^Oy4N4D`XKD{6YX0;vUx80!Y>Tq+K>5XY-XlTbIHmPm<(+fm-m$$urzvCmX zl-vm)UjCG2Y$+KtQkJz%K6Az3ysXI89wxcuPM6AGWFwN00LcS6DWa4_%xl@G5t9JW_?K0KE#rEXWDDzCi% zv*Z&O8hCh5{K@?AQp(Th#2l%(NtK?@G-obP0U0@IW{TZ3ptOzg4z8J#>(2!9>YMIy zO#|9=Ps!Nyg2UnC6DnODcJ6BPeCBvqWwWhRj<Anp_e9O|xE(Jpb*soNWt{o=Qp1^W zrc+CGcYRUnJf7k3ES#5n=E{e@&kUs|{QPpz`>?nE8@Fd`6BR#O7?~)1{&cXrM`}Xl zo;J_4tRJDwM*TqT%MYqcO3C>@W0y=3GdgkMg9Ok9(;r@KZI8JnrSjG@&V0`@WqCHx z>a2(pKD_xx{;CUPZcE8TOUp$6esE6eUWyp+ Date: Wed, 17 Jun 2020 15:13:32 +0200 Subject: [PATCH 77/78] fixing Nano X address UI (#7) --- deps/ledger-zxlib/app/common/view_x.c | 14 ++++++++++++-- deps/ledger-zxlib/dockerized_build.mk | 5 +++++ deps/ledger-zxlib/include/zxversion.h | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/deps/ledger-zxlib/app/common/view_x.c b/deps/ledger-zxlib/app/common/view_x.c index 62d9e27a..0dc7633d 100644 --- a/deps/ledger-zxlib/app/common/view_x.c +++ b/deps/ledger-zxlib/app/common/view_x.c @@ -71,7 +71,13 @@ UX_STEP_NOCB_INIT(ux_addr_flow_2_step, bnnn_paging, UX_STEP_VALID(ux_addr_flow_3_step, pb, h_address_accept(0), { &C_icon_validate_14, "Ok"}); UX_FLOW( - ux_addr_flow, + ux_addr_flow_no_path, + &ux_addr_flow_1_step, + &ux_addr_flow_3_step +); + +UX_FLOW( + ux_addr_flow_with_path, &ux_addr_flow_1_step, &ux_addr_flow_2_step, &ux_addr_flow_3_step @@ -225,7 +231,11 @@ void view_address_show_impl() { if(G_ux.stack_count == 0) { ux_stack_push(); } - ux_flow_init(0, ux_addr_flow, NULL); + if (app_mode_expert()) { + ux_flow_init(0, ux_addr_flow_with_path, NULL); + } else { + ux_flow_init(0, ux_addr_flow_no_path, NULL); + } } void view_error_show_impl() { diff --git a/deps/ledger-zxlib/dockerized_build.mk b/deps/ledger-zxlib/dockerized_build.mk index 52cebcc8..3ac50101 100644 --- a/deps/ledger-zxlib/dockerized_build.mk +++ b/deps/ledger-zxlib/dockerized_build.mk @@ -140,6 +140,11 @@ dev_init_secondary: check_python show_info_recovery_mode dev_ca: check_python @python -m ledgerblue.setupCustomCA --targetId 0x31100004 --public $(SCP_PUBKEY) --name zondax +# This target will setup a custom developer certificate +.PHONY: dev_caX +dev_caX: check_python + @python -m ledgerblue.setupCustomCA --targetId 0x33000004 --public $(SCP_PUBKEY) --name zondax + .PHONY: dev_ca_delete dev_ca_delete: check_python @python -m ledgerblue.resetCustomCA --targetId 0x31100004 diff --git a/deps/ledger-zxlib/include/zxversion.h b/deps/ledger-zxlib/include/zxversion.h index 1926b003..0c7bcb1a 100644 --- a/deps/ledger-zxlib/include/zxversion.h +++ b/deps/ledger-zxlib/include/zxversion.h @@ -17,4 +17,4 @@ #define ZXLIB_MAJOR 2 #define ZXLIB_MINOR 3 -#define ZXLIB_PATCH 0 +#define ZXLIB_PATCH 1 From 6c194daa28936e273f9548eabca9e72ba04bb632 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Wed, 17 Jun 2020 15:30:14 +0200 Subject: [PATCH 78/78] only allow skipping review in expert mode --- deps/ledger-zxlib/app/common/view_s.c | 6 ++++-- deps/ledger-zxlib/include/zxversion.h | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/deps/ledger-zxlib/app/common/view_s.c b/deps/ledger-zxlib/app/common/view_s.c index 6e51367f..8930f85a 100644 --- a/deps/ledger-zxlib/app/common/view_s.c +++ b/deps/ledger-zxlib/app/common/view_s.c @@ -149,8 +149,10 @@ static unsigned int view_error_button(unsigned int button_mask, unsigned int but static unsigned int view_review_button(unsigned int button_mask, unsigned int button_mask_counter) { switch (button_mask) { case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: - // Press both left and right buttons to quit - view_sign_show_s(); + if (app_mode_expert()) { + // Press both left and right buttons to quit + view_sign_show_s(); + } break; case BUTTON_EVT_RELEASED | BUTTON_LEFT: // Press left to progress to the previous element diff --git a/deps/ledger-zxlib/include/zxversion.h b/deps/ledger-zxlib/include/zxversion.h index 0c7bcb1a..0c7467eb 100644 --- a/deps/ledger-zxlib/include/zxversion.h +++ b/deps/ledger-zxlib/include/zxversion.h @@ -17,4 +17,4 @@ #define ZXLIB_MAJOR 2 #define ZXLIB_MINOR 3 -#define ZXLIB_PATCH 1 +#define ZXLIB_PATCH 2