From 1e809c6b44921048ff899e7ddc3a0cf65483313d Mon Sep 17 00:00:00 2001 From: timemarkovqtum Date: Thu, 4 Nov 2021 11:53:31 +0100 Subject: [PATCH 1/2] Update qtum support * Add op_sender and update op_call and op_create checks * Fix get for script size * Update functions names in the style of the app * Check op_call and op_create only for qtum * Update max output size to 500 for Qtum * Update Qtum to be standalone app * Add Qtum specific code between macros * Fix for op_sender output check * Add Sign OP_SENDER into UX FLOW * Add function to get script for sender address * Add function to check is exist sender sig in the output * Add size to sender address script * Add sign op_sender support * Release more memory for Qtum * Move the sign sender code in btchip_hash_sender_sign.c * Add sender signature checks The signature is empty when signing the output and not empty when signing the inputs. * Add Qtum specific code between macros * Increase the max output size from 450 to 500 as before op_sender Op_sender work well with 500 too after disable Blake2b (350 with Blake2b enabled), which is not used for hashing in Qtum. * add qtum testnet * add support for other derivation path * Do not use LIB * Display delegation/contract details Co-authored-by: timemarkovqtum Co-authored-by: codeface Co-authored-by: Neil --- Makefile | 17 +- include/btchip_context.h | 10 + include/btchip_helpers.h | 12 ++ src/btchip_apdu_get_trusted_input.c | 3 + src/btchip_apdu_hash_input_finalize_full.c | 66 +++++-- src/btchip_apdu_hash_input_start.c | 15 ++ src/btchip_apdu_hash_sign.c | 38 +++- src/btchip_hash_sender_sign.c | 99 ++++++++++ src/btchip_hash_sender_sign.h | 27 +++ src/btchip_helpers.c | 207 ++++++++++++++++++++- src/btchip_internal.h | 3 + src/btchip_transaction.c | 42 ++++- src/main.c | 161 +++++++++++++++- 13 files changed, 661 insertions(+), 39 deletions(-) create mode 100644 src/btchip_hash_sender_sign.c create mode 100644 src/btchip_hash_sender_sign.h diff --git a/Makefile b/Makefile index 4d0fa531..0b4a93c0 100644 --- a/Makefile +++ b/Makefile @@ -138,9 +138,18 @@ else ifeq ($(COIN),qtum) # Qtum # Qtum can run significantly different code paths, thus is locked by the OS # using APP_LOAD_PARAMS instead of BIP44_COIN_TYPE -DEFINES += BIP44_COIN_TYPE=0 BIP44_COIN_TYPE_2=0 COIN_P2PKH_VERSION=58 COIN_P2SH_VERSION=50 COIN_FAMILY=3 COIN_COINID=\"Qtum\" COIN_COINID_HEADER=\"QTUM\" COIN_COLOR_HDR=0x2E9AD0 COIN_COLOR_DB=0x97CDE8 COIN_COINID_NAME=\"QTUM\" COIN_COINID_SHORT=\"QTUM\" COIN_NATIVE_SEGWIT_PREFIX=\"qc\" COIN_KIND=COIN_KIND_QTUM COIN_FLAGS=FLAG_SEGWIT_CHANGE_SUPPORT +DEFINES += BIP44_COIN_TYPE=0 BIP44_COIN_TYPE_2=0 COIN_P2PKH_VERSION=58 COIN_P2SH_VERSION=50 COIN_FAMILY=3 COIN_COINID=\"Qtum\" COIN_COINID_HEADER=\"QTUM\" COIN_COLOR_HDR=0x2E9AD0 COIN_COLOR_DB=0x97CDE8 COIN_COINID_NAME=\"QTUM\" COIN_COINID_SHORT=\"QTUM\" COIN_NATIVE_SEGWIT_PREFIX=\"qc\" COIN_KIND=COIN_KIND_QTUM COIN_FLAGS=FLAG_SEGWIT_CHANGE_SUPPORT MAX_OUTPUT_TO_CHECK=500 HAVE_QTUM_SUPPORT USE_NO_OVERWINTER APPNAME ="Qtum" -APP_LOAD_PARAMS += --path "44'/88'" --path "49'/88'" --path "84'/88'" --path "0'/45342'" --path "20698'/3053'/12648430'" +DEFINES_LIB=# we're not using the lib :) +APP_LOAD_PARAMS += --path $(APP_PATH) --path "44'/88'" --path "45'/88'" --path "48'/88'" --path "49'/88'" --path "84'/88'" --path "0'/45342'" --path "20698'/3053'/12648430'" +APP_LOAD_FLAGS=--appFlags 0xa50 +else ifeq ($(COIN),qtum_testnet) +# Qtum Testnet +DEFINES += BIP44_COIN_TYPE=0 BIP44_COIN_TYPE_2=0 COIN_P2PKH_VERSION=120 COIN_P2SH_VERSION=110 COIN_FAMILY=3 COIN_COINID=\"Qtum\" COIN_COINID_HEADER=\"QTUM\" COIN_COLOR_HDR=0x2E9AD0 COIN_COLOR_DB=0x97CDE8 COIN_COINID_NAME=\"QTUM\" COIN_COINID_SHORT=\"QTUM\" COIN_NATIVE_SEGWIT_PREFIX=\"tq\" COIN_KIND=COIN_KIND_QTUM COIN_FLAGS=FLAG_SEGWIT_CHANGE_SUPPORT MAX_OUTPUT_TO_CHECK=500 HAVE_QTUM_SUPPORT USE_NO_OVERWINTER +APPNAME ="Qtum Test" +DEFINES_LIB=# we're not using the lib :) +APP_LOAD_PARAMS += --path $(APP_PATH) --path "44'/1'" --path "45'/1'" --path "48'/1'" --path "49'/1'" --path "84'/1'" --path "0'/45342'" --path "20698'/3053'/12648430'" +APP_LOAD_FLAGS=--appFlags 0xa50 else ifeq ($(COIN),firo) DEFINES += BIP44_COIN_TYPE=136 BIP44_COIN_TYPE_2=136 COIN_P2PKH_VERSION=82 COIN_P2SH_VERSION=7 COIN_FAMILY=1 COIN_COINID=\"Zcoin\" COIN_COINID_HEADER=\"FIRO\" COIN_COLOR_HDR=0x3EAD54 COIN_COLOR_DB=0xA3DCAE COIN_COINID_NAME=\"Firo\" COIN_COINID_SHORT=\"FIRO\" COIN_KIND=COIN_KIND_FIRO APPNAME ="Firo" @@ -189,12 +198,12 @@ APPNAME ="Ravencoin" APP_LOAD_PARAMS += --path $(APP_PATH) else ifeq ($(COIN),hydra_testnet) # Hydra testnet -DEFINES += BIP44_COIN_TYPE=0 BIP44_COIN_TYPE_2=0 COIN_P2PKH_VERSION=66 COIN_P2SH_VERSION=128 COIN_FAMILY=3 COIN_COINID=\"Hydra\" COIN_COINID_HEADER=\"HYDRA\" COIN_COLOR_HDR=0x2E9AD0 COIN_COLOR_DB=0x97CDE8 COIN_COINID_NAME=\"HYDRA\" COIN_COINID_SHORT=\"HYDRA\" COIN_NATIVE_SEGWIT_PREFIX=\"hc\" COIN_KIND=COIN_KIND_HYDRA COIN_FLAGS=FLAG_SEGWIT_CHANGE_SUPPORT +DEFINES += BIP44_COIN_TYPE=0 BIP44_COIN_TYPE_2=0 COIN_P2PKH_VERSION=66 COIN_P2SH_VERSION=128 COIN_FAMILY=3 COIN_COINID=\"Hydra\" COIN_COINID_HEADER=\"HYDRA\" COIN_COLOR_HDR=0x2E9AD0 COIN_COLOR_DB=0x97CDE8 COIN_COINID_NAME=\"HYDRA\" COIN_COINID_SHORT=\"HYDRA\" COIN_NATIVE_SEGWIT_PREFIX=\"hc\" COIN_KIND=COIN_KIND_HYDRA COIN_FLAGS=FLAG_SEGWIT_CHANGE_SUPPORT HAVE_QTUM_SUPPORT APPNAME ="Hydra" APP_LOAD_PARAMS += --path "44'/609'" else ifeq ($(COIN),hydra) # Hydra mainnet -DEFINES += BIP44_COIN_TYPE=0 BIP44_COIN_TYPE_2=0 COIN_P2PKH_VERSION=40 COIN_P2SH_VERSION=63 COIN_FAMILY=3 COIN_COINID=\"Hydra\" COIN_COINID_HEADER=\"HYDRA\" COIN_COLOR_HDR=0x2E9AD0 COIN_COLOR_DB=0x97CDE8 COIN_COINID_NAME=\"HYDRA\" COIN_COINID_SHORT=\"HYDRA\" COIN_NATIVE_SEGWIT_PREFIX=\"hc\" COIN_KIND=COIN_KIND_HYDRA COIN_FLAGS=FLAG_SEGWIT_CHANGE_SUPPORT +DEFINES += BIP44_COIN_TYPE=0 BIP44_COIN_TYPE_2=0 COIN_P2PKH_VERSION=40 COIN_P2SH_VERSION=63 COIN_FAMILY=3 COIN_COINID=\"Hydra\" COIN_COINID_HEADER=\"HYDRA\" COIN_COLOR_HDR=0x2E9AD0 COIN_COLOR_DB=0x97CDE8 COIN_COINID_NAME=\"HYDRA\" COIN_COINID_SHORT=\"HYDRA\" COIN_NATIVE_SEGWIT_PREFIX=\"hc\" COIN_KIND=COIN_KIND_HYDRA COIN_FLAGS=FLAG_SEGWIT_CHANGE_SUPPORT HAVE_QTUM_SUPPORT APPNAME ="Hydra" APP_LOAD_PARAMS += --path "44'/609'" else diff --git a/include/btchip_context.h b/include/btchip_context.h index 2667b9d6..8b84bca6 100644 --- a/include/btchip_context.h +++ b/include/btchip_context.h @@ -24,7 +24,9 @@ #include "btchip_secure_value.h" #include "btchip_filesystem_tx.h" +#ifndef MAX_OUTPUT_TO_CHECK #define MAX_OUTPUT_TO_CHECK 100 +#endif #define MAX_COIN_ID 13 #define MAX_SHORT_COIN_ID 5 @@ -96,7 +98,9 @@ typedef enum btchip_output_parsing_state_e btchip_output_parsing_state_t; typedef union multi_hash { cx_sha256_t sha256; +#ifndef USE_NO_OVERWINTER cx_blake2b_t blake2b; +#endif } multi_hash; struct segwit_hash_s { @@ -171,6 +175,9 @@ struct btchip_context_s { cx_sha256_t transactionHashAuthorization; /** Current hash to perform (TRANSACTION_HASH_) */ unsigned char transactionHashOption; + #ifdef HAVE_QTUM_SUPPORT + cx_sha256_t transactionOutputHash; + #endif /* Segregated Witness changes */ @@ -185,6 +192,9 @@ struct btchip_context_s { unsigned char segwitParsedOnce; /** Prevents display of segwit input warning at each InputHashStart APDU */ unsigned char segwitWarningSeen; + #ifdef HAVE_QTUM_SUPPORT + unsigned char signOpSender; + #endif /* /Segregated Witness changes */ diff --git a/include/btchip_helpers.h b/include/btchip_helpers.h index a99504b7..82892374 100644 --- a/include/btchip_helpers.h +++ b/include/btchip_helpers.h @@ -35,14 +35,26 @@ unsigned char btchip_output_script_is_p2sh(unsigned char *buffer); unsigned char btchip_output_script_is_op_return(unsigned char *buffer); unsigned char btchip_output_script_is_native_witness(unsigned char *buffer); +#ifdef HAVE_QTUM_SUPPORT unsigned char btchip_output_script_is_op_create(unsigned char *buffer, size_t size); unsigned char btchip_output_script_is_op_call(unsigned char *buffer, size_t size); +unsigned char btchip_output_script_is_op_sender(unsigned char *buffer, + size_t size); +unsigned char btchip_get_script_size(unsigned char *buffer, size_t maxSize, + unsigned int *scriptSize, unsigned int *discardSize); +unsigned char btchip_get_script_sender_address(unsigned char *buffer, + size_t size, unsigned char *script); +unsigned char btchip_get_sender_sig(unsigned char *buffer, + size_t size, unsigned char **sig, unsigned int *sigSize); +#endif void btchip_sleep16(unsigned short delay); void btchip_sleep32(unsigned long int delayEach, unsigned long int delayRepeat); +unsigned long int btchip_read_u16(unsigned char *buffer, unsigned char be, + unsigned char skipSign); unsigned long int btchip_read_u32(unsigned char *buffer, unsigned char be, unsigned char skipSign); diff --git a/src/btchip_apdu_get_trusted_input.c b/src/btchip_apdu_get_trusted_input.c index cc46f4be..ae16abea 100644 --- a/src/btchip_apdu_get_trusted_input.c +++ b/src/btchip_apdu_get_trusted_input.c @@ -49,6 +49,9 @@ unsigned short btchip_apdu_get_trusted_input() { btchip_context_D.transactionHashOption = TRANSACTION_HASH_FULL; btchip_context_D.usingSegwit = 0; btchip_context_D.usingOverwinter = 0; + #ifdef HAVE_QTUM_SUPPORT + btchip_context_D.signOpSender = 0; + #endif } else if (G_io_apdu_buffer[ISO_OFFSET_P1] != GET_TRUSTED_INPUT_P1_NEXT) { return BTCHIP_SW_INCORRECT_P1_P2; } diff --git a/src/btchip_apdu_hash_input_finalize_full.c b/src/btchip_apdu_hash_input_finalize_full.c index dfb1cd9c..5327dca3 100644 --- a/src/btchip_apdu_hash_input_finalize_full.c +++ b/src/btchip_apdu_hash_input_finalize_full.c @@ -44,7 +44,7 @@ static bool check_output_displayable() { bool displayable = true; unsigned char amount[8], isOpReturn, isP2sh, isNativeSegwit, j, nullAmount = 1; - unsigned char isOpCreate, isOpCall; + unsigned char isOpCreate = 0, isOpCall = 0, isOpSender = 0; for (j = 0; j < 8; j++) { if (btchip_context_D.currentOutput[j] != 0) { @@ -62,12 +62,19 @@ static bool check_output_displayable() { isP2sh = btchip_output_script_is_p2sh(btchip_context_D.currentOutput + 8); isNativeSegwit = btchip_output_script_is_native_witness( btchip_context_D.currentOutput + 8); - isOpCreate = - btchip_output_script_is_op_create(btchip_context_D.currentOutput + 8, - sizeof(btchip_context_D.currentOutput) - 8); - isOpCall = - btchip_output_script_is_op_call(btchip_context_D.currentOutput + 8, - sizeof(btchip_context_D.currentOutput) - 8); + #ifdef HAVE_QTUM_SUPPORT + if(G_coin_config->kind == COIN_KIND_QTUM) { + isOpCreate = + btchip_output_script_is_op_create(btchip_context_D.currentOutput + 8, + sizeof(btchip_context_D.currentOutput) - 8); + isOpCall = + btchip_output_script_is_op_call(btchip_context_D.currentOutput + 8, + sizeof(btchip_context_D.currentOutput) - 8); + isOpSender = + btchip_output_script_is_op_sender(btchip_context_D.currentOutput + 8, + sizeof(btchip_context_D.currentOutput) - 8); + } + #endif if (((G_coin_config->kind == COIN_KIND_QTUM || G_coin_config->kind == COIN_KIND_HYDRA) && !btchip_output_script_is_regular(btchip_context_D.currentOutput + 8) && !isP2sh && !(nullAmount && isOpReturn) && !isOpCreate && !isOpCall) || @@ -77,13 +84,32 @@ static bool check_output_displayable() { PRINTF("Error : Unrecognized output script"); THROW(EXCEPTION); } + #ifdef HAVE_QTUM_SUPPORT + if((G_coin_config->kind == COIN_KIND_QTUM) && isOpSender && (isOpCreate || isOpCall)) + { + unsigned char *sig = 0; + unsigned int sigSize = 0; + btchip_get_sender_sig(btchip_context_D.currentOutput + 8, + sizeof(btchip_context_D.currentOutput) - 8, &sig, &sigSize); + if(!btchip_context_D.signOpSender && sigSize == 0) + { + PRINTF("Error : No op_sender signature"); + THROW(EXCEPTION); + } + if(btchip_context_D.signOpSender && sigSize > 0) + { + PRINTF("Error : op_sender is already signed"); + THROW(EXCEPTION); + } + } + #endif if (btchip_context_D.tmpCtx.output.changeInitialized && !isOpReturn) { bool changeFound = false; unsigned char addressOffset = (isNativeSegwit ? OUTPUT_SCRIPT_NATIVE_WITNESS_PROGRAM_OFFSET : isP2sh ? OUTPUT_SCRIPT_P2SH_PRE_LENGTH : OUTPUT_SCRIPT_REGULAR_PRE_LENGTH); - if (!isP2sh && + if (!isP2sh && !isOpSender && os_memcmp(btchip_context_D.currentOutput + 8 + addressOffset, btchip_context_D.tmpCtx.output.changeAddress, 20) == 0) { @@ -176,7 +202,7 @@ bool handle_output_state() { break; } scriptSize = - btchip_read_u32(btchip_context_D.currentOutput + 9, 0, 0); + btchip_read_u16(btchip_context_D.currentOutput + 9, 0, 0); discardSize = 3; } else { // Unrealistically large script @@ -332,10 +358,13 @@ unsigned short btchip_apdu_hash_input_finalize_full_internal( sw = BTCHIP_SW_INCORRECT_DATA; goto discardTransaction; } + #ifndef USE_NO_OVERWINTER if (btchip_context_D.usingOverwinter) { cx_hash(&btchip_context_D.transactionHashFull.blake2b.header, 0, G_io_apdu_buffer + ISO_OFFSET_CDATA + hashOffset, apduLength - hashOffset, NULL, 0); } - else { + else + #endif + { PRINTF("--- ADD TO HASH FULL:\n%.*H\n", apduLength - hashOffset, G_io_apdu_buffer + ISO_OFFSET_CDATA + hashOffset); cx_hash(&btchip_context_D.transactionHashFull.sha256.header, 0, G_io_apdu_buffer + ISO_OFFSET_CDATA + hashOffset, @@ -392,10 +421,13 @@ unsigned short btchip_apdu_hash_input_finalize_full_internal( if (btchip_context_D.usingSegwit) { if (!btchip_context_D.segwitParsedOnce) { + #ifndef USE_NO_OVERWINTER if (btchip_context_D.usingOverwinter) { cx_hash(&btchip_context_D.transactionHashFull.blake2b.header, CX_LAST, btchip_context_D.segwit.cache.hashedOutputs, 0, btchip_context_D.segwit.cache.hashedOutputs, 32); } - else { + else + #endif + { cx_hash(&btchip_context_D.transactionHashFull.sha256.header, CX_LAST, btchip_context_D.segwit.cache.hashedOutputs, 0, @@ -470,7 +502,11 @@ unsigned short btchip_apdu_hash_input_finalize_full_internal( } if (btchip_context_D.usingSegwit && - !btchip_context_D.segwitParsedOnce) { + !btchip_context_D.segwitParsedOnce + #ifdef HAVE_QTUM_SUPPORT + && !btchip_context_D.signOpSender + #endif + ) { // This input cannot be signed when using segwit - just restart. btchip_context_D.segwitParsedOnce = 1; PRINTF("Segwit parsed once\n"); @@ -592,7 +628,11 @@ unsigned char btchip_bagl_user_action(unsigned char confirming) { btchip_context_D.transactionContext.firstSigned = 0; if (btchip_context_D.usingSegwit && - !btchip_context_D.segwitParsedOnce) { + !btchip_context_D.segwitParsedOnce + #ifdef HAVE_QTUM_SUPPORT + && !btchip_context_D.signOpSender + #endif + ) { // This input cannot be signed when using segwit - just restart. btchip_context_D.segwitParsedOnce = 1; PRINTF("Segwit parsed once\n"); diff --git a/src/btchip_apdu_hash_input_start.c b/src/btchip_apdu_hash_input_start.c index a2e7bfb9..16431b98 100644 --- a/src/btchip_apdu_hash_input_start.c +++ b/src/btchip_apdu_hash_input_start.c @@ -27,6 +27,7 @@ #define P2_NEW_SEGWIT_OVERWINTER 0x04 #define P2_NEW_SEGWIT_SAPLING 0x05 #define P2_CONTINUE 0x80 +#define P2_NEW_SENDER 0x81 #define IS_INPUT() \ (G_io_apdu_buffer[ISO_OFFSET_LC] - 1 > 8 \ @@ -64,6 +65,9 @@ unsigned short btchip_apdu_hash_input_start() { } if ((G_io_apdu_buffer[ISO_OFFSET_P2] == P2_NEW) || + #ifdef HAVE_QTUM_SUPPORT + (G_io_apdu_buffer[ISO_OFFSET_P2] == P2_NEW_SENDER) || + #endif (G_io_apdu_buffer[ISO_OFFSET_P2] == P2_NEW_SEGWIT) || (G_io_apdu_buffer[ISO_OFFSET_P2] == P2_NEW_SEGWIT_CASHADDR) || (G_io_apdu_buffer[ISO_OFFSET_P2] == P2_NEW_SEGWIT_OVERWINTER) || @@ -78,6 +82,10 @@ unsigned short btchip_apdu_hash_input_start() { (G_io_apdu_buffer[ISO_OFFSET_P2] == P2_NEW_SEGWIT_SAPLING); unsigned char usingCashAddr = (G_io_apdu_buffer[ISO_OFFSET_P2] == P2_NEW_SEGWIT_CASHADDR); + #ifdef HAVE_QTUM_SUPPORT + unsigned char signOpSender = + (G_io_apdu_buffer[ISO_OFFSET_P2] == P2_NEW_SENDER); + #endif // Request PIN validation // Only request PIN validation (user presence) to start a new // transaction signing flow. @@ -92,7 +100,14 @@ unsigned short btchip_apdu_hash_input_start() { btchip_context_D.transactionContext.firstSigned = 1; btchip_context_D.transactionContext.consumeP2SH = 0; btchip_context_D.transactionContext.relaxed = 0; + #ifdef HAVE_QTUM_SUPPORT + if(signOpSender) + usingSegwit = 1; + #endif btchip_context_D.usingSegwit = usingSegwit; + #ifdef HAVE_QTUM_SUPPORT + btchip_context_D.signOpSender = signOpSender; + #endif btchip_context_D.usingCashAddr = (G_coin_config->kind == COIN_KIND_BITCOIN_CASH ? usingCashAddr : 0); diff --git a/src/btchip_apdu_hash_sign.c b/src/btchip_apdu_hash_sign.c index a9c86d03..bd12d5ac 100644 --- a/src/btchip_apdu_hash_sign.c +++ b/src/btchip_apdu_hash_sign.c @@ -56,6 +56,7 @@ unsigned short btchip_apdu_hash_sign() { // Zcash special - store parameters for later + #ifndef USE_NO_OVERWINTER if ((btchip_context_D.usingOverwinter) && (!btchip_context_D.overwinterSignReady) && (btchip_context_D.segwitParsedOnce) && @@ -75,6 +76,7 @@ unsigned short btchip_apdu_hash_sign() { CLOSE_TRY; return BTCHIP_SW_OK; } + #endif if (btchip_context_D.transactionContext.transactionState != BTCHIP_TRANSACTION_SIGN_READY) { @@ -83,11 +85,13 @@ unsigned short btchip_apdu_hash_sign() { goto discardTransaction; } + #ifndef USE_NO_OVERWINTER if (btchip_context_D.usingOverwinter && !btchip_context_D.overwinterSignReady) { PRINTF("Overwinter not ready to sign\n"); sw = BTCHIP_SW_CONDITIONS_OF_USE_NOT_SATISFIED; goto discardTransaction; } + #endif // Read parameters if (G_io_apdu_buffer[ISO_OFFSET_CDATA] > MAX_BIP32_PATH) { @@ -130,9 +134,18 @@ unsigned short btchip_apdu_hash_sign() { if (!btchip_context_D.usingOverwinter) { btchip_write_u32_le(dataBuffer, lockTime); btchip_write_u32_le(dataBuffer + 4, sighashType); - PRINTF("--- ADD TO HASH FULL:\n%.*H\n", sizeof(dataBuffer), dataBuffer); - cx_hash(&btchip_context_D.transactionHashFull.sha256.header, 0, - dataBuffer, sizeof(dataBuffer), NULL, 0); + #ifdef HAVE_QTUM_SUPPORT + if(btchip_context_D.signOpSender) + { + btchip_hash_sender_finalize(dataBuffer, sizeof(dataBuffer)); + } + else + #endif + { + PRINTF("--- ADD TO HASH FULL:\n%.*H\n", sizeof(dataBuffer), dataBuffer); + cx_hash(&btchip_context_D.transactionHashFull.sha256.header, 0, + dataBuffer, sizeof(dataBuffer), NULL, 0); + } } // Check if the path needs to be enforced @@ -179,13 +192,26 @@ void btchip_bagl_user_action_signtx(unsigned char confirming, unsigned char dire unsigned char hash[32]; // Fetch the private key btchip_private_derive_keypair(btchip_context_D.transactionSummary.keyPath, 0, NULL, &private_key, NULL); + #ifndef USE_NO_OVERWINTER if (btchip_context_D.usingOverwinter) { cx_hash(&btchip_context_D.transactionHashFull.blake2b.header, CX_LAST, hash, 0, hash, 32); } - else { + else + #endif + { cx_sha256_t localHash; - cx_hash(&btchip_context_D.transactionHashFull.sha256.header, CX_LAST, - hash, 0, hash, 32); + #ifdef HAVE_QTUM_SUPPORT + if(btchip_context_D.signOpSender) + { + cx_hash(&btchip_context_D.transactionOutputHash.header, CX_LAST, + hash, 0, hash, 32); + } + else + #endif + { + cx_hash(&btchip_context_D.transactionHashFull.sha256.header, CX_LAST, + hash, 0, hash, 32); + } PRINTF("Hash1\n%.*H\n", sizeof(hash), hash); // Rehash diff --git a/src/btchip_hash_sender_sign.c b/src/btchip_hash_sender_sign.c new file mode 100644 index 00000000..d12347a8 --- /dev/null +++ b/src/btchip_hash_sender_sign.c @@ -0,0 +1,99 @@ +/******************************************************************************* +* Ledger App - Bitcoin Wallet +* (c) 2016-2019 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +#ifdef HAVE_QTUM_SUPPORT +#include "btchip_internal.h" +#include "btchip_hash_sender_sign.h" + +unsigned char btchip_hash_sender_start(unsigned char* senderOutput) +{ + cx_sha256_init(&btchip_context_D.transactionOutputHash); + + // Use cache data generated from Segwit + PRINTF("--- ADD TO HASH SENDER:\n%.*H\n", sizeof(btchip_context_D.transactionVersion), btchip_context_D.transactionVersion); + cx_hash( + &btchip_context_D.transactionOutputHash.header, 0, + btchip_context_D.transactionVersion, + sizeof(btchip_context_D.transactionVersion), + NULL, 0); + + PRINTF("--- ADD TO HASH SENDER:\n%.*H\n", sizeof(btchip_context_D.segwit.cache.hashedPrevouts), btchip_context_D.segwit.cache.hashedPrevouts); + cx_hash( + &btchip_context_D.transactionOutputHash.header, 0, + btchip_context_D.segwit.cache.hashedPrevouts, + sizeof(btchip_context_D.segwit.cache + .hashedPrevouts), + NULL, 0); + + PRINTF("--- ADD TO HASH SENDER:\n%.*H\n", sizeof(btchip_context_D.segwit.cache.hashedSequence), btchip_context_D.segwit.cache.hashedSequence); + cx_hash( + &btchip_context_D.transactionOutputHash.header, 0, + btchip_context_D.segwit.cache.hashedSequence, + sizeof(btchip_context_D.segwit.cache + .hashedSequence), + NULL, 0); + + // Op sender specific data + unsigned int scriptSize = 0; + unsigned int discardSize = 0; + if(btchip_get_script_size(senderOutput + 8, sizeof(btchip_context_D.currentOutput), &scriptSize, &discardSize)) + { + unsigned outputSize = 8 + scriptSize + discardSize; + PRINTF("--- ADD TO HASH SENDER:\n%.*H\n", outputSize, senderOutput); + cx_hash( + &btchip_context_D.transactionOutputHash.header, 0, + senderOutput, + outputSize, + NULL, 0); + + unsigned char scriptCode[26]; + if(!btchip_get_script_sender_address(senderOutput + 8, sizeof(btchip_context_D.currentOutput), scriptCode)) + return 0; + + PRINTF("--- ADD TO HASH SENDER:\n%.*H\n", sizeof(scriptCode), scriptCode); + cx_hash( + &btchip_context_D.transactionOutputHash.header, 0, + scriptCode, + sizeof(scriptCode), + NULL, 0); + + PRINTF("--- ADD TO HASH SENDER:\n%.*H\n", 8, senderOutput); + cx_hash( + &btchip_context_D.transactionOutputHash.header, 0, + senderOutput, + 8, + NULL, 0); + } + else + return 0; + + return 1; +} + +void btchip_hash_sender_finalize(unsigned char* dataBuffer, unsigned int bufferSize) +{ + PRINTF("--- ADD TO HASH SENDER:\n%.*H\n", sizeof(btchip_context_D.segwit.cache.hashedOutputs), btchip_context_D.segwit.cache.hashedOutputs); + cx_hash( + &btchip_context_D.transactionOutputHash.header, 0, + btchip_context_D.segwit.cache.hashedOutputs, + sizeof(btchip_context_D.segwit.cache + .hashedOutputs), + NULL, 0); + PRINTF("--- ADD TO HASH SENDER:\n%.*H\n", bufferSize, dataBuffer); + cx_hash(&btchip_context_D.transactionOutputHash.header, 0, + dataBuffer, bufferSize, NULL, 0); +} +#endif diff --git a/src/btchip_hash_sender_sign.h b/src/btchip_hash_sender_sign.h new file mode 100644 index 00000000..694808b4 --- /dev/null +++ b/src/btchip_hash_sender_sign.h @@ -0,0 +1,27 @@ +/******************************************************************************* +* Ledger App - Bitcoin Wallet +* (c) 2016-2019 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. +********************************************************************************/ + +#ifndef BTCHIP_HASH_SENDER_SIGN_H +#define BTCHIP_HASH_SENDER_SIGN_H +#ifdef HAVE_QTUM_SUPPORT +#include "os.h" +#include "cx.h" + +unsigned char btchip_hash_sender_start(unsigned char* senderOutput); +void btchip_hash_sender_finalize(unsigned char* dataBuffer, unsigned int bufferSize); +#endif +#endif diff --git a/src/btchip_helpers.c b/src/btchip_helpers.c index a7131b0c..567a5df1 100644 --- a/src/btchip_helpers.c +++ b/src/btchip_helpers.c @@ -18,6 +18,35 @@ #include "btchip_internal.h" #include "btchip_apdu_constants.h" +#ifdef HAVE_QTUM_SUPPORT +/** Script opcodes */ +enum opcodetype +{ + // push value + OP_PUSHDATA1 = 0x4c, + OP_PUSHDATA2 = 0x4d, + OP_PUSHDATA4 = 0x4e, + + // stack ops + OP_DUP = 0x76, + + // bit logic + OP_EQUALVERIFY = 0x88, + + // crypto + OP_HASH160 = 0xa9, + OP_CHECKSIG = 0xac, + + // Execute EXT byte code. + OP_CREATE = 0xc1, + OP_CALL = 0xc2, + OP_SPEND = 0xc3, + OP_SENDER = 0xc4, + + OP_INVALIDOPCODE = 0xff, +}; +#endif + const unsigned char TRANSACTION_OUTPUT_SCRIPT_PRE[] = { 0x19, 0x76, 0xA9, 0x14}; // script length, OP_DUP, OP_HASH160, address length @@ -120,6 +149,130 @@ unsigned char btchip_output_script_is_native_witness(unsigned char *buffer) { return 0; } +#ifdef HAVE_QTUM_SUPPORT +unsigned char btchip_get_script_op(unsigned char ** pc, const unsigned char * end, unsigned char* opcodeRet, unsigned char **pvchRet, unsigned int *pvchSize) +{ + *opcodeRet = OP_INVALIDOPCODE; + if (*pc >= end) + return 0; + + if(pvchRet) + *pvchRet = 0; + if(pvchSize) + *pvchSize = 0; + + // Read instruction + if (end - *pc < 1) + return 0; + unsigned char opcode = *(*pc)++; + + // Immediate operand + if (opcode <= OP_PUSHDATA4) + { + unsigned int nSize = 0; + if (opcode < OP_PUSHDATA1) + { + nSize = opcode; + } + else if (opcode == OP_PUSHDATA1) + { + if (end - *pc < 1) + return 0; + nSize = *(*pc)++; + } + else if (opcode == OP_PUSHDATA2) + { + if (end - *pc < 2) + return 0; + + nSize = btchip_read_u16(*pc, 0, 0); + *pc += 2; + } + else if (opcode == OP_PUSHDATA4) + { + if (end - *pc < 4) + return 0; + nSize = btchip_read_u32(*pc, 0, 0); + *pc += 4; + } + if (end - *pc < 0 || (unsigned int)(end - *pc) < nSize) + return 0; + if(pvchRet) + *pvchRet = *pc; + if(pvchSize) + *pvchSize = nSize; + *pc += nSize; + } + + *opcodeRet = opcode; + return 1; +} + +unsigned char btchip_get_script_size(unsigned char *buffer, size_t maxSize, unsigned int *scriptSize, unsigned int *discardSize) +{ + *scriptSize = 0; + *discardSize = 0; + if (maxSize > 0 && buffer[0] < 0xFD) { + *scriptSize = buffer[0]; + *discardSize = 1; + } else if (maxSize > 2 && buffer[0] == 0xFD) { + *scriptSize = btchip_read_u16(buffer + 1, 0, 0); + *discardSize = 3; + } else { + return 0; + } + + size_t bifferSize = *scriptSize + *discardSize; + if(bifferSize <= maxSize) { + return 1; + } + + return 0; +} + +int btchip_find_script_op(unsigned char *buffer, size_t size, unsigned char op, unsigned char haveSize) +{ + int nFound = 0; + unsigned int scriptSize = size; + unsigned int discardSize = 0; + if(haveSize) + btchip_get_script_size(buffer, size, &scriptSize, &discardSize); + unsigned char opcode = OP_INVALIDOPCODE; + const unsigned char* end = buffer + scriptSize + discardSize; + unsigned char *begin = buffer + discardSize; + for (unsigned char * pc = begin; pc != end && btchip_get_script_op(&pc, end, &opcode, 0, 0);) + if (opcode == op) + ++nFound; + return nFound; +} + +unsigned char btchip_find_script_data(unsigned char *buffer, size_t size, int index, unsigned char haveSize, unsigned char **pvchRet, unsigned int *pvchSize) +{ + unsigned int scriptSize = size; + unsigned int discardSize = 0; + if(haveSize) + btchip_get_script_size(buffer, size, &scriptSize, &discardSize); + unsigned char opcode = OP_INVALIDOPCODE; + const unsigned char* end = buffer + scriptSize + discardSize; + unsigned char *begin = buffer + discardSize; + int i = 0; + for (unsigned char * pc = begin; i < index && pc != end && btchip_get_script_op(&pc, end, &opcode, pvchRet, pvchSize); i++); + return i == index; +} + +void btchip_get_script_p2pkh(const unsigned char *pkh, unsigned char *script, unsigned char haveSize) +{ + unsigned char offset = haveSize ? 1 : 0; + if(haveSize) script[0] = 0x19; + script[0 + offset] = OP_DUP; + script[1 + offset] = OP_HASH160; + script[2 + offset] = 0x14; + os_memcpy(script + 3 + offset, pkh, 20); + script[23 + offset] = OP_EQUALVERIFY; + script[24 + offset] = OP_CHECKSIG; +} +#endif + unsigned char btchip_output_script_is_op_return(unsigned char *buffer) { if (G_coin_config->kind == COIN_KIND_BITCOIN_CASH) { return ((buffer[1] == 0x6A) || ((buffer[1] == 0x00) && (buffer[2] == 0x6A))); @@ -129,26 +282,48 @@ unsigned char btchip_output_script_is_op_return(unsigned char *buffer) { } } -static unsigned char output_script_is_op_create_or_call(unsigned char *buffer, +#ifdef HAVE_QTUM_SUPPORT +static unsigned char output_script_is_op_contract(unsigned char *buffer, size_t size, unsigned char value) { return (!btchip_output_script_is_regular(buffer) && !btchip_output_script_is_p2sh(buffer) && - !btchip_output_script_is_op_return(buffer) && (buffer[0] <= 0xEA) && - (buffer[0] < size) && - (buffer[buffer[0]] == value)); + !btchip_output_script_is_op_return(buffer) && + btchip_find_script_op(buffer, size, value, 1) == 1); } unsigned char btchip_output_script_is_op_create(unsigned char *buffer, size_t size) { - return output_script_is_op_create_or_call(buffer, size, 0xC1); + return output_script_is_op_contract(buffer, size, OP_CREATE); } unsigned char btchip_output_script_is_op_call(unsigned char *buffer, size_t size) { - return output_script_is_op_create_or_call(buffer, size, 0xC2); + return output_script_is_op_contract(buffer, size, OP_CALL); } +unsigned char btchip_output_script_is_op_sender(unsigned char *buffer, + size_t size) { + return output_script_is_op_contract(buffer, size, OP_SENDER); +} + +unsigned char btchip_get_script_sender_address(unsigned char *buffer, + size_t size, unsigned char *script) { + unsigned char *pkh = 0; + unsigned int pkhSize = 0; + unsigned char ret = btchip_find_script_data(buffer, size, 2, 1, &pkh, &pkhSize) == 1 && pkh != 0 && pkhSize == 20; + if(ret) btchip_get_script_p2pkh(pkh, script, 1); + return ret; +} + +unsigned char btchip_get_sender_sig(unsigned char *buffer, + size_t size, unsigned char **sig, unsigned int *sigSize) { + if(sig == 0 || sigSize == 0) + return 0; + return btchip_find_script_data(buffer, size, 3, 1, sig, sigSize) && *sig != 0 && *sigSize > 0; +} +#endif + unsigned char btchip_rng_u8_modulo(unsigned char modulo) { unsigned int rng_max = 256 % modulo; unsigned int rng_limit = 256 - rng_max; @@ -171,6 +346,26 @@ unsigned char btchip_secure_memcmp(const void *buf1, const void *buf2, return error; } +unsigned long int btchip_read_u16(unsigned char *buffer, unsigned char be, + unsigned char skipSign) { + unsigned char i; + unsigned long int result = 0; + unsigned char shiftValue = (be ? 8 : 0); + for (i = 0; i < 2; i++) { + unsigned char x = (unsigned char)buffer[i]; + if ((i == 0) && skipSign) { + x &= 0x7f; + } + result += ((unsigned long int)x) << shiftValue; + if (be) { + shiftValue -= 8; + } else { + shiftValue += 8; + } + } + return result; +} + unsigned long int btchip_read_u32(unsigned char *buffer, unsigned char be, unsigned char skipSign) { unsigned char i; diff --git a/src/btchip_internal.h b/src/btchip_internal.h index d6b455dd..4222ca3d 100644 --- a/src/btchip_internal.h +++ b/src/btchip_internal.h @@ -27,5 +27,8 @@ #include "btchip_ecc.h" #include "btchip_helpers.h" #include "btchip_transaction.h" +#ifdef HAVE_QTUM_SUPPORT +#include "btchip_hash_sender_sign.h" +#endif #endif diff --git a/src/btchip_transaction.c b/src/btchip_transaction.c index 4f218534..457c04e3 100644 --- a/src/btchip_transaction.c +++ b/src/btchip_transaction.c @@ -86,10 +86,13 @@ unsigned char transaction_amount_sub_be(unsigned char *target, void transaction_offset(unsigned char value) { if ((btchip_context_D.transactionHashOption & TRANSACTION_HASH_FULL) != 0) { PRINTF("--- ADD TO HASH FULL:\n%.*H\n", value, btchip_context_D.transactionBufferPointer); + #ifndef USE_NO_OVERWINTER if (btchip_context_D.usingOverwinter) { cx_hash(&btchip_context_D.transactionHashFull.blake2b.header, 0, btchip_context_D.transactionBufferPointer, value, NULL, 0); } - else { + else + #endif + { cx_hash(&btchip_context_D.transactionHashFull.sha256.header, 0, btchip_context_D.transactionBufferPointer, value, NULL, 0); } @@ -163,6 +166,7 @@ void transaction_parse(unsigned char parseMode) { .transactionAmount)); // TODO : transactionControlFid // Reset hashes + #ifndef USE_NO_OVERWINTER if (btchip_context_D.usingOverwinter) { if (btchip_context_D.segwitParsedOnce) { uint8_t parameters[16]; @@ -178,7 +182,9 @@ void transaction_parse(unsigned char parseMode) { cx_blake2b_init2(&btchip_context_D.transactionHashFull.blake2b, 256, NULL, 0, parameters, 16); } } - else { + else + #endif + { cx_sha256_init(&btchip_context_D.transactionHashFull.sha256); } cx_sha256_init( @@ -186,11 +192,14 @@ void transaction_parse(unsigned char parseMode) { if (btchip_context_D.usingSegwit) { btchip_context_D.transactionHashOption = 0; if (!btchip_context_D.segwitParsedOnce) { + #ifndef USE_NO_OVERWINTER if (btchip_context_D.usingOverwinter) { cx_blake2b_init2(&btchip_context_D.segwit.hash.hashPrevouts.blake2b, 256, NULL, 0, (uint8_t *)OVERWINTER_PARAM_PREVOUTS, 16); cx_blake2b_init2(&btchip_context_D.transactionHashFull.blake2b, 256, NULL, 0, (uint8_t *)OVERWINTER_PARAM_SEQUENCE, 16); } - else { + else + #endif + { cx_sha256_init( &btchip_context_D.segwit.hash.hashPrevouts.sha256); } @@ -199,6 +208,7 @@ void transaction_parse(unsigned char parseMode) { PRINTF("SEGWIT Version\n%.*H\n",sizeof(btchip_context_D.transactionVersion),btchip_context_D.transactionVersion); PRINTF("SEGWIT HashedPrevouts\n%.*H\n",sizeof(btchip_context_D.segwit.cache.hashedPrevouts),btchip_context_D.segwit.cache.hashedPrevouts); PRINTF("SEGWIT HashedSequence\n%.*H\n",sizeof(btchip_context_D.segwit.cache.hashedSequence),btchip_context_D.segwit.cache.hashedSequence); + #ifndef USE_NO_OVERWINTER if (btchip_context_D.usingOverwinter) { cx_hash(&btchip_context_D.transactionHashFull.blake2b.header, 0, btchip_context_D.transactionVersion, sizeof(btchip_context_D.transactionVersion), NULL, 0); cx_hash(&btchip_context_D.transactionHashFull.blake2b.header, 0, btchip_context_D.nVersionGroupId, sizeof(btchip_context_D.nVersionGroupId), NULL, 0); @@ -219,7 +229,9 @@ void transaction_parse(unsigned char parseMode) { } cx_hash(&btchip_context_D.transactionHashFull.blake2b.header, 0, btchip_context_D.sigHashType, sizeof(btchip_context_D.sigHashType), NULL, 0); } - else { + else + #endif + { PRINTF("--- ADD TO HASH FULL:\n%.*H\n", sizeof(btchip_context_D.transactionVersion), btchip_context_D.transactionVersion); cx_hash( &btchip_context_D.transactionHashFull.sha256.header, 0, @@ -412,10 +424,13 @@ void transaction_parse(unsigned char parseMode) { check_transaction_available( 36); // prevout : 32 hash + 4 index if (!btchip_context_D.segwitParsedOnce) { + #ifndef USE_NO_OVERWINTER if (btchip_context_D.usingOverwinter) { cx_hash(&btchip_context_D.segwit.hash.hashPrevouts.blake2b.header, 0, btchip_context_D.transactionBufferPointer, 36, NULL, 0); } - else { + else + #endif + { cx_hash( &btchip_context_D.segwit.hash.hashPrevouts .sha256.header, @@ -607,10 +622,13 @@ void transaction_parse(unsigned char parseMode) { if (btchip_context_D.segwitParsedOnce) { // Append the saved value PRINTF("SEGWIT Add value\n%.*H\n",8,btchip_context_D.inputValue); + #ifndef USE_NO_OVERWINTER if (btchip_context_D.usingOverwinter) { cx_hash(&btchip_context_D.transactionHashFull.blake2b.header, 0, btchip_context_D.inputValue, 8, NULL, 0); } - else { + else + #endif + { PRINTF("--- ADD TO HASH FULL:\n%.*H\n", sizeof(btchip_context_D.inputValue), btchip_context_D.inputValue); cx_hash(&btchip_context_D .transactionHashFull.sha256.header, @@ -624,10 +642,13 @@ void transaction_parse(unsigned char parseMode) { check_transaction_available(4); if (btchip_context_D.usingSegwit && !btchip_context_D.segwitParsedOnce) { + #ifndef USE_NO_OVERWINTER if (btchip_context_D.usingOverwinter) { cx_hash(&btchip_context_D.transactionHashFull.blake2b.header, 0, btchip_context_D.transactionBufferPointer, 4, NULL, 0); } - else { + else + #endif + { PRINTF("--- ADD TO HASH FULL:\n%.*H\n", 4, btchip_context_D.transactionBufferPointer); cx_hash(&btchip_context_D.transactionHashFull .sha256.header, @@ -673,11 +694,14 @@ void transaction_parse(unsigned char parseMode) { unsigned char hashedPrevouts[32]; unsigned char hashedSequence[32]; // Flush the cache + #ifndef USE_NO_OVERWINTER if (btchip_context_D.usingOverwinter) { cx_hash(&btchip_context_D.segwit.hash.hashPrevouts.blake2b.header, CX_LAST, hashedPrevouts, 0, hashedPrevouts, 32); cx_hash(&btchip_context_D.transactionHashFull.blake2b.header, CX_LAST, hashedSequence, 0, hashedSequence, 32); } - else { + else + #endif + { cx_hash(&btchip_context_D.segwit.hash.hashPrevouts .sha256.header, CX_LAST, hashedPrevouts, 0, hashedPrevouts, 32); @@ -726,10 +750,12 @@ void transaction_parse(unsigned char parseMode) { btchip_context_D.transactionContext .transactionState = BTCHIP_TRANSACTION_PRESIGN_READY; + #ifndef USE_NO_OVERWINTER if (btchip_context_D.usingOverwinter) { cx_blake2b_init2(&btchip_context_D.transactionHashFull.blake2b, 256, NULL, 0, (uint8_t *)OVERWINTER_PARAM_OUTPUTS, 16); } else + #endif if (btchip_context_D.usingSegwit) { cx_sha256_init(&btchip_context_D.transactionHashFull.sha256); } diff --git a/src/main.c b/src/main.c index b4069954..3a283047 100644 --- a/src/main.c +++ b/src/main.c @@ -327,6 +327,17 @@ UX_STEP_CB( &C_icon_crossmark, "Reject", }); +#ifdef HAVE_QTUM_SUPPORT +UX_STEP_CB( + ux_confirm_full_flow_7_step, + pbb, + io_seproxyhal_touch_verify_ok(NULL), + { + &C_icon_validate_14, + "Sign", + "OP_SENDER", + }); +#endif // confirm_full: confirm transaction / Amount: fullAmount / Address: fullAddress / Fees: feesAmount UX_FLOW(ux_confirm_full_flow, &ux_confirm_full_flow_1_step, @@ -337,6 +348,18 @@ UX_FLOW(ux_confirm_full_flow, &ux_confirm_full_flow_6_step ); +#ifdef HAVE_QTUM_SUPPORT +// confirm_full: sign output sender transaction / Amount: fullAmount / Address: fullAddress / Fees: feesAmount +UX_FLOW(ux_confirm_sender_flow, + &ux_confirm_full_flow_1_step, + &ux_confirm_full_flow_2_step, + &ux_confirm_full_flow_3_step, + &ux_confirm_full_flow_4_step, + &ux_confirm_full_flow_7_step, + &ux_confirm_full_flow_6_step +); +#endif + ////////////////////////////////////////////////////////////////////// UX_STEP_NOCB( @@ -420,6 +443,17 @@ UX_STEP_CB( &C_icon_crossmark, "Reject", }); +#ifdef HAVE_QTUM_SUPPORT +UX_STEP_CB( + ux_finalize_flow_7_step, + pbb, + io_seproxyhal_touch_verify_ok(NULL), + { + &C_icon_validate_14, + "Sign", + "OP_SENDER", + }); +#endif // finalize: confirm transaction / Fees: feesAmount UX_FLOW(ux_finalize_flow, &ux_finalize_flow_1_step, @@ -428,6 +462,16 @@ UX_FLOW(ux_finalize_flow, &ux_finalize_flow_6_step ); +#ifdef HAVE_QTUM_SUPPORT +// finalize: sign output sender transaction / Fees: feesAmount +UX_FLOW(ux_finalize_sender_flow, + &ux_finalize_flow_1_step, + &ux_finalize_flow_4_step, + &ux_finalize_flow_7_step, + &ux_finalize_flow_6_step +); +#endif + ////////////////////////////////////////////////////////////////////// UX_STEP_NOCB( ux_display_public_flow_1_step, @@ -841,21 +885,124 @@ uint8_t prepare_fees() { #define MAIDSAFE_ASSETID 3 #define USDT_ASSETID 31 +#ifdef HAVE_QTUM_SUPPORT +#define DELEGATIONS_ADDRESS "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x86" +#define ADD_DELEGATION_HASH "\x4c\x0e\x96\x8c" +#define REMOVE_DELEGATION_HASH "\x3d\x66\x6e\x8b" +#endif + void get_address_from_output_script(unsigned char* script, int script_size, char* out, int out_size) { if (btchip_output_script_is_op_return(script)) { strcpy(out, "OP_RETURN"); return; } + #ifdef HAVE_QTUM_SUPPORT + unsigned char isOpSender = 0; + if(G_coin_config->kind == COIN_KIND_QTUM) { + isOpSender = btchip_output_script_is_op_sender(script, script_size); + } if ((G_coin_config->kind == COIN_KIND_QTUM || G_coin_config->kind == COIN_KIND_HYDRA) && btchip_output_script_is_op_create(script, script_size)) { - strcpy(out, "OP_CREATE"); + if(btchip_context_D.signOpSender && isOpSender) + { + unsigned char* senderOutput = script - 8; + btchip_hash_sender_start(senderOutput); + } + strcpy(out, isOpSender ? "OP_SENDER_CREATE" : "OP_CREATE"); return; } if ((G_coin_config->kind == COIN_KIND_QTUM || G_coin_config->kind == COIN_KIND_HYDRA) && btchip_output_script_is_op_call(script, script_size)) { - strcpy(out, "OP_CALL"); + if(btchip_context_D.signOpSender && isOpSender) + { + unsigned char* senderOutput = script - 8; + btchip_hash_sender_start(senderOutput); + } + unsigned char contractaddress[20]; + int i; + int pos = 1; + int outputsize; + if (script[0] == 0xfd) { + outputsize = script[2] * 0x100 + + script[1] + 3; + } else { + outputsize = script[0] + 1; + } + for (i = 0; i < sizeof(contractaddress); i++) { + contractaddress[i] = script[outputsize - 21 + i]; + } + if (strncmp(contractaddress, DELEGATIONS_ADDRESS, + sizeof(contractaddress)) == 0) { + unsigned char functionhash[4]; + if (script[0] == 0xfd) + pos += 2; // varint>255? + if (!isOpSender) { + pos += script[pos]; // version + pos += script[pos] + 1; // gas limit + pos += script[pos] + 1; // gas price + } else { + pos += script[pos]; // address version + pos += script[pos]; // address + pos += script[pos] + 1; // gas price + if (script[pos] == 0x4c) { // check for OP_PUSHDATA1 + pos += script[pos + 1] + 2; + } else if (script[pos] == 0x00) { + pos += 1; + } + pos += 1; // OP_SENDER + pos += script[pos] + 1; // // version + pos += script[pos] + 1; // gas limit + pos += script[pos] + 1; // gas price + } + if (script[pos] == 0x4c) + pos++; // check for OP_PUSHDATA1 + + for (i = 0; i < sizeof(functionhash); i++) { + functionhash[i] = script[pos + 1 + i]; + } + if (strncmp(functionhash, ADD_DELEGATION_HASH, sizeof(functionhash)) + == 0) { + unsigned char stakeraddress[21]; + char stakerbase58[80]; + unsigned short stakerbase58size; + unsigned char delegationfee; + stakeraddress[0] = G_coin_config->p2pkh_version; + + for (i = 0; i < sizeof(stakeraddress); i++) { + stakeraddress[i + 1] = script[pos + + 17 + i]; + } + stakerbase58size = btchip_public_key_to_encoded_base58( + stakeraddress, sizeof(stakeraddress), + (unsigned char*) stakerbase58, sizeof(stakerbase58), + G_coin_config->p2pkh_version, 1); + stakerbase58[stakerbase58size] = '\0'; + + delegationfee = script[pos + 17 + 20 + + 31]; + snprintf(out, sizeof(vars.tmp.fullAddress), + "Delegate to %s (fee %d %%)", stakerbase58, + delegationfee); + } else if (strncmp(functionhash, REMOVE_DELEGATION_HASH, + sizeof(functionhash)) == 0) { + strcpy(out, "Undelegate"); + } + } else { + unsigned char contractaddressstring[41]; + const char *hex = "0123456789ABCDEF"; + for (i = 0; i < sizeof(contractaddressstring); i = i + 2) { + contractaddressstring[i] = hex[(contractaddress[i / 2] >> 4) + & 0xF]; + contractaddressstring[i + 1] = + hex[contractaddress[i / 2] & 0xF]; + } + contractaddressstring[40] = '\0'; + snprintf(out, sizeof(vars.tmp.fullAddress), + "Call contract %s", contractaddressstring); + } return; } + #endif if (btchip_output_script_is_native_witness(script)) { if (G_coin_config->native_segwit_prefix) { segwit_addr_encode( @@ -1082,7 +1229,17 @@ unsigned int btchip_bagl_finalize_tx() { return 0; } + #ifdef HAVE_QTUM_SUPPORT + if(btchip_context_D.signOpSender) { + ux_flow_init(0, ux_finalize_sender_flow, NULL); + } + else { + ux_flow_init(0, ux_finalize_flow, NULL); + } + #else ux_flow_init(0, ux_finalize_flow, NULL); + #endif + return 1; } From 5b25be8ab43caf8cb140ffa8a5429e6cd6ead853 Mon Sep 17 00:00:00 2001 From: timemarkovqtum Date: Fri, 19 Nov 2021 19:02:02 +0100 Subject: [PATCH 2/2] Update qtum icons (#4) --- icons/blue_app_qtum.gif | Bin 590 -> 607 bytes icons/nanos_app_qtum.gif | Bin 1128 -> 68 bytes icons/nanox_app_qtum.gif | Bin 1119 -> 65 bytes icons/qtum.png | Bin 3361 -> 4990 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/blue_app_qtum.gif b/icons/blue_app_qtum.gif index 4c0194c377b31d9b99b157bc0c26ec6365216fca..c4f61bb69052bc2106d1b8972685ac13d22c22fc 100644 GIT binary patch literal 607 zcmV-l0-*gzNk%v~VKM+R0OJq<$k+e2$^K)E{X=#9F=qS{RQmx;`ttewrM&-9e*BWI z{wiPld!7F6^8f$;{{SlaEC2ui05SkF000F3(8)<15Uae+yZ6PQ099!^NAj&KTL{N# z4sC6BiaZ6z?c0RdDex=WNF{)A*%DHE%xH&DQAw{G#^iwQ7A6AeKohzKB$^)qp`j|z zghv4pFAh*f1WH4HtRDt32uluwH)}m42WcDyF-i}8J^(Z~dRz}k2Mm!ykTIM-k}Xg% zm`F;f8>SmjH3^I?cn_>{j0I`6F|I5NH@YYb1jE9_z?e04Ho!WVwLmqqwG03O0oB$3 z0HL)cHM}3kKH{X`z`n`SDBvFK?Vt~EKn_Hg?j84$M0Z5;>HQ991TeHBUyKI}EW~r5 zj0F{dzGfBtwxK`|e=QyYN|O&zjWqrQG+=Z{Pfnd!9}GG`sBq*8jb|bvOW8sJCw>bX zzASl$1_K4h!4iq;AkoYZ zC<`)^aY{nR4LVm?rG+9SF1rN?DB6JJZAcpm>0%m2X4tA(NYZu~;B=Wfg_;hxUDQH= z6&qTdp%|!-++G$8#@d)8nH@O>VOShEYvWQD8Vk4;k_;#{S_yGm6j0CuCPP397{yqS zz`^AX#T|rVvH(hB59p3}aH6=&kAxGJTVLWtLZ$6Ra+Jsoe#IRV2gsM_a9)P=^&G_O tu%N!nfbR2AB&ctMfCBvaQ5JAO0170)*f0ntsNezvB!B<_0G1E|06Trf`l0{; literal 590 zcmV-U0hz+~>V(7NU$Wdsr`I~5)bjcKz2EVc&FHV# z?Q^@|%jWd@`T76<|1Fx)A^8LV00000EC2ui05SkF000F4u*peV5R1IcyZ6K*1XXD^ zz)*Z_%N7H0mZ@vsT|7&Hf&VuH>7)P}Zwgc3$aD=S0AF+3F~}wZs+Kc=ZoiLf7Od?y zR%B|y(o8tsa5zGGyc`RGxc&{$95F@%2Mz}cgi>w?0B>VJ89xSw4h$=KMFejP1_*Bq zbT@vWiw+4Km<|hz2wZ~)p8(u54FeH6 zK(hfMf2QKXj- z&>6zFCF(^710W8PnE)a}0E2o>$pf-3K{97crqq*CB{?z$2BGbs3>D5Cf(L`y)X9fB z2ly@?aBPf$B#tcPwh*Lmo0L2hN0oXYaZZa6MhMV-);40CK8U@2;vnLPfpH*BdkB2@ z!-yse5(pQNa_8;eZg!~8qJCT<7Pyhe` diff --git a/icons/nanos_app_qtum.gif b/icons/nanos_app_qtum.gif index 7e2c908c38d2251c01112da4a3a05c466bb8efe3..62782dcb9b482256ef7247ececa6384b920423a5 100644 GIT binary patch literal 68 zcmZ?wbh9u|6krfwXkcUjg8%>j>wsvG2m_OHPyfo(Zy916L`f8s?i%Y^#Xk SW?PlrYCBQ$>b77825SIrg%p+m literal 1128 zcmZ?wbhEHb6krfwXkY+=|Ns9h{^ySH4N!1NEJ*~?Hue<-iOJciB??KY>6v-9O7C~? zS5nAKu~iB;^)>JRtPXT0NVp4u-iLDaQ zr4TRV7Ql_oE7k*hM=v=)SHB{$K;KZ$0OTc@LSJ9}N^^7Js*6j4QW5UOYH)E#WkITb zP-=00X;E@2P`NV5ssbzLqSVBa{GyQj{2W*)24v)y_?lo1 zzP?tTdBr7(dC94sF1AWQBlI#eQ>@$^&7DjPjLe*zoy-jlUCk{Gom|}voh*%=9W5;^ zjg4V?UGkGlb5rw5V0u#!dd+d_1*L`D0-((Enm@@7}(7{p#h5=g*!#dHm?%gZuaH-no72=8fyuu3ou(>Eea+=gyuved^?i(;JWy=vu(<;#{XS-fcBg8B32&Y3-H=8Wmn zrcRkWY2t+bzTTehuFj73w$_&BrpAW)y4srRs>+J;veJ^`qQZjwyxg4Ztjvt`wA7U3 zq{M{yxY(HJsK|)$u+Wg;puhlsKVKhjFHaA5H&+*DCr1Z+J6juTD@zM=GgA{|BSQmy zJzX7bElmw|HB}X5B}D~!IawKLDM<-&F;Nj=AwdCtK3*PfE=~@1HdYp9CPoGZ#h)zT zazY10f{G0WCZ(SKm8Z`#EbRW`De!2QW%ANQDUC9r$Ucp)xo=)dTBRz;GCL~=}}db8eHWUl3bOYY?-2DZ>L~WVFlEiTau_y zk(-lOY*k^al$esAlxhWJ=cbk<>MJRLRg`4gDmj8A6e4^B6rA&mQWZ?~%yg3tO$-#w zE%gk|3`|T7brg&Y49)coO!W=SbPWux3@ogS3>Bb2$vOKg>t6#hd!kXryZHLq9?>>0h}{9OHt!~%UoJp+)3AU@44@b$&7G&dKl z8WTK@=NlI zGx7@*oShXkd=ry1^FVw}pa!6utUU9IOA_;vQ$1a5m4GJbWoD*WxjC9UnHU(EIXOF- z8ydQrTN*mKx*0lI8aq2$T38wzgZ&TE?2?~cnwy$e0@Iv=&}@#O8LAhQ3_zg)4BMj2 zveXo}qWoM1u$Qeeal6F?Y#cVHK(wY{cL~rJrWg)^`9dG)6n(Tv4v1M{1x&BN zlnG?RQ+#S(itT@;B4C1M{QvLopWnZJ{`mgw>zB`;K7M%r?(Lh`uU@`*{_N?K$B!O9 zxPR~No!hr=-nf44>XplvE?zi)?(CVz2)% zHf~tIZta@Yt5&X9zHI4|MGF_qpEq~T>{&BsOrJJ&%H&BCC-nFA_H=i3cC@#(wlp_2 zHq_VE)>KzjR+N{OmJ}Bi7UbvU=45AOW~8U3rX(jNCd9|Z#zaR&Mudljh6D!%2Kf8= z`gnVJdbqo}x;Q&II@sIU+E`mzT9})eniv}y8tCik>S$|eYN)HJswgWdD#**p%1BE| zN{EYziU(9^%N`@y++A^Hs5x9?h9 YohvIKE+t=98RWV)^~e#UAO;3&04EHMWB>pF diff --git a/icons/qtum.png b/icons/qtum.png index 0f154f9bf9a9cc12e57fc8cfbaa9127dc887b243..242128c3640aba8d253d34e9615dbc89ef09e90a 100644 GIT binary patch delta 4980 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 oWJXF9D8yxn@hm$$6c