From 2760322eab066701bee8161c9a82d4552d9a2b0a Mon Sep 17 00:00:00 2001 From: Andrew Kozlik Date: Tue, 27 Dec 2022 23:48:10 +0100 Subject: [PATCH] feat(legacy): Improve signing progress. --- legacy/firmware/signing.c | 238 ++++++++++++++++++++++++++++++-------- 1 file changed, 190 insertions(+), 48 deletions(-) diff --git a/legacy/firmware/signing.c b/legacy/firmware/signing.c index d33197cf9..a2624c229 100644 --- a/legacy/firmware/signing.c +++ b/legacy/firmware/signing.c @@ -34,6 +34,10 @@ #ifdef USE_SECP256K1_ZKP_ECDSA #include "zkp_ecdsa.h" #endif +#if DEBUG_LINK +#include +#include +#endif static uint32_t change_count; static const CoinInfo *coin; @@ -102,13 +106,16 @@ static uint8_t decred_hash_prefix[32]; static uint64_t total_in, external_in, total_out, change_out; static uint64_t orig_total_in, orig_external_in, orig_total_out, orig_change_out; -static uint32_t progress, progress_step, progress_meta_step; +static uint32_t progress_step, progress_steps, progress_substep, + progress_substeps, progress_midpoint, progress_update; +static const char *progress_label; static uint32_t tx_weight, tx_base_weight, our_weight, our_inputs_len; PathSchema unlocked_schema; typedef struct { uint32_t inputs_count; uint32_t outputs_count; + uint32_t segwit_count; uint32_t next_legacy_input; uint32_t min_sequence; bool multisig_fp_set; @@ -370,6 +377,135 @@ static bool is_external_input(uint32_t i) { return external_inputs[i / 32] & (1 << (i % 32)); } +static void report_progress(bool force) { + static uint32_t update_ctr = 0; + if (!force && update_ctr < progress_update) { + update_ctr++; + return; + } + + uint32_t progress = 0; + if (progress_midpoint != 0) { + if (progress_step < progress_midpoint) { + // Checking previous transactions. + progress = + (500 * progress_step + 500 * progress_substep / progress_substeps) / + progress_midpoint; + } else { + // Signing transaction after checking. No substeps. + progress = 500 + 500 * (progress_step - progress_midpoint) / + (progress_steps - progress_midpoint); + } + } else { + // Loading transaction or signing transaction without checking. No substeps. + progress = 1000 * progress_step / progress_steps; + } + + layoutProgress(progress_label, progress); + update_ctr = 0; +} + +#if DEBUG_LINK +static bool assert_progress_finished(void) { + char line[54] = {0}; + if (progress_step != progress_steps) { + snprintf(line, sizeof(line), "%s finished at %" PRIu32 "/%" PRIu32, + progress_label, progress_step, progress_steps); + } else if (progress_substep != 0) { + snprintf(line, sizeof(line), "%s finished at substep %" PRIu32, + progress_label, progress_substep); + } else { + return true; + } + fsm_sendFailure(FailureType_Failure_FirmwareError, line); + signing_abort(); + return false; +} +#endif + +static void init_loading_progress(void) { + progress_label = _("Loading transaction"); + progress_step = 0; + progress_substep = 0; + progress_substeps = 1; // no substeps in tx loading + progress_midpoint = 0; // no midpoint in tx loading + progress_update = 10; // update screen every 10 steps + + // Stage 1 and 2 - load inputs and outputs + progress_steps = info.inputs_count + info.outputs_count; + + report_progress(true); +} + +static void init_signing_progress(void) { + progress_label = _("Signing transaction"); + progress_step = 0; + progress_steps = 0; + progress_update = 20; // update screen every 20 steps + + // Verify previous transactions (STAGE_REQUEST_3_PREV_*). + // We don't know how long it will take to fetch the previous transactions. If + // they need to be checked, then we will reserve 50 % of the progress for + // this. Once we fetch a prev_tx's metadata, we subdivide the reserved space + // into substeps which represent the progress of fetching one prev_tx input or + // output. + if (!taproot_only && !(coin->overwintered && info.version == 5)) { + progress_steps += info.inputs_count; + progress_midpoint = progress_steps; + } + + uint32_t external_count = 0; + for (size_t i = 0; i < info.inputs_count; ++i) { + external_count += is_external_input(i); + } + + // Process inputs. + if (!(coin->force_bip143 || coin->overwintered || coin->decred)) { + // Sign and optionally serialize legacy inputs (STAGE_REQUEST_4_*). + progress_steps += (info.inputs_count - info.segwit_count - external_count) * + (info.inputs_count + info.outputs_count); + + if (serialize) { + // Serialize non-legacy inputs (STAGE_REQUEST_NONLEGACY_INPUT). + progress_steps += info.segwit_count + external_count; + } + + if (is_replacement) { + // Verify original input signatures (STAGE_REQUEST_3_ORIG_*). + progress_steps += + (orig_info.inputs_count - orig_info.segwit_count - external_count) * + (orig_info.inputs_count + orig_info.outputs_count); + progress_steps += orig_info.segwit_count; + } + } else { + // Sign and optionally serialize each input (STAGE_REQUEST_NONLEGACY_INPUT, + // or in case of Decred STAGE_REQUEST_DECRED_WITNESS). + progress_steps += info.inputs_count; + if (is_replacement) { + // Verify each original input (STAGE_REQUEST_3_ORIG_NONLEGACY_INPUT). + progress_steps += orig_info.inputs_count; + } + } + + if (to.is_segwit) { + if (serialize) { + // Serialize witnesses for all inputs (STAGE_REQUEST_SEGWIT_WITNESS). + progress_steps += info.inputs_count; + } else { + // Process witnesses for all internal inputs + // (STAGE_REQUEST_SEGWIT_WITNESS). + progress_steps += info.inputs_count - external_count; + } + } + + // Serialize outputs (STAGE_REQUEST_5_OUTPUT). + if (serialize && !coin->decred) { + progress_steps += info.outputs_count; + } + + report_progress(true); +} + void send_req_1_input(void) { signing_stage = STAGE_REQUEST_1_INPUT; resp.has_request_type = true; @@ -616,6 +752,11 @@ void send_req_5_output(void) { } void send_req_finished(void) { +#if DEBUG_LINK + if (!assert_progress_finished()) { + return; + } +#endif resp.has_request_type = true; resp.request_type = RequestType_TXFINISHED; msg_write(MessageType_MessageType_TxRequest, &resp); @@ -1056,6 +1197,7 @@ static bool tx_info_init(TxInfo *tx_info, uint32_t inputs_count, tx_info->inputs_count = inputs_count; tx_info->outputs_count = outputs_count; + tx_info->segwit_count = 0; tx_info->next_legacy_input = 0xffffffff; tx_info->min_sequence = SEQUENCE_FINAL; tx_info->multisig_fp_set = false; @@ -1256,11 +1398,6 @@ void signing_init(const SignTx *msg, const CoinInfo *_coin, const HDNode *_root, is_replacement = false; unlocked_schema = unlock; signing = true; - progress = 0; - // we step by 500/inputs_count per input in phase1 and phase2 - // this means 50 % per phase. - progress_step = (500 << PROGRESS_PRECISION) / info.inputs_count; - is_coinjoin = (authorization != NULL) ? sectrue : secfalse; if (is_coinjoin == sectrue) { if (!init_coinjoin(msg, authorization)) { @@ -1292,7 +1429,7 @@ void signing_init(const SignTx *msg, const CoinInfo *_coin, const HDNode *_root, hasher_Init(&hasher_check, HASHER_SHA2); - layoutProgressSwipe(_("Signing transaction"), 0); + init_loading_progress(); send_req_1_input(); } @@ -1511,6 +1648,10 @@ static bool tx_info_add_input(TxInfo *tx_info, const TxInputType *txinput) { extract_input_bip32_path(tx_info, txinput); } + if (is_segwit_input_script_type(txinput->script_type)) { + tx_info->segwit_count++; + } + // Remember the minimum nSequence value. if (txinput->sequence < tx_info->min_sequence) { tx_info->min_sequence = txinput->sequence; @@ -1689,6 +1830,9 @@ static bool coinjoin_add_input(TxInputType *txi) { return false; } } + + // Since this took a longer time, update progress. + report_progress(true); return true; } @@ -1748,6 +1892,10 @@ static bool signing_check_prevtx_hash(void) { return false; } + // prevtx is checked + progress_step++; + progress_substep = 0; + if (idx1 < info.inputs_count - 1) { idx1++; send_req_3_input(); @@ -1756,10 +1904,6 @@ static bool signing_check_prevtx_hash(void) { return false; } - // Everything was checked, now phase 2 begins and the transaction is signed. - progress_meta_step = - progress_step / (info.inputs_count + info.outputs_count); - layoutProgress(_("Signing transaction"), progress); idx1 = 0; #if !BITCOIN_ONLY if (coin->decred) { @@ -1851,7 +1995,7 @@ static bool signing_add_output(TxOutputType *txoutput) { int co = compile_output(coin, amount_unit, &root, txoutput, &bin_output, !skip_confirm); if (!skip_confirm) { - layoutProgress(_("Signing transaction"), progress); + report_progress(true); } if (co < 0) { fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); @@ -2773,6 +2917,14 @@ static void phase1_finish(void) { return; } +#if DEBUG_LINK + if (!assert_progress_finished()) { + return; + } +#endif + // Now phase 2 begins and the transaction is signed. + init_signing_progress(); + if (taproot_only) { // All internal inputs are Taproot. We do not need to verify that their // parameters match previous transactions. We can trust the amounts and @@ -3092,16 +3244,13 @@ void signing_txack(TransactionType *tx) { return; } - static int update_ctr = 0; - if (update_ctr++ == 20) { - layoutProgress(_("Signing transaction"), progress); - update_ctr = 0; - } + report_progress(false); memzero(&resp, sizeof(TxRequest)); switch (signing_stage) { case STAGE_REQUEST_1_INPUT: + progress_step++; if (!signing_validate_input(&tx->inputs[0]) || !signing_add_input(&tx->inputs[0])) { return; @@ -3271,6 +3420,7 @@ void signing_txack(TransactionType *tx) { phase1_request_next_input(); return; case STAGE_REQUEST_2_OUTPUT: + progress_step++; if (!signing_validate_output(&tx->outputs[0]) || !signing_add_output(&tx->outputs[0])) { return; @@ -3431,7 +3581,7 @@ void signing_txack(TransactionType *tx) { tp.is_decred = true; } #endif - progress_meta_step = progress_step / (tp.inputs_len + tp.outputs_len); + progress_substeps = tp.inputs_len + tp.outputs_len; idx2 = 0; if (tp.inputs_len > 0) { send_req_3_prev_input(); @@ -3444,8 +3594,7 @@ void signing_txack(TransactionType *tx) { if (!signing_validate_input(&tx->inputs[0])) { return; } - progress = (idx1 * progress_step + idx2 * progress_meta_step) >> - PROGRESS_PRECISION; + progress_substep++; if (!tx_serialize_input_hash(&tp, tx->inputs)) { fsm_sendFailure(FailureType_Failure_ProcessError, _("Failed to serialize input")); @@ -3464,9 +3613,7 @@ void signing_txack(TransactionType *tx) { if (!signing_validate_bin_output(&tx->bin_outputs[0])) { return; } - progress = (idx1 * progress_step + - (tp.inputs_len + idx2) * progress_meta_step) >> - PROGRESS_PRECISION; + progress_substep++; if (!tx_serialize_output_hash(&tp, tx->bin_outputs)) { fsm_sendFailure(FailureType_Failure_ProcessError, _("Failed to serialize output")); @@ -3500,7 +3647,7 @@ void signing_txack(TransactionType *tx) { #endif } if (idx2 < tp.outputs_len - 1) { - /* Check prevtx of next input */ + /* Check next output of prevtx */ idx2++; send_req_3_prev_output(); #if !BITCOIN_ONLY @@ -3541,6 +3688,7 @@ void signing_txack(TransactionType *tx) { if (!signing_validate_input(tx->inputs)) { return; } + progress_step++; // Add input to the outer transaction check. if (!tx_input_check_hash(&hasher_check, tx->inputs)) { @@ -3563,6 +3711,7 @@ void signing_txack(TransactionType *tx) { !signing_hash_orig_input(tx->inputs)) { return; } + progress_step++; idx2++; if (idx2 < orig_info.inputs_count) { @@ -3587,6 +3736,7 @@ void signing_txack(TransactionType *tx) { !signing_hash_orig_output(tx->outputs)) { return; } + progress_step++; idx2++; if (idx2 < orig_info.outputs_count) { @@ -3613,9 +3763,8 @@ void signing_txack(TransactionType *tx) { if (!signing_validate_input(&tx->inputs[0])) { return; } - progress = - 500 + ((signatures * progress_step + idx2 * progress_meta_step) >> - PROGRESS_PRECISION); + progress_step++; + if (idx2 == 0) { tx_init(&ti, info.inputs_count, info.outputs_count, info.version, info.lock_time, info.expiry, tx->branch_id, 0, @@ -3670,9 +3819,8 @@ void signing_txack(TransactionType *tx) { if (!signing_validate_output(&tx->outputs[0])) { return; } - progress = 500 + ((signatures * progress_step + - (info.inputs_count + idx2) * progress_meta_step) >> - PROGRESS_PRECISION); + progress_step++; + if (compile_output(coin, amount_unit, &root, tx->outputs, &bin_output, false) <= 0) { fsm_sendFailure(FailureType_Failure_ProcessError, @@ -3696,11 +3844,9 @@ void signing_txack(TransactionType *tx) { !signing_sign_legacy_input()) { return; } - // since this took a longer time, update progress signatures++; - progress = 500 + ((signatures * progress_step) >> PROGRESS_PRECISION); - layoutProgress(_("Signing transaction"), progress); - update_ctr = 0; + // since this took a longer time, update progress + report_progress(true); phase2_request_next_input(false); } return; @@ -3709,6 +3855,7 @@ void signing_txack(TransactionType *tx) { if (!signing_validate_input(&tx->inputs[0])) { return; } + progress_step++; if (is_external_input(idx1) != (tx->inputs[0].script_type == InputScriptType_EXTERNAL)) { @@ -3766,11 +3913,9 @@ void signing_txack(TransactionType *tx) { if (!signing_sign_ecdsa(&tx->inputs[0], node.private_key, node.public_key, hash)) return; - // since this took a longer time, update progress signatures++; - progress = 500 + ((signatures * progress_step) >> PROGRESS_PRECISION); - layoutProgress(_("Signing transaction"), progress); - update_ctr = 0; + // since this took a longer time, update progress + report_progress(true); } else if (tx->inputs[0].script_type == InputScriptType_SPENDP2SHWITNESS && !tx->inputs[0].has_multisig) { @@ -3824,6 +3969,8 @@ void signing_txack(TransactionType *tx) { if (!signing_validate_output(&tx->outputs[0])) { return; } + progress_step++; + if (compile_output(coin, amount_unit, &root, tx->outputs, &bin_output, false) <= 0) { fsm_sendFailure(FailureType_Failure_ProcessError, @@ -3846,9 +3993,8 @@ void signing_txack(TransactionType *tx) { return; } signatures++; - progress = 500 + ((signatures * progress_step) >> PROGRESS_PRECISION); - layoutProgress(_("Signing transaction"), progress); - update_ctr = 0; + progress_step++; + report_progress(true); phase2_request_next_witness(false); return; @@ -3858,9 +4004,6 @@ void signing_txack(TransactionType *tx) { if (!signing_validate_input(&tx->inputs[0])) { return; } - progress = - 500 + ((signatures * progress_step + idx2 * progress_meta_step) >> - PROGRESS_PRECISION); if (idx1 == 0) { // witness tx_init(&to, info.inputs_count, info.outputs_count, info.version, @@ -3902,11 +4045,10 @@ void signing_txack(TransactionType *tx) { if (!signing_sign_decred_input(&tx->inputs[0])) { return; } - // since this took a longer time, update progress signatures++; - progress = 500 + ((signatures * progress_step) >> PROGRESS_PRECISION); - layoutProgress(_("Signing transaction"), progress); - update_ctr = 0; + progress_step++; + // since this took a longer time, update progress + report_progress(true); if (idx1 < info.inputs_count - 1) { idx1++; send_req_decred_witness();