diff --git a/bitcoin/test/run-unified_sighash.c b/bitcoin/test/run-unified_sighash.c index bb436a09bdb8..e16d25729805 100644 --- a/bitcoin/test/run-unified_sighash.c +++ b/bitcoin/test/run-unified_sighash.c @@ -110,10 +110,64 @@ static void check_signing(void) assert(!check_tx_sig(tx, 0, NULL, code, &pub, &unified)); } +/* 0xa3 covers only the HTLC input and its output, wherever they sit in the + * tx; 0x21 covers every input and output. */ +static void check_htlc_tx_sighash(void) +{ + const enum sighash_type single = SIGHASH_SINGLE | SIGHASH_ANYONECANPAY | SIGHASH_UNIFIED; + const enum sighash_type all = SIGHASH_ALL | SIGHASH_UNIFIED; + struct privkey key = { .secret.data = { 2 } }; + struct pubkey pub; + struct bitcoin_outpoint htlc = { .n = 1 }, coin = { .n = 7 }; + struct bitcoin_tx *txs[3]; + struct sha256_double single_digest[3], all_digest[3]; + struct bitcoin_signature sig; + /* The HTLC input's index in each of txs[] */ + const size_t idx[3] = { 0, 0, 1 }; + assert(pubkey_from_privkey(&key, &pub)); + u8 *wscript = bitcoin_wscript_anchor(tmpctx, &pub); + u8 *coin_spk = scriptpubkey_p2wpkh(tmpctx, &pub); + memset(&htlc.txid, 1, sizeof(htlc.txid)); + memset(&coin.txid, 2, sizeof(coin.txid)); + for (size_t i = 0; i < 3; i++) { + txs[i] = bitcoin_tx(tmpctx, chainparams, 2, 2, 0); + if (i == 2) { + bitcoin_tx_add_input(txs[i], &coin, 0xfffffffd, NULL, + amount_sat(20000), coin_spk, NULL); + bitcoin_tx_add_output(txs[i], coin_spk, NULL, amount_sat(15000)); + } + bitcoin_tx_add_input(txs[i], &htlc, 1, NULL, amount_sat(50000), NULL, wscript); + bitcoin_tx_add_output(txs[i], scriptpubkey_p2wsh(tmpctx, wscript), NULL, + amount_sat(50000)); + if (i == 1) { + bitcoin_tx_add_input(txs[i], &coin, 0xfffffffd, NULL, + amount_sat(20000), coin_spk, NULL); + bitcoin_tx_add_output(txs[i], coin_spk, NULL, amount_sat(15000)); + } + bitcoin_tx_hash_for_sig(txs[i], idx[i], wscript, single, &single_digest[i]); + bitcoin_tx_hash_for_sig(txs[i], idx[i], wscript, all, &all_digest[i]); + } + for (size_t i = 1; i < 3; i++) { + assert(memcmp(&single_digest[0], &single_digest[i], sizeof(single_digest[0])) == 0); + assert(memcmp(&all_digest[0], &all_digest[i], sizeof(all_digest[0])) != 0); + } + assert(memcmp(&all_digest[1], &all_digest[2], sizeof(all_digest[1])) != 0); + + /* So a signature on the bare tx carries over to the others only as 0xa3. */ + sign_tx_input(txs[0], 0, NULL, wscript, &key, &pub, single, &sig); + for (size_t i = 0; i < 3; i++) + assert(check_tx_sig(txs[i], idx[i], NULL, wscript, &pub, &sig)); + sign_tx_input(txs[0], 0, NULL, wscript, &key, &pub, all, &sig); + assert(check_tx_sig(txs[0], 0, NULL, wscript, &pub, &sig)); + for (size_t i = 1; i < 3; i++) + assert(!check_tx_sig(txs[i], idx[i], NULL, wscript, &pub, &sig)); +} + int main(int argc, char *argv[]) { common_setup(argv[0]); chainparams = chainparams_for_network("regtest"); check_signing(); + check_htlc_tx_sighash(); check("2316de5a02a4fedc0d9e902f93a402329869afcf6b22c444f800297418b54d2e002c57eb6af3c4730b00569037965acac7bfd72758f60ddbe8ca7b1da23a70984f5e8b03bc44889190ea56034ec7bf259257003f9f66fc01cbbc461ea5ac04000254544e112901", "5353535353", 0, 163, 0, "4a84224afd272deeaa13972fb03ea70c738d78e50b53a63af3b3a9decfb548f5", 2, (u64[]){650374865900231ULL, 877685345816497ULL}, (const char *[]){"515151515151", "52525252525252"}); check("74f713070329575216fe42fc7fb93e35ca2e52b3b12fee83f4d1aa8250be4445a6f912f33dd6b0974100a7b2d52e139daf3f073b5e3216427568954ad988564bcb34d053891a3ce8da53d815ada9fe8589a5003f14f14ff921522b5ecf9dbd392c2a386a620bfa03f0d6c6de57ac3e4a02e2bdd30d2b319e1d86cd0042937ea802affd6346928e0300035454540a1c474686220100065252525252525ec58fb5", "535353", 1, 161, 1, "0c049fe49dbe9a344f2ef8d3de920aa671e37a31919f106a6250c5635c7682e1", 3, (u64[]){217964177126525ULL, 1286402448270793ULL, 875002681445907ULL}, (const char *[]){"54545454545454", "53535353535353", "545454545454"}); check("db27e51d021b2927dff1c4fa374ca66a750aa549f3a08db178978f57c663804273fc8dea29eb27f022008f680f8436a4c31084e27b4f296e24a932bb4121282c6962afc572f093d0c038a74ee843e462a72c0092a5b34701519c44e959aa040001561092b372", "515151", 0, 162, 1, "5ff71c408c490803bd6d0d5578685b565a3fb36a2adebadbea680ecf5ecdfd98", 2, (u64[]){1105898160581225ULL, 734602758275010ULL}, (const char *[]){"52525252", "5252525252525252"}); diff --git a/common/hsm_version.h b/common/hsm_version.h index e894974ce2f1..e58a30e20d69 100644 --- a/common/hsm_version.h +++ b/common/hsm_version.h @@ -34,6 +34,7 @@ * v6 with hsm_secret_type TLV: 7bb5deb2367482feb084d304ee14b2373d42910ad56484fbf47614dbb3d4cb74 * v6 with bip86_base in TLV: 6bb6e6ee256f22a6fb41856c90feebde3065a9074e79a46731e453a932be83f0 * v6 with hsmd_sign_local_htlc_tx removed: 0e28ca3699196cf9d26d38f8f621d1bf68c76cf5e2cdc1f4157a6937b02dc2f6 + * v6 with sign_htlc_tx_mingle_unified: 290bcb0cc0c03319fc0f1a91d6e396ac9e80c77c709da0d2905b98f058714059 */ #define HSM_MIN_VERSION 5 #define HSM_MAX_VERSION 6 diff --git a/hsmd/hsmd.c b/hsmd/hsmd.c index 42cacfe5d196..ee93ac869f34 100644 --- a/hsmd/hsmd.c +++ b/hsmd/hsmd.c @@ -833,7 +833,8 @@ static struct io_plan *handle_client(struct io_conn *conn, struct client *c) case WIRE_HSMD_SIGN_ANY_REMOTE_HTLC_TO_US: case WIRE_HSMD_SIGN_ANY_LOCAL_HTLC_TX: case WIRE_HSMD_SIGN_ANCHORSPEND: - case WIRE_HSMD_SIGN_HTLC_TX_MINGLE: { + case WIRE_HSMD_SIGN_HTLC_TX_MINGLE: + case WIRE_HSMD_SIGN_HTLC_TX_MINGLE_UNIFIED: { u8 *reply = hsmd_handle_client_message(tmpctx, c->hsmd_client, c->msg_in); if (!reply) @@ -880,6 +881,7 @@ static struct io_plan *handle_client(struct io_conn *conn, struct client *c) case WIRE_HSMD_CHECK_BIP86_PUBKEY_REPLY: case WIRE_HSMD_SIGN_ANCHORSPEND_REPLY: case WIRE_HSMD_SIGN_HTLC_TX_MINGLE_REPLY: + case WIRE_HSMD_SIGN_HTLC_TX_MINGLE_UNIFIED_REPLY: case WIRE_HSMD_SIGN_ANY_CANNOUNCEMENT_REPLY: return bad_req_fmt(conn, c, c->msg_in, "Received an incoming message of type %s, " diff --git a/hsmd/hsmd_wire.csv b/hsmd/hsmd_wire.csv index d3ee517b83ed..10122d819270 100644 --- a/hsmd/hsmd_wire.csv +++ b/hsmd/hsmd_wire.csv @@ -513,3 +513,21 @@ msgdata,hsmd_sign_htlc_tx_mingle,psbt,wally_psbt, msgtype,hsmd_sign_htlc_tx_mingle_reply,150 msgdata,hsmd_sign_htlc_tx_mingle_reply,psbt,wally_psbt, + +# For option_unified_sigs channels: signs the wallet inputs, then our half +# of the HTLC input SIGHASH_ALL over the final transaction. Numbered well +# clear of upstream's range. +msgtype,hsmd_sign_htlc_tx_mingle_unified,600 +msgdata,hsmd_sign_htlc_tx_mingle_unified,peerid,node_id, +msgdata,hsmd_sign_htlc_tx_mingle_unified,channel_dbid,u64, +msgdata,hsmd_sign_htlc_tx_mingle_unified,num_inputs,u16, +msgdata,hsmd_sign_htlc_tx_mingle_unified,inputs,hsm_utxo,num_inputs +msgdata,hsmd_sign_htlc_tx_mingle_unified,psbt,wally_psbt, +msgdata,hsmd_sign_htlc_tx_mingle_unified,commit_num,u64, +msgdata,hsmd_sign_htlc_tx_mingle_unified,htlc_input,u32, +msgdata,hsmd_sign_htlc_tx_mingle_unified,wscript_len,u16, +msgdata,hsmd_sign_htlc_tx_mingle_unified,wscript,u8,wscript_len + +msgtype,hsmd_sign_htlc_tx_mingle_unified_reply,601 +msgdata,hsmd_sign_htlc_tx_mingle_unified_reply,psbt,wally_psbt, +msgdata,hsmd_sign_htlc_tx_mingle_unified_reply,sig,bitcoin_signature, diff --git a/hsmd/libhsmd.c b/hsmd/libhsmd.c index 8c68c841a276..95b8997c66f2 100644 --- a/hsmd/libhsmd.c +++ b/hsmd/libhsmd.c @@ -166,6 +166,7 @@ bool hsmd_check_client_capabilities(struct hsmd_client *client, case WIRE_HSMD_SIGN_ANY_LOCAL_HTLC_TX: case WIRE_HSMD_SIGN_ANCHORSPEND: case WIRE_HSMD_SIGN_HTLC_TX_MINGLE: + case WIRE_HSMD_SIGN_HTLC_TX_MINGLE_UNIFIED: case WIRE_HSMD_SIGN_ANY_CANNOUNCEMENT_REQ: return (client->capabilities & HSM_PERM_MASTER) != 0; @@ -211,6 +212,7 @@ bool hsmd_check_client_capabilities(struct hsmd_client *client, case WIRE_HSMD_CHECK_BIP86_PUBKEY_REPLY: case WIRE_HSMD_SIGN_ANCHORSPEND_REPLY: case WIRE_HSMD_SIGN_HTLC_TX_MINGLE_REPLY: + case WIRE_HSMD_SIGN_HTLC_TX_MINGLE_UNIFIED_REPLY: case WIRE_HSMD_SIGN_ANY_CANNOUNCEMENT_REPLY: break; } @@ -1602,29 +1604,21 @@ static u8 *handle_sign_splice_tx(struct hsmd_client *c, const u8 *msg_in) * * The interfaces are very similar, so we have core functions that both * variants call after unwrapping the message. */ -static u8 *do_sign_local_htlc_tx(struct hsmd_client *c, + +/* Our htlc key on our own commitment @commit_num. Returns NULL on + * success, otherwise the bad request reply to send. */ +static u8 *derive_local_htlc_key(struct hsmd_client *c, const u8 *msg_in, - u32 input_num, const struct node_id *peerid, u64 channel_dbid, u64 commit_num, - struct bitcoin_tx *tx, - const u8 *wscript, - bool option_anchor_outputs) + struct privkey *htlc_privkey, + struct pubkey *htlc_pubkey) { struct secret channel_seed, htlc_basepoint_secret; struct sha256 shaseed; struct pubkey per_commitment_point, htlc_basepoint; - struct bitcoin_signature sig; - struct privkey htlc_privkey; - struct pubkey htlc_pubkey; - - if (input_num >= tx->wtx->num_inputs) - return hsmd_status_bad_request_fmt(c, msg_in, - "bad input %u of %zu", - input_num, tx->wtx->num_inputs); - tx->chainparams = c->chainparams; get_channel_seed(peerid, channel_dbid, &channel_seed); if (!derive_shaseed(&channel_seed, &shaseed)) @@ -1644,14 +1638,44 @@ static u8 *do_sign_local_htlc_tx(struct hsmd_client *c, if (!derive_simple_privkey(&htlc_basepoint_secret, &htlc_basepoint, &per_commitment_point, - &htlc_privkey)) + htlc_privkey)) return hsmd_status_bad_request_fmt( c, msg_in, "Failed deriving htlc privkey"); - if (!pubkey_from_privkey(&htlc_privkey, &htlc_pubkey)) + if (!pubkey_from_privkey(htlc_privkey, htlc_pubkey)) return hsmd_status_bad_request_fmt(c, msg_in, "bad pubkey_from_privkey"); + return NULL; +} + +static u8 *do_sign_local_htlc_tx(struct hsmd_client *c, + const u8 *msg_in, + u32 input_num, + const struct node_id *peerid, + u64 channel_dbid, + u64 commit_num, + struct bitcoin_tx *tx, + const u8 *wscript, + bool option_anchor_outputs) +{ + struct bitcoin_signature sig; + struct privkey htlc_privkey; + struct pubkey htlc_pubkey; + enum sighash_type sighash_type; + u8 *err; + + if (input_num >= tx->wtx->num_inputs) + return hsmd_status_bad_request_fmt(c, msg_in, + "bad input %u of %zu", + input_num, tx->wtx->num_inputs); + + tx->chainparams = c->chainparams; + err = derive_local_htlc_key(c, msg_in, peerid, channel_dbid, + commit_num, &htlc_privkey, &htlc_pubkey); + if (err) + return err; + if (tx->wtx->num_inputs != 1) return hsmd_status_bad_request_fmt(c, msg_in, "bad txinput count"); @@ -1664,11 +1688,23 @@ static u8 *do_sign_local_htlc_tx(struct hsmd_client *c, * * if `option_anchors` applies to this commitment transaction, * `SIGHASH_SINGLE|SIGHASH_ANYONECANPAY` is used as described in [BOLT #5] */ + sighash_type = option_anchor_outputs + ? (SIGHASH_SINGLE|SIGHASH_ANYONECANPAY) + : SIGHASH_ALL; + + /* BOLT #5: + * Otherwise, `SIGHASH_SINGLE|SIGHASH_ANYONECANPAY` MUST be used on the + * HTLC signatures received from the peer, as this allows HTLC transactions to be combined with + * other transactions. The local signature MUST use `SIGHASH_ALL`, otherwise + * anyone can attach additional inputs and outputs to the tx. + */ + /* A unified channel follows this: lightningd signs again, through + * hsmd_sign_htlc_tx_mingle_unified, whenever it adds fee inputs. */ + if (tx->psbt->inputs[input_num].sighash & SIGHASH_UNIFIED) + sighash_type = SIGHASH_ALL; + sign_tx_input(tx, input_num, NULL, wscript, &htlc_privkey, &htlc_pubkey, - option_anchor_outputs - ? (SIGHASH_SINGLE|SIGHASH_ANYONECANPAY) - : SIGHASH_ALL, - &sig); + sighash_type, &sig); check_overgrind(&sig); return towire_hsmd_sign_tx_reply(NULL, &sig); @@ -1985,6 +2021,106 @@ static u8 *handle_sign_htlc_tx_mingle(struct hsmd_client *c, const u8 *msg_in) return towire_hsmd_sign_htlc_tx_mingle_reply(NULL, psbt); } +/*~ Called from lightningd, for channels with option_unified_sigs: unlike + * hsmd_sign_htlc_tx_mingle, our half of the HTLC input is signed here, last, + * so it can commit to the whole transaction. */ +static u8 *handle_sign_htlc_tx_mingle_unified(struct hsmd_client *c, + const u8 *msg_in) +{ + struct node_id peer_id; + u64 dbid, commit_num; + struct hsm_utxo **utxos; + struct wally_psbt *psbt; + u32 htlc_input; + u8 *wscript, *err; + const u8 *spk; + const struct wally_tx_output *htlc_utxo; + struct bitcoin_tx *tx; + struct unified_sighash_input exec = { .script_type = 1 }; + struct sha256_double digest; + struct bitcoin_signature sig; + struct privkey htlc_privkey; + struct pubkey htlc_pubkey; + + if (!fromwire_hsmd_sign_htlc_tx_mingle_unified(tmpctx, msg_in, + &peer_id, &dbid, + &utxos, &psbt, + &commit_num, + &htlc_input, + &wscript)) + return hsmd_status_malformed_request(c, msg_in); + + if (is_elements(chainparams)) + return hsmd_status_bad_request(c, msg_in, + "No unified signing on elements"); + + /* SIGHASH_SINGLE pairs the HTLC input with its output. */ + if (htlc_input >= psbt->num_inputs || htlc_input >= psbt->num_outputs) + return hsmd_status_bad_request_fmt(c, msg_in, + "bad htlc input %u of %zu/%zu", + htlc_input, psbt->num_inputs, + psbt->num_outputs); + + for (size_t i = 0; i < tal_count(utxos); i++) { + if (wally_psbt_input_spends(&psbt->inputs[htlc_input], + &utxos[i]->outpoint)) + return hsmd_status_bad_request(c, msg_in, + "HTLC input given as wallet utxo"); + } + + htlc_utxo = psbt->inputs[htlc_input].witness_utxo; + spk = scriptpubkey_p2wsh(tmpctx, wscript); + if (!htlc_utxo + || !memeq(htlc_utxo->script, htlc_utxo->script_len, + spk, tal_bytelen(spk))) + return hsmd_status_bad_request(c, msg_in, + "HTLC input does not spend wscript"); + + if (psbt->inputs[htlc_input].sighash != 0 + && psbt->inputs[htlc_input].sighash != (SIGHASH_ALL | SIGHASH_UNIFIED)) + return hsmd_status_bad_request_fmt(c, msg_in, + "bad HTLC input sighash 0x%x", + psbt->inputs[htlc_input].sighash); + if (wally_psbt_input_set_sighash(&psbt->inputs[htlc_input], + SIGHASH_ALL | SIGHASH_UNIFIED) != WALLY_OK) + return hsmd_status_bad_request(c, msg_in, + "Cannot set HTLC input sighash"); + + err = derive_local_htlc_key(c, msg_in, &peer_id, dbid, commit_num, + &htlc_privkey, &htlc_pubkey); + if (err) + return err; + + /* Sign the wallet inputs first: that can rewrite their prevout + * scripts and amounts, which our signature below commits to. */ + if (!sign_our_inputs(utxos, psbt)) + return hsmd_status_bad_request(c, msg_in, + "Cannot sign wallet inputs"); + + /* BOLT #5: + * Otherwise, `SIGHASH_SINGLE|SIGHASH_ANYONECANPAY` MUST be used on the + * HTLC signatures received from the peer, as this allows HTLC transactions to be combined with + * other transactions. The local signature MUST use `SIGHASH_ALL`, otherwise + * anyone can attach additional inputs and outputs to the tx. + */ + tx = bitcoin_tx_with_psbt(tmpctx, psbt); + if (!tx) + return hsmd_status_bad_request(c, msg_in, "Cannot extract tx"); + /* The digest covers every input's prevout, so it fails, rather + * than us, if any of them is missing or inconsistent. */ + exec.script_code = wscript; + if (!bitcoin_tx_unified_sighash(tx, htlc_input, + SIGHASH_ALL | SIGHASH_UNIFIED, + &exec, &digest.sha)) + return hsmd_status_bad_request(c, msg_in, + "Missing or inconsistent prevout data"); + sig.sighash_type = SIGHASH_ALL | SIGHASH_UNIFIED; + sign_hash(&htlc_privkey, &digest, &sig.s); + check_overgrind(&sig); + + return towire_hsmd_sign_htlc_tx_mingle_unified_reply(NULL, psbt, &sig); +} + /*~ This is another lightningd-only interface; signing a commit transaction. * This is dangerous, since if we sign a revoked commitment tx we'll lose * funds, thus it's only available to lightningd. @@ -2163,6 +2299,7 @@ static u8 *do_sign_remote_htlc_to_us(struct hsmd_client *c, struct secret channel_seed, htlc_basepoint_secret; struct pubkey htlc_basepoint; struct privkey privkey; + enum sighash_type sighash_type; tx->chainparams = c->chainparams; get_channel_seed(peerid, channel_dbid, &channel_seed); @@ -2185,10 +2322,18 @@ static u8 *do_sign_remote_htlc_to_us(struct hsmd_client *c, * * if `option_anchors` applies to this commitment transaction, * `SIGHASH_SINGLE|SIGHASH_ANYONECANPAY` is used as described in [BOLT #5] */ - return handle_sign_to_us_tx( - c, msg_in, input_num, tx, &privkey, wscript, - option_anchor_outputs ? (SIGHASH_SINGLE | SIGHASH_ANYONECANPAY) - : SIGHASH_ALL); + sighash_type = option_anchor_outputs + ? (SIGHASH_SINGLE | SIGHASH_ANYONECANPAY) + : SIGHASH_ALL; + + /* Nobody else signs this one, and lightningd signs it again for every + * fee change, so a unified channel commits to the whole tx. */ + if (input_num < tx->psbt->num_inputs + && (tx->psbt->inputs[input_num].sighash & SIGHASH_UNIFIED)) + sighash_type = SIGHASH_ALL; + + return handle_sign_to_us_tx(c, msg_in, input_num, tx, &privkey, + wscript, sighash_type); } /*~ When called by onchaind */ @@ -2455,6 +2600,8 @@ u8 *hsmd_handle_client_message(const tal_t *ctx, struct hsmd_client *client, return handle_sign_anchorspend(client, msg); case WIRE_HSMD_SIGN_HTLC_TX_MINGLE: return handle_sign_htlc_tx_mingle(client, msg); + case WIRE_HSMD_SIGN_HTLC_TX_MINGLE_UNIFIED: + return handle_sign_htlc_tx_mingle_unified(client, msg); case WIRE_HSMD_DEV_MEMLEAK: case WIRE_HSMD_ECDH_RESP: @@ -2496,6 +2643,7 @@ u8 *hsmd_handle_client_message(const tal_t *ctx, struct hsmd_client *client, case WIRE_HSMD_CHECK_BIP86_PUBKEY_REPLY: case WIRE_HSMD_SIGN_ANCHORSPEND_REPLY: case WIRE_HSMD_SIGN_HTLC_TX_MINGLE_REPLY: + case WIRE_HSMD_SIGN_HTLC_TX_MINGLE_UNIFIED_REPLY: case WIRE_HSMD_SIGN_ANY_CANNOUNCEMENT_REPLY: break; } @@ -2581,6 +2729,7 @@ u8 *hsmd_init(const u8 *secret_data, size_t secret_len, const u64 hsmd_version, WIRE_HSMD_SIGN_ANY_DELAYED_PAYMENT_TO_US, WIRE_HSMD_SIGN_ANCHORSPEND, WIRE_HSMD_SIGN_HTLC_TX_MINGLE, + WIRE_HSMD_SIGN_HTLC_TX_MINGLE_UNIFIED, WIRE_HSMD_SIGN_SPLICE_TX, WIRE_HSMD_CHECK_OUTPOINT, WIRE_HSMD_FORGET_CHANNEL, diff --git a/hsmd/test/run-sign_htlc_tx_mingle_unified.c b/hsmd/test/run-sign_htlc_tx_mingle_unified.c new file mode 100644 index 000000000000..90ffbd88d1b2 --- /dev/null +++ b/hsmd/test/run-sign_htlc_tx_mingle_unified.c @@ -0,0 +1,239 @@ +/* hsmd_sign_htlc_tx_mingle_unified signs our half of an HTLC tx + * SIGHASH_ALL|SIGHASH_UNIFIED, over the final transaction, after the wallet + * inputs; and refuses a request it cannot sign that way. + * + * We compile hsmd.c in, as run-bad-request-close does, and call libhsmd's + * handler directly from a non-lightningd client, so a refusal returns NULL + * rather than ending the process. */ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int unused_main(int argc, char *argv[]); +#define main unused_main +#include "../hsmd.c" +#undef main + +static struct client *client; +static struct node_id peer_id; +static const u64 dbid = 1; +static const u64 commit_num = 3; + +static const u8 *request(const u8 *msg) +{ + return tal_steal(tmpctx, + hsmd_handle_client_message(tmpctx, client->hsmd_client, + msg)); +} + +/* An HTLC input and output at index 0, then a fee input and change. */ +static struct wally_psbt *htlc_psbt(const struct bitcoin_outpoint *htlc, + const u8 *wscript, + const struct hsm_utxo *coin, + size_t num_outputs) +{ + struct bitcoin_tx *tx = bitcoin_tx(tmpctx, chainparams, 2, 2, 0); + /* The coin's prevout as lightningd might first describe it: hsmd + * replaces it for an anchor close output, and our signature must + * commit to the replacement. */ + struct pubkey stale; + + assert(pubkey_from_hexstr("02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", + 66, &stale)); + bitcoin_tx_add_input(tx, htlc, 1, NULL, AMOUNT_SAT(50000), NULL, wscript); + bitcoin_tx_add_output(tx, scriptpubkey_p2wsh(tmpctx, wscript), NULL, + AMOUNT_SAT(50000)); + bitcoin_tx_add_input(tx, &coin->outpoint, 1, NULL, AMOUNT_SAT(1), + scriptpubkey_p2wpkh(tmpctx, &stale), NULL); + if (num_outputs > 1) + bitcoin_tx_add_output(tx, scriptpubkey_p2wpkh(tmpctx, &stale), + NULL, AMOUNT_SAT(15000)); + return tx->psbt; +} + +/* coin NULL: no wallet utxos at all. */ +static const u8 *sign(const struct hsm_utxo *coin, struct wally_psbt *psbt, + u32 htlc_input, const u8 *wscript) +{ + const struct hsm_utxo **utxos = tal_arr(tmpctx, const struct hsm_utxo *, + coin ? 1 : 0); + if (coin) + utxos[0] = coin; + return request(towire_hsmd_sign_htlc_tx_mingle_unified(tmpctx, &peer_id, + dbid, utxos, psbt, + commit_num, + htlc_input, + wscript)); +} + +int main(int argc, char *argv[]) +{ + int status_fds[2], client_fds[2]; + const struct chainparams *params; + struct privkey priv; + struct pubkey peer_key, per_commitment_point, htlc_pubkey, funding_pubkey; + struct basepoints basepoints; + struct secret *old_secret; + struct unilateral_close_info close_info; + struct hsm_utxo coin; + struct bitcoin_outpoint htlc; + struct bitcoin_signature sig; + struct bitcoin_tx *tx; + struct wally_psbt *psbt, *signed_psbt; + const u8 *wscript, *reply; + u8 hsmseed[32]; + u8 *msg; + + common_setup(argv[0]); + uintmap_init(&clients); + + /* Refusals are reported to lightningd over status_conn. */ + assert(socketpair(AF_LOCAL, SOCK_STREAM, 0, status_fds) == 0); + status_conn = daemon_conn_new(NULL, status_fds[0], NULL, NULL, NULL); + status_setup_async(status_conn); + + params = chainparams_for_network("regtest"); + chainparams = params; + memset(hsmseed, 1, sizeof(hsmseed)); + msg = hsmd_init(hsmseed, sizeof(hsmseed), 6, + params->bip32_key_version, HSM_SECRET_PLAIN); + assert(msg); + taken(msg); + tal_free(msg); + + memset(&priv, 1, sizeof(priv)); + assert(pubkey_from_privkey(&priv, &peer_key)); + node_id_from_pubkey(&peer_id, &peer_key); + assert(socketpair(AF_LOCAL, SOCK_STREAM, 0, client_fds) == 0); + client = new_client(NULL, params, &peer_id, dbid, + HSM_PERM_MASTER | HSM_PERM_COMMITMENT_POINT, + client_fds[0]); + + /* Our htlc key, the way lightningd derives it. */ + reply = request(towire_hsmd_get_channel_basepoints(tmpctx, &peer_id, dbid)); + assert(fromwire_hsmd_get_channel_basepoints_reply(reply, &basepoints, + &funding_pubkey)); + reply = request(towire_hsmd_get_per_commitment_point(tmpctx, commit_num)); + assert(fromwire_hsmd_get_per_commitment_point_reply(tmpctx, reply, + &per_commitment_point, + &old_secret)); + assert(derive_simple_key(&basepoints.htlc, &per_commitment_point, + &htlc_pubkey)); + + /* Any witness script will do: hsmd only checks the prevout is it. */ + wscript = bitcoin_wscript_anchor(tmpctx, &htlc_pubkey); + memset(&htlc, 0, sizeof(htlc)); + memset(&htlc.txid, 0x11, sizeof(htlc.txid)); + + /* The fee input is our to_remote output from an anchor close. */ + close_info.channel_id = dbid; + close_info.peer_id = peer_id; + close_info.option_anchors = true; + close_info.commitment_point = NULL; + close_info.csv = 1; + memset(&coin, 0, sizeof(coin)); + memset(&coin.outpoint.txid, 0x22, sizeof(coin.outpoint.txid)); + coin.outpoint.n = 1; + coin.amount = AMOUNT_SAT(20000); + coin.close_info = &close_info; + coin.scriptPubkey = scriptpubkey_p2wsh(tmpctx, + bitcoin_wscript_to_remote_anchored(tmpctx, &basepoints.payment, 1)); + + psbt = htlc_psbt(&htlc, wscript, &coin, 2); + reply = sign(&coin, psbt, 0, wscript); + assert(reply); + assert(fromwire_hsmd_sign_htlc_tx_mingle_unified_reply(tmpctx, reply, + &signed_psbt, + &sig)); + assert(sig.sighash_type == (SIGHASH_ALL | SIGHASH_UNIFIED)); + assert(signed_psbt->inputs[0].sighash == (SIGHASH_ALL | SIGHASH_UNIFIED)); + /* The fee input is signed too, and its prevout was replaced. */ + assert(signed_psbt->inputs[1].signatures.num_items == 1); + assert(signed_psbt->inputs[1].witness_utxo->satoshi == 20000); + + /* Valid for our htlc key over the transaction as it will be sent... */ + tx = bitcoin_tx_with_psbt(tmpctx, signed_psbt); + assert(check_tx_sig(tx, 0, NULL, wscript, &htlc_pubkey, &sig)); + /* ...and over nothing else: not with the fee input described as it was + * before hsmd signed it, nor with any output changed. */ + assert(!check_tx_sig(bitcoin_tx_with_psbt(tmpctx, psbt), 0, NULL, + wscript, &htlc_pubkey, &sig)); + bitcoin_tx_output_set_amount(tx, 1, AMOUNT_SAT(14999)); + assert(!check_tx_sig(tx, 0, NULL, wscript, &htlc_pubkey, &sig)); + + /* A wallet utxo which is the HTLC input itself. */ + { + struct hsm_utxo self = coin; + self.outpoint = htlc; + assert(!sign(&self, htlc_psbt(&htlc, wscript, &coin, 2), 0, wscript)); + } + /* No such input. */ + assert(!sign(&coin, htlc_psbt(&htlc, wscript, &coin, 2), 2, wscript)); + /* No output to pair the HTLC input with. */ + { + struct bitcoin_tx *bad = bitcoin_tx(tmpctx, chainparams, 2, 1, 0); + bitcoin_tx_add_input(bad, &coin.outpoint, 1, NULL, coin.amount, + coin.scriptPubkey, NULL); + bitcoin_tx_add_input(bad, &htlc, 1, NULL, AMOUNT_SAT(50000), + NULL, wscript); + bitcoin_tx_add_output(bad, scriptpubkey_p2wsh(tmpctx, wscript), + NULL, AMOUNT_SAT(50000)); + assert(!sign(&coin, bad->psbt, 1, wscript)); + } + /* Not the script the HTLC input spends. */ + assert(!sign(&coin, htlc_psbt(&htlc, wscript, &coin, 2), 0, + bitcoin_wscript_anchor(tmpctx, &funding_pubkey))); + /* Asked to sign the HTLC input as anything but SIGHASH_ALL. */ + psbt = htlc_psbt(&htlc, wscript, &coin, 2); + assert(wally_psbt_input_set_sighash(&psbt->inputs[0], + SIGHASH_SINGLE | SIGHASH_ANYONECANPAY + | SIGHASH_UNIFIED) == WALLY_OK); + assert(!sign(&coin, psbt, 0, wscript)); + /* Another input whose prevout we are not told, so cannot commit to. + * Without wallet utxos, nothing else looks at it before our digest. */ + { + struct bitcoin_outpoint other; + memset(&other.txid, 0x33, sizeof(other.txid)); + other.n = 0; + psbt = htlc_psbt(&htlc, wscript, &coin, 2); + psbt_append_input(psbt, &other, 1, NULL, NULL, NULL); + assert(!sign(NULL, psbt, 0, wscript)); + assert(!sign(&coin, psbt, 0, wscript)); + } + /* Another input whose prevout and previous transaction disagree. */ + { + struct bitcoin_tx *prev = bitcoin_tx(tmpctx, chainparams, 1, 1, 0); + struct bitcoin_outpoint other; + bitcoin_tx_add_input(prev, &htlc, 1, NULL, AMOUNT_SAT(9000), NULL, wscript); + bitcoin_tx_add_output(prev, coin.scriptPubkey, NULL, AMOUNT_SAT(8000)); + bitcoin_txid(prev, &other.txid); + other.n = 0; + psbt = htlc_psbt(&htlc, wscript, &coin, 2); + psbt_append_input(psbt, &other, 1, NULL, NULL, NULL); + psbt_input_set_wit_utxo(psbt, 2, coin.scriptPubkey, AMOUNT_SAT(8001)); + psbt_input_set_utxo(psbt, 2, prev->wtx); + assert(!sign(NULL, psbt, 0, wscript)); + assert(!sign(&coin, psbt, 0, wscript)); + /* Consistent, it is fine. */ + psbt_input_set_wit_utxo(psbt, 2, coin.scriptPubkey, AMOUNT_SAT(8000)); + assert(sign(NULL, psbt, 0, wscript)); + assert(sign(&coin, psbt, 0, wscript)); + } + /* While a request it can sign still succeeds. */ + assert(sign(&coin, htlc_psbt(&htlc, wscript, &coin, 2), 0, wscript)); + + close(client_fds[1]); + close(status_fds[1]); + tal_free(client->conn); + tal_free(status_conn); + hsmd_deinit(); + common_shutdown(); + return 0; +} diff --git a/lightningd/hsm_control.c b/lightningd/hsm_control.c index a451a6bcced7..c76fae05950e 100644 --- a/lightningd/hsm_control.c +++ b/lightningd/hsm_control.c @@ -185,6 +185,12 @@ struct ext_key *hsm_init(struct lightningd *ld) fatal("anchors needs HSM capable of signing anchors!"); } + if (feature_offered(ld->our_features->bits[INIT_FEATURE], + OPT_UNIFIED_SIGS) + && !hsm_capable(ld, WIRE_HSMD_SIGN_HTLC_TX_MINGLE_UNIFIED)) { + fatal("unified_sigs needs HSM capable of signing unified HTLC txs!"); + } + if (feature_offered(ld->our_features->bits[INIT_FEATURE], OPT_SPLICE) && !hsm_capable(ld, WIRE_HSMD_SIGN_SPLICE_TX)) { diff --git a/lightningd/onchain_control.c b/lightningd/onchain_control.c index 88d441810f2c..9990bacb8191 100644 --- a/lightningd/onchain_control.c +++ b/lightningd/onchain_control.c @@ -769,14 +769,12 @@ static const struct bitcoin_tx *onchain_signing_tx(const struct bitcoin_tx *tx, if (channel_type_has(info->channel->type, OPT_UNIFIED_SIGS)) { /* Only needed until the caller serializes its HSM request. */ struct bitcoin_tx *copy = clone_bitcoin_tx(tmpctx, tx); - enum sighash_type base = SIGHASH_ALL; - if (channel_type_has_anchors(info->channel->type) - && (info->msgtype == WIRE_ONCHAIND_SPEND_HTLC_SUCCESS - || info->msgtype == WIRE_ONCHAIND_SPEND_HTLC_TIMEOUT - || info->msgtype == WIRE_ONCHAIND_SPEND_FULFILL - || info->msgtype == WIRE_ONCHAIND_SPEND_HTLC_EXPIRED)) - base = SIGHASH_SINGLE | SIGHASH_ANYONECANPAY; - bitcoin_tx_require_unified(copy, 0, base); + /* Every signature here is ours alone, and is made again for + * each fee change, so all are SIGHASH_ALL: our half of an + * HTLC-success/timeout tx too, which + * consider_onchain_htlc_tx_rebroadcast signs again whenever it + * adds fee inputs. */ + bitcoin_tx_require_unified(copy, 0, SIGHASH_ALL); return copy; } return tx; @@ -1140,6 +1138,87 @@ static bool consider_onchain_rebroadcast(struct channel *channel, return true; } +/* BOLT #5: + * Otherwise, `SIGHASH_SINGLE|SIGHASH_ANYONECANPAY` MUST be used on the + * HTLC signatures received from the peer, as this allows HTLC transactions to be combined with + * other transactions. The local signature MUST use `SIGHASH_ALL`, otherwise + * anyone can attach additional inputs and outputs to the tx. + */ +/* So on a unified channel our half of the HTLC input (always input 0) is + * signed again, last, over the transaction with its fee inputs and change. */ +static struct wally_psbt *sign_htlc_tx_mingle_unified(struct lightningd *ld, + const struct channel *channel, + const struct onchain_signing_info *info, + const struct hsm_utxo **hsm_utxos, + struct wally_psbt *psbt) +{ + const u8 *msg; + struct bitcoin_signature sig; + u64 commit_num; + u8 **witness; + struct wally_tx_witness_stack *stack = NULL; + bool ok; + + /* Drop our old signature, and ask for the new one. */ + if (wally_psbt_input_set_final_witness(&psbt->inputs[0], NULL) != WALLY_OK + || wally_psbt_input_set_sighash(&psbt->inputs[0], + SIGHASH_ALL | SIGHASH_UNIFIED) != WALLY_OK) + fatal("Cannot reopen HTLC input: %s", + fmt_wally_psbt(tmpctx, psbt)); + + if (info->msgtype == WIRE_ONCHAIND_SPEND_HTLC_SUCCESS) { + commit_num = info->u.htlc_success.commit_num; + } else { + assert(info->msgtype == WIRE_ONCHAIND_SPEND_HTLC_TIMEOUT); + commit_num = info->u.htlc_timeout.commit_num; + } + + msg = towire_hsmd_sign_htlc_tx_mingle_unified(NULL, + &channel->peer->id, + channel->dbid, + hsm_utxos, + psbt, + commit_num, + 0, + info->wscript); + msg = hsm_sync_req(tmpctx, ld, take(msg)); + if (!fromwire_hsmd_sign_htlc_tx_mingle_unified_reply(tmpctx, msg, + &psbt, &sig)) + fatal("Reading sign_htlc_tx_mingle_unified_reply: %s", + tal_hex(tmpctx, msg)); + if (sig.sighash_type != (SIGHASH_ALL | SIGHASH_UNIFIED)) + fatal("HSM signed our HTLC input with sighash 0x%x", + sig.sighash_type); + + if (info->msgtype == WIRE_ONCHAIND_SPEND_HTLC_SUCCESS) + witness = bitcoin_witness_htlc_success_tx(tmpctx, &sig, + &info->u.htlc_success.remote_htlc_sig, + &info->u.htlc_success.preimage, + info->wscript); + else + witness = bitcoin_witness_htlc_timeout_tx(tmpctx, &sig, + &info->u.htlc_timeout.remote_htlc_sig, + info->wscript); + + tal_wally_start(); + ok = (wally_tx_witness_stack_init_alloc(tal_count(witness), &stack) + == WALLY_OK); + for (size_t i = 0; ok && i < tal_count(witness); i++) + ok = (wally_tx_witness_stack_add(stack, witness[i], + tal_bytelen(witness[i])) + == WALLY_OK); + if (ok) + ok = (wally_psbt_input_set_final_witness(&psbt->inputs[0], + stack) == WALLY_OK); + wally_tx_witness_stack_free(stack); + tal_wally_end(psbt); + if (!ok) + fatal("Cannot set HTLC input witness: %s", + fmt_wally_psbt(tmpctx, psbt)); + + return psbt; +} + static bool consider_onchain_htlc_tx_rebroadcast(struct channel *channel, const struct bitcoin_tx **tx, struct onchain_signing_info *info) @@ -1238,15 +1317,20 @@ static bool consider_onchain_htlc_tx_rebroadcast(struct channel *channel, /* Now, get HSM to sign off. */ hsm_utxos = utxos_to_hsm_utxos(tmpctx, utxos); - msg = towire_hsmd_sign_htlc_tx_mingle(NULL, - &channel->peer->id, - channel->dbid, - hsm_utxos, - psbt); - msg = hsm_sync_req(tmpctx, ld, take(msg)); - if (!fromwire_hsmd_sign_htlc_tx_mingle_reply(tmpctx, msg, &psbt)) - fatal("Reading sign_htlc_tx_mingle_reply: %s", - tal_hex(tmpctx, msg)); + if (channel_type_has(channel->type, OPT_UNIFIED_SIGS)) { + psbt = sign_htlc_tx_mingle_unified(ld, channel, info, + hsm_utxos, psbt); + } else { + msg = towire_hsmd_sign_htlc_tx_mingle(NULL, + &channel->peer->id, + channel->dbid, + hsm_utxos, + psbt); + msg = hsm_sync_req(tmpctx, ld, take(msg)); + if (!fromwire_hsmd_sign_htlc_tx_mingle_reply(tmpctx, msg, &psbt)) + fatal("Reading sign_htlc_tx_mingle_reply: %s", + tal_hex(tmpctx, msg)); + } if (!psbt_finalize(psbt)) fatal("Non-final PSBT from hsm: %s", diff --git a/tests/test_unified_transactions.py b/tests/test_unified_transactions.py index c83cf77cadd4..d092cbb4fc73 100644 --- a/tests/test_unified_transactions.py +++ b/tests/test_unified_transactions.py @@ -1,5 +1,7 @@ """Run only against isolated Knots regtest with Blake2b active from height 1.""" from fixtures import * # noqa: F401,F403 +from bitcoin.rpc import JSONRPCError +from pyln.client import Millisatoshi from utils import only_one, wait_for, sync_blockheight, mine_funding_to_announce, TEST_NETWORK, TIMEOUT import queue import threading @@ -10,19 +12,29 @@ pytestmark = pytest.mark.skipif(TEST_NETWORK != 'regtest', reason='Blake2b consensus tests require Knots regtest') +def is_signature(raw): + return len(raw) == 65 or (69 <= len(raw) <= 73 and raw[0] == 0x30) + + def assert_unified_witnesses(bitcoind, txid, minimum): tx = bitcoind.rpc.getrawtransaction(txid, True) signatures = [] + # SIGHASH_UNIFIED is added to the hash type that would otherwise apply, + # so SIGHASH_ALL becomes 0x21, and the anchors HTLC form 0xa3. for vin in tx['vin']: - for item in vin.get('txinwitness', []): - raw = bytes.fromhex(item) - if len(raw) == 65 or (69 <= len(raw) <= 73 and raw[0] == 0x30): + wit = [bytes.fromhex(item) for item in vin.get('txinwitness', [])] + # HTLC-timeout/success: <> <> . + # Only the half the peer pre-signed may be 0xa3: ours is always 0x21. + if len(wit) == 5 and wit[0] == b'' and is_signature(wit[1]) and is_signature(wit[2]): + assert wit[1][-1] == 0xa3, '0x%02x' % wit[1][-1] + assert wit[2][-1] == 0x21, '0x%02x' % wit[2][-1] + signatures += wit[1:3] + continue + for raw in wit: + if is_signature(raw): + assert raw[-1] == 0x21, '0x%02x' % raw[-1] signatures.append(raw) assert len(signatures) >= minimum - # SIGHASH_UNIFIED is added to the hash type that would otherwise apply, - # so SIGHASH_ALL becomes 0x21, and the anchors HTLC form 0xa3. - for sig in signatures: - assert sig[-1] in (0x21, 0xa3), '0x%02x' % sig[-1] return tx @@ -387,3 +399,202 @@ def try_pay(): bitcoind.generate_block(100) sync_blockheight(bitcoind, [b]) assert b.daemon.is_in_log('All outputs resolved') + + +def split_wallet(node, bitcoind, n, sats): + """Leave node only n coins of sats each, so fees come from several.""" + fundsats = sum(int(Millisatoshi(o['amount_msat']).to_satoshi()) + for o in node.rpc.listfunds()['outputs'] if o['status'] == 'confirmed') + psbt = node.rpc.fundpsbt("all", "1000perkw", 1000)['psbt'] + psbt = node.rpc.addpsbtoutput(fundsats - n * sats - 5000, psbt, + destination=bitcoind.rpc.getnewaddress())['psbt'] + for _ in range(n): + psbt = node.rpc.addpsbtoutput(sats, psbt)['psbt'] + node.rpc.sendpsbt(node.rpc.signpsbt(psbt)['signed_psbt']) + bitcoind.generate_block(1, wait_for_mempool=1) + sync_blockheight(bitcoind, [node]) + wait_for(lambda: len([o for o in node.rpc.listfunds()['outputs'] + if o['status'] == 'confirmed' and o['amount_msat'] == sats * 1000]) == n) + + +def mempool_spend(bitcoind, txid, witness_items=None): + """The mempool tx whose first input spends txid, if any.""" + for t in bitcoind.rpc.getrawmempool(): + try: + tx = bitcoind.rpc.getrawtransaction(t, True) + except JSONRPCError: + # Replaced since we listed the mempool. + continue + vin0 = tx['vin'][0] + if vin0.get('txid') == txid and (witness_items is None + or len(vin0.get('txinwitness', [])) == witness_items): + return tx + return None + + +def mempool_htlc_tx(bitcoind, commit): + return mempool_spend(bitcoind, commit, witness_items=5) + + +def offer_stuck_htlc(l1, l2): + """l1 offers l2 an HTLC which l2 never resolves, then l2 goes away.""" + # Ignored rather than failed: l1 may reconnect, and a peer that only + # disconnects before failing it can still fail it then. + l2.rpc.dev_ignore_htlcs(id=l1.info['id'], ignore=True) + inv = l2.rpc.invoice(10**8, 'stuck', 'desc') + routestep = {'amount_msat': 10**8, 'id': l2.info['id'], 'delay': 12, + 'channel': only_one(l1.rpc.listpeerchannels()['channels'])['short_channel_id']} + l1.rpc.sendpay([routestep], inv['payment_hash'], payment_secret=inv['payment_secret']) + l2.daemon.wait_for_log('their htlc 0 dev_ignore_htlcs') + wait_for(lambda: only_one(only_one(l1.rpc.listpeerchannels()['channels'])['htlcs'])['state'] + == 'SENT_ADD_ACK_REVOCATION') + l2.stop() + + +def htlc_timeout_onchain(l1, bitcoind, funding): + """The HTLC hits its deadline: l1 goes on chain and times it out.""" + bitcoind.generate_block(14) + wait_for(lambda: mempool_spend(bitcoind, funding) is not None) + commit = mempool_spend(bitcoind, funding)['txid'] + bitcoind.generate_block(1, wait_for_mempool=commit) + l1.daemon.wait_for_log(' to ONCHAIN') + l1.wait_for_onchaind_tx('OUR_HTLC_TIMEOUT_TX', 'OUR_UNILATERAL/OUR_HTLC') + wait_for(lambda: mempool_htlc_tx(bitcoind, commit) is not None) + return commit + + +def htlc_tx_onchain(node_factory, bitcoind, case): + """Leave our anchor channel's HTLC-timeout or HTLC-success tx, with + fee inputs from a wallet of small coins, in the mempool. Returns the + node which broadcast it and the commitment tx it spends.""" + opts = {'feerates': (3000,) * 4, + 'min-emergency-msat': 546000, + 'may_reconnect': True, + 'broken_log': 'overgrind: short signature length'} + if case == 'timeout': + node, peer = node_factory.line_graph(2, opts=[opts, {'feerates': (3000,) * 4}]) + chan = only_one(node.rpc.listpeerchannels()['channels']) + split_wallet(node, bitcoind, 12, 20000) + offer_stuck_htlc(node, peer) + commit = htlc_timeout_onchain(node, bitcoind, chan['funding_txid']) + else: + # a -> node -> c, and node drops to chain after learning the preimage. + opts['disconnect'] = ['-WIRE_UPDATE_FULFILL_HTLC', 'permfail'] + a, node, c = node_factory.get_nodes(3, opts=[{'may_reconnect': True}, opts, + {'may_reconnect': True}]) + node.rpc.connect(a.info['id'], 'localhost', a.port) + node.rpc.connect(c.info['id'], 'localhost', c.port) + node.fundwallet(10**7) + node.fundchannel(a, 10**6) + c_scid, _ = node.fundchannel(c, 10**6) + mine_funding_to_announce(bitcoind, [a, node, c]) + a.wait_channel_active(c_scid) + chan = only_one(node.rpc.listpeerchannels(a.info['id'])['channels']) + split_wallet(node, bitcoind, 12, 20000) + node.pay(a, 2 * 10**8) + inv = c.rpc.invoice(10**8, 'success', 'desc') + route = a.single_route(c.info['id'], 10**8) + a.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret']) + wait_for(lambda: mempool_spend(bitcoind, chan['funding_txid']) is not None) + commit = mempool_spend(bitcoind, chan['funding_txid'])['txid'] + bitcoind.generate_block(1, wait_for_mempool=commit) + node.daemon.wait_for_log(' to ONCHAIN') + node.wait_for_onchaind_tx('OUR_HTLC_SUCCESS_TX', 'OUR_UNILATERAL/THEIR_HTLC') + wait_for(lambda: mempool_htlc_tx(bitcoind, commit) is not None) + + names = chan['channel_type']['names'] + assert 'unified_sigs/even' in names and 'anchors/even' in names + return node, commit + + +@pytest.mark.openchannel('v1') +@pytest.mark.parametrize('case', ['timeout', 'success']) +def test_unified_htlc_tx_rbf(node_factory, bitcoind, case): + """Every fee bump of our HTLC tx signs our half again, SIGHASH_ALL.""" + node, commit = htlc_tx_onchain(node_factory, bitcoind, case) + # txid -> our signature, for each version seen in the mempool. + versions = {} + + def check_current(): + tx = mempool_htlc_tx(bitcoind, commit) + assert len(tx['vin']) > 1 + wit = [bytes.fromhex(w) for w in tx['vin'][0]['txinwitness']] + assert wit[1][-1] == 0xa3, '0x%02x' % wit[1][-1] + assert wit[2][-1] == 0x21, '0x%02x' % wit[2][-1] + assert_unified_witnesses(bitcoind, tx['txid'], 2) + if tx['txid'] not in versions: + # SIGHASH_ALL: a different tx needs a different signature. + assert wit[2] not in versions.values() + versions[tx['txid']] = wit[2] + return tx['txid'] + + def next_version(oldtxid): + wait_for(lambda: (mempool_htlc_tx(bitcoind, commit) or {}).get('txid') not in (None, oldtxid)) + known = len(versions) + txid = check_current() + assert len(versions) == known + 1, versions + return txid + + txid = check_current() + # Each step outbids the last by enough to replace it in the mempool, + # even after smoothing and even when it uses fewer inputs and no change. + for rate in (6000, 18000): + node.set_feerates((rate,) * 4) + # An empty block makes it rebroadcast now, at the new feerate. + bitcoind.generate_block(1, needfeerate=10**9) + txid = next_version(txid) + + # Fees rise while it is down, so only the restarted node can have + # built and signed the next version. + node.stop() + node.set_feerates((rate * 3 // 2,) * 4, wait_for_effect=False) + logpos = len(node.daemon.logs) + node.start() + txid = next_version(txid) + assert node.daemon.is_in_log(r'RBF HTLC txid .* with txid {}'.format(txid), start=logpos) + + assert len(versions) == 5, versions + + +@pytest.mark.openchannel('v1') +def test_htlc_tx_rbf_without_unified_sigs(node_factory, bitcoind): + """A channel without option_unified_sigs signs its HTLC txs as before, on + a node which offers it.""" + no_unified = {'dev-force-features': '-514', 'may_reconnect': True, 'allow_warning': True} + l1, l2 = node_factory.line_graph(2, opts=[{**no_unified, + 'feerates': (3000,) * 4, + 'min-emergency-msat': 546000, + 'broken_log': 'overgrind: short signature length'}, + {**no_unified, + 'feerates': (3000,) * 4}]) + chan = only_one(l1.rpc.listpeerchannels()['channels']) + names = chan['channel_type']['names'] + assert 'unified_sigs/even' not in names and 'anchors/even' in names + + # Both upgrade. The channel keeps the type it was opened with. + for n in (l1, l2): + del n.daemon.opts['dev-force-features'] + n.restart() + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) + wait_for(lambda: only_one(l1.rpc.listpeerchannels()['channels'])['state'] == 'CHANNELD_NORMAL') + assert only_one(l1.rpc.listpeerchannels()['channels'])['channel_type']['names'] == names + + split_wallet(l1, bitcoind, 12, 5000) + offer_stuck_htlc(l1, l2) + commit = htlc_timeout_onchain(l1, bitcoind, chan['funding_txid']) + + def our_htlc_sigs(): + tx = mempool_htlc_tx(bitcoind, commit) + assert len(tx['vin']) > 1 + wit = [bytes.fromhex(w) for w in tx['vin'][0]['txinwitness']] + # SIGHASH_SINGLE|SIGHASH_ANYONECANPAY for both halves, no SIGHASH_UNIFIED. + assert wit[1][-1] == 0x83 and wit[2][-1] == 0x83, (wit[1][-1], wit[2][-1]) + return tx['txid'], wit[2] + + txid, sig = our_htlc_sigs() + l1.set_feerates((6000,) * 4) + bitcoind.generate_block(1, needfeerate=10**9) + wait_for(lambda: (mempool_htlc_tx(bitcoind, commit) or {}).get('txid') not in (None, txid)) + # The fee bump left our signature alone: it was never over the fee inputs. + newtxid, newsig = our_htlc_sigs() + assert newtxid != txid and newsig == sig