diff --git a/channeld/channeld.c b/channeld/channeld.c index 498f9d9cccf1..e6b91778019d 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -186,6 +186,12 @@ struct peer { /* Send a corrupted shared_input_signature */ bool dev_splice_corrupt_shared_sig; + /* Send corrupted witnesses for our splice inputs, first */ + bool dev_splice_corrupt_input_witness; + + /* Never send splice tx_signatures first */ + bool dev_splice_sign_second; + /* Information used for reestablishment. */ bool last_was_revoke; struct changed_htlc *last_sent_commit; @@ -3164,6 +3170,18 @@ static bool do_i_sign_first(struct peer *peer, struct wally_psbt *psbt, return our_role == TX_INITIATOR; } +/* Commitments keep do_i_sign_first's order; only tx_signatures move. */ +static bool do_i_send_tx_sigs_first(struct peer *peer, struct wally_psbt *psbt, + enum tx_role our_role, + bool force_sign_first) +{ + if (peer->dev_splice_corrupt_input_witness) + return true; + if (peer->dev_splice_sign_second) + return false; + return do_i_sign_first(peer, psbt, our_role, force_sign_first); +} + static struct wally_psbt *next_splice_step(const tal_t *ctx, struct interactivetx_context *ictx) { @@ -3914,6 +3932,80 @@ static size_t count_inputs_with_exclusions(struct wally_psbt *psbt, return result; } +/* Otherwise a bad witness on one of their inputs leaves the splice + * unbroadcastable for us while they hold a valid copy. Checked as their + * tx_signatures arrive, before any of ours go out and before theirs are + * saved. */ +static void check_their_witnesses(struct peer *peer, + const struct wally_psbt *psbt, + struct witness **inws, + enum tx_role our_role, + size_t splice_funding_index, + const struct bitcoin_txid *txid) +{ + struct wally_psbt *checked; + struct bitcoin_tx *tx; + bool unified; + size_t j = 0; + + if (chainparams->is_elements) + return; + + checked = clone_psbt(tmpctx, psbt); + for (size_t i = 0; i < checked->num_inputs; i++) { + struct wally_psbt_input *in = &checked->inputs[i]; + u64 in_serial; + + /* Each digest then reads the spent output here, rather than + * hashing the whole prevtx again for every input. */ + if (in->utxo && in->index < in->utxo->num_outputs) { + const struct wally_tx_output *out + = &in->utxo->outputs[in->index]; + psbt_input_set_wit_utxo(checked, i, + tal_dup_arr(tmpctx, u8, + out->script, + out->script_len, 0), + amount_sat(out->satoshi)); + } + if (i == splice_funding_index + || !psbt_get_serial_id(&in->unknowns, &in_serial) + || in_serial % 2 == our_role) + continue; + if (j == tal_count(inws)) + peer_failed_warn(peer->pps, &peer->channel_id, + "No witness for splice input %zu", i); + psbt_finalize_input(checked, in, inws[j++]); + } + if (j != tal_count(inws)) + peer_failed_warn(peer->pps, &peer->channel_id, + "%zu too many witness elements received", + tal_count(inws) - j); + + tx = bitcoin_tx_with_psbt(tmpctx, take(checked)); + if (!tx) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Could not build splice tx %s to check witnesses", + fmt_bitcoin_txid(tmpctx, txid)); + unified = channel_type_sighash(peer->channel->type, SIGHASH_ALL) + & SIGHASH_UNIFIED; + + for (size_t i = 0; i < tx->psbt->num_inputs; i++) { + u64 in_serial; + const char *bad; + + if (i == splice_funding_index + || !psbt_get_serial_id(&tx->psbt->inputs[i].unknowns, + &in_serial) + || in_serial % 2 == our_role) + continue; + bad = psbt_input_witness_invalid(tmpctx, tx, i, unified); + if (bad) + peer_failed_err(peer->pps, &peer->channel_id, + "Bad witness for splice tx %s: %s", + fmt_bitcoin_txid(tmpctx, txid), bad); + } +} + /* Called to finish an ongoing splice OR on restart from channel_reestablish. */ static void resume_splice_negotiation(struct peer *peer, bool send_commitments, @@ -4070,6 +4162,11 @@ static void resume_splice_negotiation(struct peer *peer, outws = psbt_to_witnesses(tmpctx, current_psbt, our_role, splice_funding_index); + /* Byte 7 is inside the signature, after the item count, its length + * and the start of its encoding. */ + if (peer->dev_splice_corrupt_input_witness) + for (size_t i = 0; i < tal_count(outws); i++) + outws[i]->witness_data[7] ^= 1; sigmsg = towire_tx_signatures(tmpctx, &peer->channel_id, &inflight->outpoint.txid, outws, txsig_tlvs); @@ -4079,8 +4176,8 @@ static void resume_splice_negotiation(struct peer *peer, /* Never clear it: send_signature is false when we only receive theirs */ if (send_signature) inflight->i_sent_sigs = true; - if (do_i_sign_first(peer, current_psbt, our_role, - inflight->force_sign_first) + if (do_i_send_tx_sigs_first(peer, current_psbt, our_role, + inflight->force_sign_first) && send_signature) { msg = towire_channeld_update_inflight(NULL, current_psbt, NULL, NULL, NULL, @@ -4177,6 +4274,9 @@ static void resume_splice_negotiation(struct peer *peer, fmt_bitcoin_signature(tmpctx, their_sig), fmt_bitcoin_txid(tmpctx, &final_txid)); + check_their_witnesses(peer, current_psbt, inws, our_role, + splice_funding_index, &final_txid); + /* Set the commit_sig on the commitment tx psbt */ if (!psbt_input_set_signature(current_psbt, splice_funding_index, @@ -4271,7 +4371,6 @@ static void resume_splice_negotiation(struct peer *peer, } if (recv_signature || send_signature) { - /* We let core validate our peer's signatures are correct. */ msg = towire_channeld_update_inflight(NULL, current_psbt, NULL, NULL, NULL, inflight->locked_scid, @@ -4279,8 +4378,8 @@ static void resume_splice_negotiation(struct peer *peer, wire_sync_write(MASTER_FD, take(msg)); } - if (!do_i_sign_first(peer, current_psbt, our_role, - inflight->force_sign_first) + if (!do_i_send_tx_sigs_first(peer, current_psbt, our_role, + inflight->force_sign_first) && send_signature) { msg = towire_channeld_splice_sending_sigs(tmpctx, &final_txid); wire_sync_write(MASTER_FD, take(msg)); @@ -4936,8 +5035,8 @@ static void splice_initiator_user_finalized(struct peer *peer) new_inflight->i_sent_sigs); wire_sync_write(MASTER_FD, take(outmsg)); - sign_first = do_i_sign_first(peer, new_inflight->psbt, our_role, - peer->splicing->force_sign_first); + sign_first = do_i_send_tx_sigs_first(peer, new_inflight->psbt, our_role, + peer->splicing->force_sign_first); if (!sign_first) resume_splice_negotiation(peer, false, false, false, true, 0); @@ -5136,8 +5235,8 @@ static void splice_initiator_user_signed(struct peer *peer, const u8 *inmsg) wire_sync_write(MASTER_FD, take(outmsg)); - sign_first = do_i_sign_first(peer, inflight->psbt, TX_INITIATOR, - inflight->force_sign_first); + sign_first = do_i_send_tx_sigs_first(peer, inflight->psbt, TX_INITIATOR, + inflight->force_sign_first); audit_psbt(inflight->psbt, inflight->psbt); assert(tal_parent(inflight->psbt) != tmpctx); @@ -7139,6 +7238,8 @@ static void init_channel(struct peer *peer) &channel_type, &peer->dev_disable_commit, &peer->dev_splice_corrupt_shared_sig, + &peer->dev_splice_corrupt_input_witness, + &peer->dev_splice_sign_second, &pbases, &peer->splice_state->inflights, &peer->local_alias, diff --git a/channeld/channeld_wire.csv b/channeld/channeld_wire.csv index 069b12f4b6c3..96e3f95d0559 100644 --- a/channeld/channeld_wire.csv +++ b/channeld/channeld_wire.csv @@ -76,6 +76,8 @@ msgdata,channeld_init,upfront_shutdown_script,u8,upfront_shutdown_script_len msgdata,channeld_init,desired_type,channel_type, msgdata,channeld_init,dev_disable_commit,?u32, msgdata,channeld_init,dev_splice_corrupt_shared_sig,bool, +msgdata,channeld_init,dev_splice_corrupt_input_witness,bool, +msgdata,channeld_init,dev_splice_sign_second,bool, msgdata,channeld_init,num_penalty_bases,u32, msgdata,channeld_init,pbases,penalty_base,num_penalty_bases msgdata,channeld_init,num_inflights,u16, diff --git a/common/psbt_internal.c b/common/psbt_internal.c index 22d46852491a..4f31c0f564e3 100644 --- a/common/psbt_internal.c +++ b/common/psbt_internal.c @@ -2,9 +2,17 @@ #include #include #include +#include +#include +#include +#include #include #include #include +#include +#include +#include +#include #include @@ -169,3 +177,249 @@ psbt_to_witnesses(const tal_t *ctx, return witnesses; } + +static const struct wally_tx_output *spent_output(const struct wally_psbt *psbt, + size_t i) +{ + const struct wally_psbt_input *in = &psbt->inputs[i]; + + if (in->utxo) { + if (in->index >= in->utxo->num_outputs) + return NULL; + return &in->utxo->outputs[in->index]; + } + return in->witness_utxo; +} + +static bool bip341_keypath_digest(const struct bitcoin_tx *tx, size_t i, + u32 sighash, u8 digest[SHA256_LEN]) +{ + struct wally_map scripts, values; + bool ok = false; + + tal_wally_start(); + if (wally_map_init(tx->psbt->num_inputs, NULL, &scripts) != WALLY_OK) + abort(); + if (wally_map_init(tx->psbt->num_inputs, NULL, &values) != WALLY_OK) + abort(); + for (size_t j = 0; j < tx->psbt->num_inputs; j++) { + const struct wally_tx_output *out = spent_output(tx->psbt, j); + u64 sat; + + if (!out) + goto done; + sat = out->satoshi; + if (wally_map_add_integer(&scripts, j, out->script, + out->script_len) != WALLY_OK + || wally_map_add_integer(&values, j, (u8 *)&sat, + sizeof(sat)) != WALLY_OK) + goto done; + } + ok = wally_tx_get_input_signature_hash(tx->wtx, i, &scripts, NULL, + &values, NULL, 0, 0, + WALLY_NO_CODESEPARATOR, + NULL, 0, NULL, 0, sighash, + WALLY_SIGTYPE_SW_V1, NULL, + digest, SHA256_LEN) == WALLY_OK; +done: + wally_map_clear(&scripts); + wally_map_clear(&values); + tal_wally_end(tmpctx); + return ok; +} + +static const char *check_ecdsa(const tal_t *ctx, const struct bitcoin_tx *tx, + size_t i, const struct wally_tx_witness_item *item, + const u8 *script_code, const struct pubkey *key, + bool unified) +{ + struct bitcoin_signature sig; + + if (!signature_from_der(item->witness, item->witness_len, &sig)) + return tal_fmt(ctx, "input %zu has a bad signature encoding", + i); + if (sig.sighash_type != SIGHASH_ALL + && !(unified && sig.sighash_type == (SIGHASH_ALL|SIGHASH_UNIFIED))) + return tal_fmt(ctx, "input %zu uses hash type 0x%02x", i, + sig.sighash_type); + if (!check_tx_sig(tx, i, NULL, script_code, key, &sig)) + return tal_fmt(ctx, "input %zu has an invalid signature", i); + return NULL; +} + +/* A funding output's 2-of-2, as when the peer splices another channel in. */ +static const char *check_p2wsh_2of2(const tal_t *ctx, + const struct bitcoin_tx *tx, + size_t i, + const struct wally_tx_witness_stack *stack, + const u8 *script, bool unified) +{ + struct pubkey k1, k2; + const char *bad; + u8 *expected; + + if (stack->num_items != 4 || stack->items[0].witness_len != 0 + || tal_bytelen(script) != 71 + || !pubkey_from_der(script + 2, 33, &k1) + || !pubkey_from_der(script + 36, 33, &k2)) + return tal_fmt(ctx, "P2WSH input %zu is not a 2-of-2 spend", i); + expected = bitcoin_redeem_2of2(tmpctx, &k1, &k2); + if (!memeq(expected, tal_bytelen(expected), script, tal_bytelen(script))) + return tal_fmt(ctx, "P2WSH input %zu is not a 2-of-2 spend", i); + + /* OP_CHECKMULTISIG takes the signatures in the keys' order. */ + bad = check_ecdsa(ctx, tx, i, &stack->items[1], script, &k1, unified); + if (!bad) + bad = check_ecdsa(ctx, tx, i, &stack->items[2], script, &k2, + unified); + return bad; +} + +/* An anchor channel's to_remote output, as addpsbtinput may use. */ +static const char *check_p2wsh_to_remote(const tal_t *ctx, + const struct bitcoin_tx *tx, size_t i, + const struct wally_tx_witness_stack *stack, + const u8 *script, bool unified) +{ + size_t len = tal_bytelen(script); + struct pubkey key; + u32 csv, seq; + u8 *expected; + + if (stack->num_items != 2 || len < 37 + || !pubkey_from_der(script + 1, 33, &key)) + return tal_fmt(ctx, "P2WSH input %zu is not a to_remote spend", + i); + /* Between OP_CHECKSIGVERIFY and OP_CHECKSEQUENCEVERIFY: OP_1 to + * OP_16, or a push of up to 3 bytes. */ + if (len == 37 && script[35] >= 0x51 && script[35] <= 0x60) + csv = script[35] - 0x50; + else if (len > 37 && script[35] == len - 37 && len - 37 <= 3) { + csv = 0; + for (size_t b = 0; b < len - 37; b++) + csv |= (u32)script[36 + b] << (8 * b); + } else + return tal_fmt(ctx, "P2WSH input %zu is not a to_remote spend", + i); + expected = bitcoin_wscript_to_remote_anchored(tmpctx, &key, csv); + if (!memeq(expected, tal_bytelen(expected), script, len)) + return tal_fmt(ctx, "P2WSH input %zu is not a to_remote spend", + i); + + /* BIP68: a block based relative lock of at least csv. */ + seq = tx->wtx->inputs[i].sequence; + if ((seq & 0x80000000) || (seq & 0x00400000) || (seq & 0xffff) < csv) + return tal_fmt(ctx, "P2WSH input %zu sequence %u does not" + " satisfy its CSV of %u", i, seq, csv); + + return check_ecdsa(ctx, tx, i, &stack->items[0], script, &key, unified); +} + +const char *psbt_input_witness_invalid(const tal_t *ctx, + const struct bitcoin_tx *tx, size_t i, + bool unified) +{ + const struct wally_psbt *psbt = tx->psbt; + const struct wally_tx_witness_stack *stack; + const struct wally_tx_output *spent; + struct sha256 wsh; + u8 xonly[32]; + + if (i >= psbt->num_inputs) + return tal_fmt(ctx, "no input %zu", i); + stack = psbt->inputs[i].final_witness; + spent = spent_output(psbt, i); + if (!spent) + return tal_fmt(ctx, "input %zu has no spent output", i); + if (!stack) + return tal_fmt(ctx, "input %zu has no witness", i); + + if (is_p2wpkh(spent->script, spent->script_len, NULL)) { + const struct wally_tx_witness_item *sig_item, *key_item; + struct pubkey key; + u8 *expected; + + if (stack->num_items != 2) + return tal_fmt(ctx, "P2WPKH input %zu has %zu witness" + " items", i, stack->num_items); + sig_item = &stack->items[0]; + key_item = &stack->items[1]; + if (!pubkey_from_der(key_item->witness, key_item->witness_len, + &key)) + return tal_fmt(ctx, "P2WPKH input %zu has a bad pubkey", + i); + expected = scriptpubkey_p2wpkh(tmpctx, &key); + if (!memeq(expected, tal_bytelen(expected), + spent->script, spent->script_len)) + return tal_fmt(ctx, "P2WPKH input %zu pubkey does not" + " match its output", i); + return check_ecdsa(ctx, tx, i, sig_item, + p2wpkh_scriptcode(tmpctx, &key), &key, + unified); + } + + if (is_p2wsh(spent->script, spent->script_len, &wsh)) { + const struct wally_tx_witness_item *last; + struct sha256 h; + u8 *script; + + if (stack->num_items < 2) + return tal_fmt(ctx, "P2WSH input %zu has %zu witness" + " items", i, stack->num_items); + last = &stack->items[stack->num_items - 1]; + script = tal_dup_arr(tmpctx, u8, last->witness, + last->witness_len, 0); + sha256(&h, script, tal_bytelen(script)); + if (!sha256_eq(&h, &wsh)) + return tal_fmt(ctx, "P2WSH input %zu witness script does" + " not match its output", i); + if (is_to_remote_anchored_witness_script(script, + tal_bytelen(script))) + return check_p2wsh_to_remote(ctx, tx, i, stack, script, + unified); + return check_p2wsh_2of2(ctx, tx, i, stack, script, unified); + } + + if (is_p2tr(spent->script, spent->script_len, xonly)) { + const struct wally_tx_witness_item *sig_item; + secp256k1_xonly_pubkey key; + u8 digest[SHA256_LEN]; + u32 sighash; + + if (stack->num_items != 1) + return tal_fmt(ctx, "P2TR input %zu is not a key path" + " spend", i); + sig_item = &stack->items[0]; + if (sig_item->witness_len == 64) + sighash = WALLY_SIGHASH_DEFAULT; + else if (sig_item->witness_len == 65) + sighash = sig_item->witness[64]; + else + return tal_fmt(ctx, "P2TR input %zu has a %zu byte" + " signature", i, sig_item->witness_len); + + /* BIP341: a 65-byte signature never carries 0x00. */ + if (sig_item->witness_len == 64 || sighash == SIGHASH_ALL) { + if (!bip341_keypath_digest(tx, i, sighash, digest)) + return tal_fmt(ctx, "P2TR input %zu digest", i); + } else if (unified && sighash == (SIGHASH_ALL|SIGHASH_UNIFIED)) { + struct unified_sighash_input exec = { .script_type = 2 }; + struct sha256 d; + if (!bitcoin_tx_unified_sighash(tx, i, sighash, &exec, &d)) + return tal_fmt(ctx, "P2TR input %zu digest", i); + memcpy(digest, d.u.u8, sizeof(digest)); + } else + return tal_fmt(ctx, "P2TR input %zu uses hash type" + " 0x%02x", i, sighash); + + if (!secp256k1_xonly_pubkey_parse(secp256k1_ctx, &key, xonly) + || secp256k1_schnorrsig_verify(secp256k1_ctx, + sig_item->witness, digest, + sizeof(digest), &key) != 1) + return tal_fmt(ctx, "P2TR input %zu has an invalid" + " signature", i); + return NULL; + } + + return tal_fmt(ctx, "input %zu spends none of P2WPKH, P2WSH or P2TR", i); +} diff --git a/common/psbt_internal.h b/common/psbt_internal.h index 9b172505c9e5..1b72a3c62998 100644 --- a/common/psbt_internal.h +++ b/common/psbt_internal.h @@ -35,4 +35,22 @@ psbt_to_witnesses(const tal_t *ctx, enum tx_role side_to_stack, int input_index_to_ignore); +/* psbt_input_witness_invalid - Check a peer's witness for its input + * + * Returns NULL if the final witness of input @i of @tx->psbt is a valid + * spend of a P2WPKH output, a P2TR output by its key path, or a P2WSH + * output which is a 2-of-2 funding output or an anchor channel's + * to_remote, signed with a hash type the BOLTs allow (0x21 only if + * @unified), otherwise why not. + * + * It checks the witness against the spent output in the PSBT, not the + * chain: whether that output exists, is confirmed and is unspent, and + * whether a relative lock it carries is already satisfied, is not known + * here. + */ +struct bitcoin_tx; +const char *psbt_input_witness_invalid(const tal_t *ctx, + const struct bitcoin_tx *tx, size_t i, + bool unified); + #endif /* LIGHTNING_COMMON_PSBT_INTERNAL_H */ diff --git a/common/test/run-psbt_input_witness.c b/common/test/run-psbt_input_witness.c new file mode 100644 index 000000000000..c815242a243f --- /dev/null +++ b/common/test/run-psbt_input_witness.c @@ -0,0 +1,391 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include "../amount.c" +#include "../psbt_internal.c" +#include "../psbt_open.c" + +/* AUTOGENERATED MOCKS START */ +/* Generated stub for fromwire */ +const u8 *fromwire(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, void *copy UNNEEDED, size_t n UNNEEDED) +{ fprintf(stderr, "fromwire called!\n"); abort(); } +/* Generated stub for fromwire_bool */ +bool fromwire_bool(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_bool called!\n"); abort(); } +/* Generated stub for fromwire_fail */ +void *fromwire_fail(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_fail called!\n"); abort(); } +/* Generated stub for fromwire_secp256k1_ecdsa_signature */ +void fromwire_secp256k1_ecdsa_signature(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, + secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "fromwire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for fromwire_sha256 */ +void fromwire_sha256(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "fromwire_sha256 called!\n"); abort(); } +/* Generated stub for fromwire_tal_arrn */ +u8 *fromwire_tal_arrn(const tal_t *ctx UNNEEDED, + const u8 **cursor UNNEEDED, size_t *max UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_tal_arrn called!\n"); abort(); } +/* Generated stub for fromwire_u32 */ +u32 fromwire_u32(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u32 called!\n"); abort(); } +/* Generated stub for fromwire_u64 */ +u64 fromwire_u64(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u64 called!\n"); abort(); } +/* Generated stub for fromwire_u8 */ +u8 fromwire_u8(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u8 called!\n"); abort(); } +/* Generated stub for fromwire_u8_array */ +void fromwire_u8_array(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_u8_array called!\n"); abort(); } +/* Generated stub for pseudorand_u64_ */ +uint64_t pseudorand_u64_(uint64_t *offset UNNEEDED) +{ fprintf(stderr, "pseudorand_u64_ called!\n"); abort(); } +/* Generated stub for siphash_seed */ +const struct siphash_seed *siphash_seed(void) +{ fprintf(stderr, "siphash_seed called!\n"); abort(); } +/* Generated stub for towire */ +void towire(u8 **pptr UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED) +{ fprintf(stderr, "towire called!\n"); abort(); } +/* Generated stub for towire_bool */ +void towire_bool(u8 **pptr UNNEEDED, bool v UNNEEDED) +{ fprintf(stderr, "towire_bool called!\n"); abort(); } +/* Generated stub for towire_secp256k1_ecdsa_signature */ +void towire_secp256k1_ecdsa_signature(u8 **pptr UNNEEDED, + const secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "towire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for towire_sha256 */ +void towire_sha256(u8 **pptr UNNEEDED, const struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "towire_sha256 called!\n"); abort(); } +/* Generated stub for towire_u32 */ +void towire_u32(u8 **pptr UNNEEDED, u32 v UNNEEDED) +{ fprintf(stderr, "towire_u32 called!\n"); abort(); } +/* Generated stub for towire_u64 */ +void towire_u64(u8 **pptr UNNEEDED, u64 v UNNEEDED) +{ fprintf(stderr, "towire_u64 called!\n"); abort(); } +/* Generated stub for towire_u8 */ +void towire_u8(u8 **pptr UNNEEDED, u8 v UNNEEDED) +{ fprintf(stderr, "towire_u8 called!\n"); abort(); } +/* Generated stub for towire_u8_array */ +void towire_u8_array(u8 **pptr UNNEEDED, const u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "towire_u8_array called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + +/* BIP341 wallet-test-vectors.json, keyPathSpending[0]. */ +static const char *unsigned_tx = + "02000000097de20cbff686da83a54981d2b9bab3586f4ca7e48f57f5b5596311" + "5f3b334e9c010000000000000000d7b7cab57b1393ace2d064f4d4a2cb8af6de" + "f61273e127517d44759b6dafdd990000000000fffffffff8e1f5833843336892" + "28c5d28eac13366be082dc57441760d957275419a418420000000000ffffffff" + "f0689180aa63b30cb162a73c6d2a38b7eeda2a83ece74310fda0843ad604853b" + "0100000000feffffffaa5202bdf6d8ccd2ee0f0202afbbb7461d9264a25e5bfd" + "3c5a52ee1239e0ba6c0000000000feffffff956149bdc66faa968eb2be2d2faa" + "29718acbfe3941215893a2a3446d32acd050000000000000000000e664b9773b" + "88c09c32cb70a2a3e4da0ced63b7ba3b22f848531bbb1d5d5f4c940100000000" + "00000000e9aa6b8e6c9de67619e6a3924ae25696bb7b694bb677a632a74ef7ea" + "dfd4eabf0000000000ffffffffa778eb6a263dc090464cd125c466b5a9966772" + "0b1c110468831d058aa1b82af10100000000ffffffff0200ca9a3b0000000019" + "76a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac807840cb000000" + "0020ac9a87f5594be208f8532db38cff670c450ed2fea8fcdefcc9a663f78bab" + "962b0065cd1d"; + +static const struct { + const char *script; + u64 amount; +} spent[] = { + { "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343", 420000000 }, + { "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", 462000000 }, + { "76a914751e76e8199196d454941c45d1b3a323f1433bd688ac", 294000000 }, + { "5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e", 504000000 }, + { "512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", 630000000 }, + { "00147dd65592d0ab2fe0d0257d571abf032cd9db93dc", 378000000 }, + { "512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", 672000000 }, + { "5120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5", 546000000 }, + { "512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220", 588000000 }, +}; + +static const struct { + size_t input; + u32 hash_type; + const char *sig; +} inputs[] = { + { 0, 0x03, + "ed7c1647cb97379e76892be0cacff57ec4a7102aa24296ca39af7541246d8ff1" + "4d38958d4cc1e2e478e4d4a764bbfd835b16d4e314b72937b29833060b87276c" + "03" }, + { 1, 0x83, + "052aedffc554b41f52b521071793a6b88d6dbca9dba94cf34c83696de0c1ec35" + "ca9c5ed4ab28059bd606a4f3a657eec0bb96661d42921b5f50a95ad33675b54f" + "83" }, + { 3, 0x01, + "ff45f742a876139946a149ab4d9185574b98dc919d2eb6754f8abaa59d18b025" + "637a3aa043b91817739554f4ed2026cf8022dbd83e351ce1fabc272841d2510a" + "01" }, + { 4, 0x00, + "b4010dd48a617db09926f729e79c33ae0b4e94b79f04a1ae93ede6315eb3669d" + "e185a17d2b0ac9ee09fd4c64b678a0b61a0a86fa888a273c8511be83bfd6810f" }, + { 6, 0x02, + "a3785919a2ce3c4ce26f298c3d51619bc474ae24014bcdd31328cd8cfbab2eff" + "3395fa0a16fe5f486d12f22a9cedded5ae74feb4bbe5351346508c5405bcfee0" + "02" }, + { 7, 0x82, + "ea0c6ba90763c2d3a296ad82ba45881abb4f426b3f87af162dd24d5109edc1cd" + "d11915095ba47c3a9963dc1e6c432939872bc49212fe34c632cd3ab9fed429c4" + "82" }, + { 8, 0x81, + "bbc9584a11074e83bc8c6759ec55401f0ae7b03ef290c3139814f545b58a9f81" + "27258000874f44bc46db7646322107d4d86aec8e73b8719a61fff761d75b5dd9" + "81" }, +}; + +static struct bitcoin_tx *tx_with_witness(const char *sighex, size_t input, + bool corrupt) +{ + struct bitcoin_tx *tx = bitcoin_tx_from_hex(tmpctx, unsigned_tx, + strlen(unsigned_tx)); + struct wally_tx_witness_stack *stack; + u8 *sig = tal_hexdata(tmpctx, sighex, strlen(sighex)); + + assert(tx); + for (size_t i = 0; i < ARRAY_SIZE(spent); i++) + psbt_input_set_wit_utxo(tx->psbt, i, + tal_hexdata(tmpctx, spent[i].script, + strlen(spent[i].script)), + amount_sat(spent[i].amount)); + if (corrupt) + sig[5] ^= 1; + tal_wally_start(); + assert(wally_tx_witness_stack_init_alloc(1, &stack) == WALLY_OK); + assert(wally_tx_witness_stack_add(stack, sig, tal_bytelen(sig)) + == WALLY_OK); + assert(wally_psbt_input_set_final_witness(&tx->psbt->inputs[input], + stack) == WALLY_OK); + wally_tx_witness_stack_free(stack); + tal_wally_end(tx->psbt); + return tx; +} + +static struct privkey privkey_of(u8 b) +{ + struct privkey k; + memset(k.secret.data, b, sizeof(k.secret.data)); + return k; +} + +static struct pubkey pubkey_of(const struct privkey *k) +{ + struct pubkey p; + assert(pubkey_from_privkey(k, &p)); + return p; +} + +/* A transaction spending one 100000 sat output, of @wscript if set. */ +static struct bitcoin_tx *spend(const u8 *spk, const u8 *wscript, u32 sequence) +{ + struct bitcoin_tx *tx = bitcoin_tx(tmpctx, chainparams, 1, 1, 0); + struct bitcoin_outpoint op; + struct privkey k = privkey_of(0x33); + struct pubkey p = pubkey_of(&k); + + memset(&op.txid, 0x11, sizeof(op.txid)); + op.n = 0; + bitcoin_tx_add_input(tx, &op, sequence, NULL, AMOUNT_SAT(100000), + spk, wscript); + bitcoin_tx_add_output(tx, scriptpubkey_p2wpkh(tmpctx, &p), NULL, + AMOUNT_SAT(90000)); + return tx; +} + +static u8 *sig_item(const struct bitcoin_tx *tx, const u8 *script_code, + const struct privkey *k, enum sighash_type type) +{ + struct pubkey p = pubkey_of(k); + struct bitcoin_signature sig; + u8 der[73]; + size_t len; + + sign_tx_input(tx, 0, NULL, script_code, k, &p, type, &sig); + len = signature_to_der(der, &sig); + return tal_dup_arr(tmpctx, u8, der, len, 0); +} + +static u8 **stack_of(size_t n, ...) +{ + u8 **stack = tal_arr(tmpctx, u8 *, n); + va_list ap; + + va_start(ap, n); + for (size_t i = 0; i < n; i++) + stack[i] = va_arg(ap, u8 *); + va_end(ap); + return stack; +} + +static bool ok(struct bitcoin_tx *tx, u8 **stack, bool unified) +{ + bitcoin_tx_input_set_witness(tx, 0, stack); + return psbt_input_witness_invalid(tmpctx, tx, 0, unified) == NULL; +} + +static void test_p2wpkh(void) +{ + struct privkey k = privkey_of(0x01), other = privkey_of(0x02); + struct pubkey p = pubkey_of(&k), q = pubkey_of(&other); + const u8 *spk = scriptpubkey_p2wpkh(tmpctx, &p); + const u8 *code = p2wpkh_scriptcode(tmpctx, &p); + struct bitcoin_tx *tx = spend(spk, NULL, 0xfffffffd); + u8 *pk = tal_arr(tmpctx, u8, PUBKEY_CMPR_LEN); + u8 *qk = tal_arr(tmpctx, u8, PUBKEY_CMPR_LEN); + + pubkey_to_der(pk, &p); + pubkey_to_der(qk, &q); + assert(ok(tx, stack_of(2, sig_item(tx, code, &k, SIGHASH_ALL), pk), + false)); + assert(ok(tx, stack_of(2, sig_item(tx, code, &k, SIGHASH_ALL), pk), + true)); + assert(ok(tx, stack_of(2, sig_item(tx, code, &k, + SIGHASH_ALL|SIGHASH_UNIFIED), pk), + true)); + assert(!ok(tx, stack_of(2, sig_item(tx, code, &k, + SIGHASH_ALL|SIGHASH_UNIFIED), pk), + false)); + /* Signed by another key, and with a key not in the output. */ + assert(!ok(tx, stack_of(2, sig_item(tx, code, &other, SIGHASH_ALL), pk), + false)); + assert(!ok(tx, stack_of(2, sig_item(tx, code, &other, SIGHASH_ALL), qk), + false)); +} + +static void test_p2wsh_2of2(void) +{ + struct privkey k1 = privkey_of(0x03), k2 = privkey_of(0x04); + struct pubkey p1 = pubkey_of(&k1), p2 = pubkey_of(&k2); + const u8 *script = bitcoin_redeem_2of2(tmpctx, &p1, &p2); + struct bitcoin_tx *tx = spend(NULL, script, 0xfffffffd); + u8 *empty = tal_arr(tmpctx, u8, 0), *junk = tal_arr(tmpctx, u8, 1); + u8 *first_key = tal_arr(tmpctx, u8, PUBKEY_CMPR_LEN); + const struct privkey *a, *b; + u8 *sa, *sb; + + /* The script orders the keys; the signatures follow it. */ + pubkey_to_der(first_key, &p1); + if (memeq(script + 2, PUBKEY_CMPR_LEN, first_key, PUBKEY_CMPR_LEN)) { + a = &k1; + b = &k2; + } else { + a = &k2; + b = &k1; + } + for (int unified = 0; unified < 2; unified++) { + enum sighash_type type = unified + ? SIGHASH_ALL|SIGHASH_UNIFIED : SIGHASH_ALL; + sa = sig_item(tx, script, a, type); + sb = sig_item(tx, script, b, type); + assert(ok(tx, stack_of(4, empty, sa, sb, + tal_dup_talarr(tmpctx, u8, script)), + unified)); + assert(!ok(tx, stack_of(4, empty, sb, sa, + tal_dup_talarr(tmpctx, u8, script)), + unified)); + junk[0] = 1; + assert(!ok(tx, stack_of(4, junk, sa, sb, + tal_dup_talarr(tmpctx, u8, script)), + unified)); + } +} + +static void test_p2wsh_to_remote(void) +{ + static const u32 csvs[] = { 1, 16, 17, 0x7f, 0x80, 0x7fff }; + struct privkey k = privkey_of(0x05); + struct pubkey p = pubkey_of(&k); + + for (size_t c = 0; c < ARRAY_SIZE(csvs); c++) { + u32 csv = csvs[c]; + const u8 *script = bitcoin_wscript_to_remote_anchored(tmpctx, &p, + csv); + struct bitcoin_tx *tx; + u8 *sig; + + /* Exactly the lock, more, and the flags BIP68 adds. */ + tx = spend(NULL, script, csv); + sig = sig_item(tx, script, &k, SIGHASH_ALL); + assert(ok(tx, stack_of(2, sig, tal_dup_talarr(tmpctx, u8, script)), + false)); + + tx = spend(NULL, script, csv - 1); + sig = sig_item(tx, script, &k, SIGHASH_ALL); + assert(!ok(tx, stack_of(2, sig, tal_dup_talarr(tmpctx, u8, script)), + false)); + + tx = spend(NULL, script, csv | 0x80000000); + sig = sig_item(tx, script, &k, SIGHASH_ALL); + assert(!ok(tx, stack_of(2, sig, tal_dup_talarr(tmpctx, u8, script)), + false)); + + tx = spend(NULL, script, csv | 0x00400000); + sig = sig_item(tx, script, &k, SIGHASH_ALL); + assert(!ok(tx, stack_of(2, sig, tal_dup_talarr(tmpctx, u8, script)), + false)); + } +} + +static void test_p2wsh_unknown(void) +{ + struct privkey k = privkey_of(0x06); + struct pubkey p = pubkey_of(&k); + /* OP_CHECKSIG: valid on chain, but not one we check. */ + u8 *script = tal_arr(tmpctx, u8, 0); + struct bitcoin_tx *tx; + + tal_arr_expand(&script, PUBKEY_CMPR_LEN); + tal_resize(&script, 1 + PUBKEY_CMPR_LEN); + pubkey_to_der(script + 1, &p); + tal_arr_expand(&script, 0xac); + tx = spend(NULL, script, 0xfffffffd); + assert(!ok(tx, stack_of(2, sig_item(tx, script, &k, SIGHASH_ALL), + tal_dup_talarr(tmpctx, u8, script)), + false)); +} + +int main(int argc, char *argv[]) +{ + common_setup(argv[0]); + chainparams = chainparams_for_network("regtest"); + + for (size_t i = 0; i < ARRAY_SIZE(inputs); i++) { + /* Only these are allowed in a tx_signatures. */ + bool allowed = inputs[i].hash_type == 0x00 + || inputs[i].hash_type == 0x01; + + for (int unified = 0; unified < 2; unified++) { + struct bitcoin_tx *tx + = tx_with_witness(inputs[i].sig, + inputs[i].input, false); + const char *bad + = psbt_input_witness_invalid(tmpctx, tx, + inputs[i].input, + unified); + assert(allowed == (bad == NULL)); + if (allowed) { + tx = tx_with_witness(inputs[i].sig, + inputs[i].input, true); + assert(psbt_input_witness_invalid(tmpctx, tx, + inputs[i].input, + unified)); + } + } + } + + test_p2wpkh(); + test_p2wsh_2of2(); + test_p2wsh_to_remote(); + test_p2wsh_unknown(); + + common_shutdown(); + return 0; +} diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index d34540f0f2a8..09890a363f71 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -2031,6 +2031,8 @@ bool peer_start_channeld(struct channel *channel, ? NULL : (u32 *)&ld->dev_disable_commit, ld->dev_splice_corrupt_shared_sig, + ld->dev_splice_corrupt_input_witness, + ld->dev_splice_sign_second, pbases, cast_const2(const struct inflight **, inflights), diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index 811b49542191..44303be1cd46 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -148,6 +148,8 @@ static struct lightningd *new_lightningd(const tal_t *ctx) ld->dev_splice_coinbase_input = false; ld->dev_splice_abort_at_tx_complete = false; ld->dev_splice_corrupt_shared_sig = false; + ld->dev_splice_corrupt_input_witness = false; + ld->dev_splice_sign_second = false; ld->dev_allow_shutdown_destination_change = false; ld->dev_hsmd_no_preapprove_check = false; ld->dev_hsmd_fail_preapprove = false; diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index b60ea7bc9ae1..f4683b1a9f80 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -378,6 +378,14 @@ struct lightningd { * be tested. */ bool dev_splice_corrupt_shared_sig; + /* Send corrupted witnesses for our splice inputs, so the peer's + * check can be tested. */ + bool dev_splice_corrupt_input_witness; + + /* Never send splice tx_signatures first, so the checks we make + * before signing can be tested. */ + bool dev_splice_sign_second; + /* Allow changing of shutdown output point even if dangerous */ bool dev_allow_shutdown_destination_change; diff --git a/lightningd/options.c b/lightningd/options.c index 0294b8ff71d8..0525c9d61add 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -921,6 +921,14 @@ static void dev_register_opts(struct lightningd *ld) opt_set_bool, &ld->dev_splice_corrupt_shared_sig, "Send a corrupted shared_input_signature in splice tx_signatures"); + clnopt_noarg("--dev-splice-corrupt-input-witness", OPT_DEV, + opt_set_bool, + &ld->dev_splice_corrupt_input_witness, + "Send splice tx_signatures first, with corrupted witnesses for our inputs"); + clnopt_noarg("--dev-splice-sign-second", OPT_DEV, + opt_set_bool, + &ld->dev_splice_sign_second, + "Never send splice tx_signatures first"); clnopt_noarg("--dev-allow-shutdown-destination-change", OPT_DEV, opt_set_bool, &ld->dev_allow_shutdown_destination_change, diff --git a/tests/plugins/channeld_fakenet.c b/tests/plugins/channeld_fakenet.c index 943568b5bd3b..23a51a9c4fa6 100644 --- a/tests/plugins/channeld_fakenet.c +++ b/tests/plugins/channeld_fakenet.c @@ -1079,6 +1079,8 @@ static struct channel *handle_init(struct info *info, const u8 *init_msg) u8 *remote_upfront_shutdown_script; u32 *dev_disable_commit; bool dev_splice_corrupt_shared_sig; + bool dev_splice_corrupt_input_witness; + bool dev_splice_sign_second; struct inflight **inflights; struct short_channel_id local_alias; bool dev_splice_abort_at_tx_complete; @@ -1143,6 +1145,8 @@ static struct channel *handle_init(struct info *info, const u8 *init_msg) &channel_type, &dev_disable_commit, &dev_splice_corrupt_shared_sig, + &dev_splice_corrupt_input_witness, + &dev_splice_sign_second, &pbases, &inflights, &local_alias, diff --git a/tests/test_splicing.py b/tests/test_splicing.py index a460e3bbdeda..1983c7fa38a4 100644 --- a/tests/test_splicing.py +++ b/tests/test_splicing.py @@ -985,6 +985,146 @@ def test_splice_bad_shared_input_signature_after_ours(node_factory, bitcoind): wait_for(lambda: only_one(n.rpc.listpeerchannels()['channels'])['state'] == 'ONCHAIN') +@pytest.mark.openchannel('v1') +@pytest.mark.openchannel('v2') +@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +@pytest.mark.parametrize("l2_signs_second", [True, False]) +def test_splice_bad_input_witness(node_factory, bitcoind, l2_signs_second): + """A bad witness on the peer's input fails the channel. + + l1 sends its tx_signatures first, with a corrupted witness for its + wallet input. If l2 signs second it finds out before sending its own, + so l1 never gets the shared input signature it needs to complete the + splice. + """ + l2_opts = {'may_reconnect': True, 'allow_warning': True} + if l2_signs_second: + l2_opts['dev-splice-sign-second'] = None + l1, l2 = node_factory.line_graph(2, fundamount=1000000, + wait_for_announce=True, + opts=[{'may_reconnect': True, + 'allow_warning': True, + 'dev-splice-corrupt-input-witness': None}, + l2_opts]) + chan_id = l1.get_channel_id(l2) + + funds_result = l1.rpc.fundpsbt("109000sat", 0, 0, excess_as_change=True) + result = l1.rpc.splice_init(chan_id, 100000, funds_result['psbt']) + result = l1.rpc.splice_update(chan_id, result['psbt']) + result = l1.rpc.splice_update(chan_id, result['psbt']) + assert result['commitments_secured'] is True + result = l1.rpc.signpsbt(result['psbt']) + try: + l1.rpc.splice_signed(chan_id, result['signed_psbt']) + except RpcError: + pass + + l2.daemon.wait_for_log(r'Peer permanent failure in CHANNELD_.*Bad witness for splice tx') + assert l2.daemon.is_in_log(r'channeld-chan#[0-9]+: peer_out WIRE_TX_SIGNATURES') != l2_signs_second + assert not l2.daemon.is_in_log(r'Broadcasting splice tx') + if l2_signs_second: + return + + # l1 holds a valid splice, and l2's commitments on the old funding + # output conflict with it, so the one which reached the mempool first + # confirms. After the splice, a commitment spending it follows. + line = l1.daemon.wait_for_log(r'Broadcasting splice tx') + splice_txid = bitcoind.rpc.decoderawtransaction(line.split('Broadcasting splice tx ')[1].split()[0])['txid'] + bitcoind.generate_block(1, wait_for_mempool=1) + block = bitcoind.rpc.getblock(bitcoind.rpc.getbestblockhash(), 2) + if (splice_txid in [tx['txid'] for tx in block['tx']] + and not any(i.get('txid') == splice_txid for tx in block['tx'] for i in tx['vin'])): + bitcoind.generate_block(1, wait_for_mempool=1) + for n in (l1, l2): + wait_for(lambda: only_one(n.rpc.listpeerchannels()['channels'])['state'] == 'ONCHAIN') + + +@pytest.mark.openchannel('v1') +@pytest.mark.openchannel('v2') +@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +@pytest.mark.parametrize("corrupt", [False, True]) +def test_splice_peer_p2wsh_to_remote_input(node_factory, bitcoind, corrupt): + """The peer checks the witness of a P2WSH to_remote input.""" + l1_opts = {'may_reconnect': True, 'allow_warning': True} + l2_opts = {'may_reconnect': True, 'allow_warning': True} + if corrupt: + l1_opts['dev-splice-corrupt-input-witness'] = None + l2_opts['dev-splice-sign-second'] = None + l1, l2 = node_factory.line_graph(2, fundamount=1000000, + wait_for_announce=True, + opts=[l1_opts, l2_opts]) + l3 = node_factory.get_node() + chan_id = l1.get_channel_id(l2) + + # l3's force close leaves l1 a P2WSH to_remote output. + node_factory.join_nodes([l1, l3], fundamount=500000) + l1.stop() + l3.rpc.close(l1.info['id'], unilateraltimeout=1) + l3.wait_for_channel_onchain(l1.info['id']) + bitcoind.generate_block(100) + l1.start() + l1.daemon.wait_for_log('onchaind complete, forgetting peer') + p2wsh = only_one([o for o in l1.rpc.listfunds()['outputs'] + if o['scriptpubkey'].startswith('0020')]) + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) + wait_for(lambda: only_one(l1.rpc.listpeerchannels(l2.info['id'])['channels'])['state'] == 'CHANNELD_NORMAL') + + psbt = l1.rpc.utxopsbt("115000sat", 0, 0, + [f"{p2wsh['txid']}:{p2wsh['output']}"], + excess_as_change=True)['psbt'] + result = l1.rpc.splice_init(chan_id, 100000, psbt) + result = l1.rpc.splice_update(chan_id, result['psbt']) + result = l1.rpc.splice_update(chan_id, result['psbt']) + assert result['commitments_secured'] is True + result = l1.rpc.signpsbt(result['psbt']) + if corrupt: + try: + l1.rpc.splice_signed(chan_id, result['signed_psbt']) + except RpcError: + pass + l2.daemon.wait_for_log(r'Peer permanent failure in CHANNELD_.*Bad witness for splice tx .*: input [0-9]+ has an invalid signature') + assert not l2.daemon.is_in_log(r'channeld-chan#[0-9]+: peer_out WIRE_TX_SIGNATURES') + return + result = l1.rpc.splice_signed(chan_id, result['signed_psbt']) + + bitcoind.generate_block(6, wait_for_mempool=result['txid']) + l1.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL') + l2.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL') + assert not l2.daemon.is_in_log(r'Bad witness') + + +@pytest.mark.openchannel('v1') +@pytest.mark.openchannel('v2') +@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +def test_splice_peer_p2tr_input(node_factory, bitcoind): + """The peer checks a P2TR key path witness on our input, and accepts it.""" + l1, l2 = node_factory.line_graph(2, fundamount=1000000, + wait_for_announce=True) + chan_id = l1.get_channel_id(l2) + + # Leave l1 only P2TR outputs to splice in (withdraw keeps the + # emergency reserve back, as P2TR change). + l1.rpc.withdraw(l1.rpc.newaddr('p2tr')['p2tr'], 'all') + bitcoind.generate_block(1, wait_for_mempool=1) + sync_blockheight(bitcoind, [l1]) + wait_for(lambda: {o['scriptpubkey'][:4] for o in l1.rpc.listfunds()['outputs'] + if o['status'] == 'confirmed'} == {'5120'}) + + # Room for the fee of two P2TR inputs, should coin selection take both. + funds_result = l1.rpc.fundpsbt("115000sat", 0, 0, excess_as_change=True) + result = l1.rpc.splice_init(chan_id, 100000, funds_result['psbt']) + result = l1.rpc.splice_update(chan_id, result['psbt']) + result = l1.rpc.splice_update(chan_id, result['psbt']) + assert result['commitments_secured'] is True + result = l1.rpc.signpsbt(result['psbt']) + result = l1.rpc.splice_signed(chan_id, result['signed_psbt']) + + bitcoind.generate_block(6, wait_for_mempool=result['txid']) + l1.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL') + l2.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL') + assert not l2.daemon.is_in_log(r'Bad witness') + + @pytest.mark.openchannel('v1') @pytest.mark.openchannel('v2') @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')