From d72b0fff959398aaebf82f215e77e70180cc9b21 Mon Sep 17 00:00:00 2001 From: ZmnSCPxj jxPCSnmZ Date: Wed, 22 Jul 2020 17:49:56 +0800 Subject: [PATCH] onchaind/onchaind.c: Implement scorch-the-earth. Fixes: #3832 Changelog-Changed: onchaind: We now scorch the earth on theft attempts, RBFing up our penalty transaction as blocks arrive without a penalty transaction getting confirmed. --- onchaind/onchaind.c | 345 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 339 insertions(+), 6 deletions(-) diff --git a/onchaind/onchaind.c b/onchaind/onchaind.c index 29b03f977af0..de9edac81e3c 100644 --- a/onchaind/onchaind.c +++ b/onchaind/onchaind.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -693,6 +694,224 @@ static struct bitcoin_tx *tx_to_us(const tal_t *ctx, return tx; } +/** replace_penalty_tx_to_us + * + * @brief creates a replacement TX for + * a given penalty tx. + * + * @param ctx - the context to allocate + * off of. + * @param hsm_sign_msg - function to construct + * the signing message to HSM. + * @param penalty_tx - the original + * penalty transaction. + * @param output_amount - the output + * amount to use instead of the + * original penalty transaction. + * If this amount is below the dust + * limit, the output is replaced with + * an `OP_RETURN` instead. + * + * @return the signed transaction. + */ +static struct bitcoin_tx * +replace_penalty_tx_to_us(const tal_t *ctx, + u8 *(*hsm_sign_msg)(const tal_t *ctx, + struct bitcoin_tx *tx, + const u8 *wscript), + const struct bitcoin_tx *penalty_tx, + struct amount_sat output_amount) +{ + struct bitcoin_tx *tx; + + /* The penalty tx input. */ + const struct wally_tx_input *input; + /* Specs of the penalty tx input. */ + struct bitcoin_txid input_txid; + u32 input_outnum; + u8 *input_wscript; + u8 *input_element; + struct amount_sat input_amount; + + /* Signature from the HSM. */ + u8 *msg; + struct bitcoin_signature sig; + /* Witness we generate from the signature and other data. */ + u8 **witness; + + + /* Get the single input of the penalty tx. */ + input = &penalty_tx->wtx->inputs[0]; + /* Extract the input-side data. */ + bitcoin_tx_input_get_txid(penalty_tx, 0, &input_txid); + input_outnum = input->index; + input_wscript = tal_dup_arr(tmpctx, u8, + input->witness->items[2].witness, + input->witness->items[2].witness_len, + 0); + input_element = tal_dup_arr(tmpctx, u8, + input->witness->items[1].witness, + input->witness->items[1].witness_len, + 0); + input_amount = psbt_input_get_amount(penalty_tx->psbt, 0); + + /* Create the replacement. */ + tx = bitcoin_tx(ctx, chainparams, 1, 1, /*locktime*/ 0); + /* Reconstruct the input. */ + bitcoin_tx_add_input(tx, &input_txid, input_outnum, + BITCOIN_TX_RBF_SEQUENCE, + NULL, input_amount, NULL, input_wscript); + /* Reconstruct the output with a smaller amount. */ + if (amount_sat_greater(output_amount, dust_limit)) + bitcoin_tx_add_output(tx, + scriptpubkey_p2wpkh(tx, + &our_wallet_pubkey), + NULL, + output_amount); + else + bitcoin_tx_add_output(tx, + scriptpubkey_opreturn(tx), + NULL, + AMOUNT_SAT(0)); + + /* Finalize the transaction. */ + bitcoin_tx_finalize(tx); + + /* Ask HSM to sign it. */ + if (!wire_sync_write(HSM_FD, take(hsm_sign_msg(NULL, tx, + input_wscript)))) + status_failed(STATUS_FAIL_HSM_IO, "While feebumping penalty: writing sign request to hsm"); + msg = wire_sync_read(tmpctx, HSM_FD); + if (!msg || !fromwire_hsm_sign_tx_reply(msg, &sig)) + status_failed(STATUS_FAIL_HSM_IO, "While feebumping penalty: reading sign_tx_reply: %s", tal_hex(tmpctx, msg)); + + /* Install the witness with the signature. */ + witness = bitcoin_witness_sig_and_element(tx, &sig, + input_element, + tal_bytelen(input_element), + input_wscript); + bitcoin_tx_input_set_witness(tx, 0, take(witness)); + + return tx; +} + +/** min_rbf_bump + * + * @brief computes the minimum RBF bump required by + * BIP125, given an index. + * + * @desc BIP125 requires that an replacement transaction + * pay, not just the fee of the evicted transactions, + * but also the minimum relay fee for itself. + * This function assumes that previous RBF attempts + * paid exactly the return value for that attempt, on + * top of the initial transaction fee. + * It can serve as a baseline for other functions that + * compute a suggested fee: get whichever is higher, + * the fee this function suggests, or your own unique + * function. + * + * This function is provided as a common function that + * all RBF-bump computations can use. + * + * @param weight - the weight of the transaction you + * are RBFing. + * @param index - 0 makes no sense, 1 means this is + * the first RBF attempt, 2 means this is the 2nd + * RBF attempt, etc. + * + * @return the suggested total fee. + */ +static struct amount_sat min_rbf_bump(size_t weight, + size_t index) +{ + struct amount_sat min_relay_fee; + struct amount_sat min_rbf_bump; + + /* Compute the minimum relay fee for a transaction of the given + * weight. */ + min_relay_fee = amount_tx_fee(min_relay_feerate, weight); + + /* For every RBF attempt, we add the min-relay-fee. + * Or in other words, we multiply the min-relay-fee by the + * index number of the attempt. + */ + if (mul_overflows_u64(index, min_relay_fee.satoshis)) /* Raw: multiplication. */ + min_rbf_bump = AMOUNT_SAT(UINT64_MAX); + else + min_rbf_bump.satoshis = index * min_relay_fee.satoshis; /* Raw: multiplication. */ + + return min_rbf_bump; +} + +/** compute_penalty_output_amount + * + * @brief computes the appropriate output amount for a + * penalty transaction that spends a theft transaction + * that is already of a specific depth. + * + * @param initial_amount - the outout amount of the first + * penalty transaction. + * @param depth - the current depth of the theft + * transaction. + * @param max_depth - the maximum depth of the theft + * transaction, after which the theft transaction will + * succeed. + * @param weight - the weight of the first penalty + * transaction, in Sipa. + */ +static struct amount_sat +compute_penalty_output_amount(struct amount_sat initial_amount, + u32 depth, u32 max_depth, + size_t weight) +{ + struct amount_sat max_output_amount; + struct amount_sat output_amount; + struct amount_sat deducted_amount; + + assert(depth <= max_depth); + assert(depth > 0); + + /* The difference between initial_amount, and the fee suggested + * by min_rbf_bump, is the largest allowed output amount. + * + * depth = 1 is the first attempt, thus maps to the 0th RBF + * (i.e. the initial attempt that is not RBFed itself). + * We actually start to replace at depth = 2, so we use + * depth - 1 as the index for min_rbf_bump. + */ + if (!amount_sat_sub(&max_output_amount, + initial_amount, min_rbf_bump(weight, depth - 1))) + /* If min_rbf_bump is larger than the initial_amount, + * we should just donate the whole output as fee, + * meaning we get 0 output amount. + */ + return AMOUNT_SAT(0); + + /* Map the depth / max_depth into a number between 0->1. */ + double x = (double) depth / (double) max_depth; + /* Get the cube of the above position, resulting in a graph + * where the y is close to 0 up to less than halfway through, + * then quickly rises up to 1 as depth nears the max depth. + */ + double y = x * x * x; + /* Scale according to the initial_amount. */ + deducted_amount.satoshis = (u64) (y * initial_amount.satoshis); /* Raw: multiplication. */ + + /* output_amount = initial_amount - deducted_amount. */ + if (!amount_sat_sub(&output_amount, + initial_amount, deducted_amount)) + /* If underflow, force to 0. */ + output_amount = AMOUNT_SAT(0); + + /* If output exceeds max, return max. */ + if (amount_sat_less(max_output_amount, output_amount)) + return max_output_amount; + + return output_amount; +} + + static void hsm_sign_local_htlc_tx(struct bitcoin_tx *tx, const u8 *wscript, struct bitcoin_signature *sig) @@ -822,8 +1041,110 @@ static enum wallet_tx_type onchain_txtype_to_wallet_txtype(enum tx_type t) abort(); } +/** proposal_is_rbfable + * + * @brief returns true if the given proposal + * would be RBFed if the output it is tracking + * increases in depth without being spent. + */ +static bool proposal_is_rbfable(const struct proposed_resolution *proposal) +{ + /* Future onchain resolutions, such as anchored commitments, might + * want to RBF as well. + */ + return proposal->tx_type == OUR_PENALTY_TX; +} + +/** proposal_should_rbf + * + * @brief the given output just increased its depth, + * so the proposal for it should be RBFed and + * rebroadcast. + * + * @desc precondition: the given output must have an + * rbfable proposal as per `proposal_is_rbfable`. + */ +static void proposal_should_rbf(struct tracked_output *out, bool is_replay) +{ + struct bitcoin_tx *tx = NULL; + u32 depth; + + assert(out->proposal); + assert(proposal_is_rbfable(out->proposal)); + + depth = out->depth; + + /* Do not RBF at depth 1. + * + * Since we react to *onchain* events, whatever proposal we made, + * the output for that proposal is already at depth 1. + * + * Since our initial proposal was broadcasted with the output at + * depth 1, we should not RBF until a new block arrives, which is + * at depth 2. + */ + if (depth <= 1) + return; + + if (out->proposal->tx_type == OUR_PENALTY_TX) { + u32 max_depth = to_self_delay[REMOTE]; + u32 my_depth = depth; + size_t weight = bitcoin_tx_weight(out->proposal->tx); + struct amount_sat initial_amount; + struct amount_sat new_amount; + + if (max_depth >= 1) + max_depth -= 1; + if (my_depth >= max_depth) + my_depth = max_depth; + + bitcoin_tx_output_get_amount_sat(out->proposal->tx, 0, + &initial_amount); + + /* Compute the new output amount for the RBF. */ + new_amount = compute_penalty_output_amount(initial_amount, + my_depth, max_depth, + weight); + assert(amount_sat_less_eq(new_amount, initial_amount)); + /* Recreate the penalty tx. */ + tx = replace_penalty_tx_to_us(tmpctx, + &penalty_to_us, + out->proposal->tx, new_amount); + + status_debug("Created RBF OUR_PENALTY_TX with output %s " + "(originally %s) for depth %"PRIu32"/%"PRIu32".", + type_to_string(tmpctx, struct amount_sat, + &new_amount), + type_to_string(tmpctx, struct amount_sat, + &initial_amount), + depth, to_self_delay[LOCAL]); + } + /* Add other RBF-able proposals here. */ + + /* Broadcast the transaction. */ + if (tx) { + enum wallet_tx_type wtt; + + status_debug("Broadcasting RBF %s (%s) to resolve %s/%s " + "depth=%"PRIu32"", + tx_type_name(out->proposal->tx_type), + type_to_string(tmpctx, struct bitcoin_tx, tx), + tx_type_name(out->tx_type), + output_type_name(out->output_type), + depth); + + wtt = onchain_txtype_to_wallet_txtype(out->proposal->tx_type); + wire_sync_write(REQ_FD, + take(towire_onchain_broadcast_tx(NULL, tx, + wtt, + true))); + } +} + static void proposal_meets_depth(struct tracked_output *out, bool is_replay) { + bool is_rbf = false; + /* If we simply wanted to ignore it after some depth */ if (!out->proposal->tx) { ignore_output(out); @@ -836,12 +1157,16 @@ static void proposal_meets_depth(struct tracked_output *out, bool is_replay) tx_type_name(out->tx_type), output_type_name(out->output_type)); + if (out->proposal) + /* Our own penalty transactions are going to be RBFed. */ + is_rbf = proposal_is_rbfable(out->proposal); + wire_sync_write( REQ_FD, take(towire_onchain_broadcast_tx( NULL, out->proposal->tx, onchain_txtype_to_wallet_txtype(out->proposal->tx_type), - false))); + is_rbf))); /* Don't wait for this if we're ignoring the tiny payment. */ if (out->proposal->tx_type == IGNORING_TINY_PAYMENT) { @@ -1359,7 +1684,7 @@ static void steal_htlc_tx(struct tracked_output *out, * ` 1` */ tx = tx_to_us(htlc_out, penalty_to_us, htlc_out, - 0xFFFFFFFF, 0, + BITCOIN_TX_RBF_SEQUENCE, 0, &ONE, sizeof(ONE), htlc_out->wscript, &tx_type, penalty_feerate); @@ -1614,6 +1939,13 @@ static void tx_new_depth(struct tracked_output **outs, && depth >= outs[i]->proposal->depth_required) { proposal_meets_depth(outs[i], is_replay); } + + /* Otherwise, is this an output whose proposed resolution + * we should RBF? */ + if (outs[i]->proposal + && bitcoin_txid_eq(&outs[i]->txid, txid) + && proposal_is_rbfable(outs[i]->proposal)) + proposal_should_rbf(outs[i], is_replay); } } @@ -2500,8 +2832,8 @@ static void steal_to_them_output(struct tracked_output *out, bool is_replay) &keyset->self_revocation_key, &keyset->self_delayed_payment_key); - tx = tx_to_us(tmpctx, penalty_to_us, out, 0xFFFFFFFF, 0, &ONE, - sizeof(ONE), wscript, &tx_type, penalty_feerate); + tx = tx_to_us(tmpctx, penalty_to_us, out, BITCOIN_TX_RBF_SEQUENCE, 0, + &ONE, sizeof(ONE), wscript, &tx_type, penalty_feerate); propose_resolution(out, tx, 0, tx_type, is_replay); } @@ -2520,8 +2852,9 @@ static void steal_htlc(struct tracked_output *out, bool is_replay) * */ pubkey_to_der(der, &keyset->self_revocation_key); - tx = tx_to_us(out, penalty_to_us, out, 0xFFFFFFFF, 0, der, sizeof(der), - out->wscript, &tx_type, penalty_feerate); + tx = tx_to_us(out, penalty_to_us, out, BITCOIN_TX_RBF_SEQUENCE, 0, + der, sizeof(der), out->wscript, &tx_type, + penalty_feerate); propose_resolution(out, tx, 0, tx_type, is_replay); }