Skip to content

Commit

Permalink
onchaind/onchaind.c: Implement scorch-the-earth.
Browse files Browse the repository at this point in the history
Fixes: ElementsProject#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.
  • Loading branch information
ZmnSCPxj committed Jul 22, 2020
1 parent 63d9a68 commit f965b9f
Showing 1 changed file with 275 additions and 6 deletions.
281 changes: 275 additions & 6 deletions onchaind/onchaind.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <common/key_derive.h>
#include <common/keyset.h>
#include <common/memleak.h>
#include <common/overflows.h>
#include <common/peer_billboard.h>
#include <common/status.h>
#include <common/subdaemon.h>
Expand Down Expand Up @@ -673,6 +674,193 @@ 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;
}

/** 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)
{
assert(depth <= max_depth);

/*-------------------------------------------------------------------*/

struct amount_sat min_relay_fee;
struct amount_sat depth_times_min_relay_fee;
struct amount_sat max_output_amount;

/* Compute the minimum relay fee for a transaction of the given
* weight. */
min_relay_fee = amount_tx_fee(min_relay_feerate, weight);

/* For every block, we add the min-relay-fee.
* Or in other words, we multiply the min-relay-fee by the
* depth.
* This means that for each block / depth change, we bump up
* the fee by at least the min-relay-fee, which is needed by
* BIP125.
*/
if (mul_overflows_u64(depth, min_relay_fee.satoshis)) /* Raw: multiplication. */
depth_times_min_relay_fee = AMOUNT_SAT(UINT64_MAX);
else
depth_times_min_relay_fee.satoshis = depth * min_relay_fee.satoshis; /* Raw: multiplication. */

/* The difference of depth * min_relay_fee and initial_amount is
* the max allowable output amount for this depth.
*/
if (!amount_sat_sub(&max_output_amount,
initial_amount, depth_times_min_relay_fee))
/* If depth * min_relay_fee exceeds initial_amount,
* we should just donate the whole output as fee,
* meaning we get 0 output amount.
*/
return AMOUNT_SAT(0);

/*-------------------------------------------------------------------*/

struct amount_sat output_amount;
struct amount_sat deducted_amount;

/* 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 (amount_sat_less(max_output_amount, output_amount))
return max_output_amount;
else
return output_amount;
}


static void hsm_sign_local_htlc_tx(struct bitcoin_tx *tx,
const u8 *wscript,
struct bitcoin_signature *sig)
Expand Down Expand Up @@ -804,8 +992,77 @@ 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.
*/
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;

if (out->proposal->tx_type == OUR_PENALTY_TX) {
u32 max_depth = to_self_delay[LOCAL];
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 (depth >= max_depth)
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,
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);
}
/* Add other RBF-able proposals here. */

/* Broadcast the transaction. */
if (tx) {
enum wallet_tx_type wtt;
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);
Expand All @@ -818,12 +1075,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) {
Expand Down Expand Up @@ -1341,7 +1602,7 @@ static void steal_htlc_tx(struct tracked_output *out,
* `<revocationsig> 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);
Expand Down Expand Up @@ -1594,6 +1855,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);
}
}

Expand Down Expand Up @@ -2403,8 +2671,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);
}
Expand All @@ -2423,8 +2691,9 @@ static void steal_htlc(struct tracked_output *out, bool is_replay)
* <revocation_sig> <revocationpubkey>
*/
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);
}
Expand Down

0 comments on commit f965b9f

Please sign in to comment.