diff --git a/lightningd/opening_control.c b/lightningd/opening_control.c index ae302630f071..5a6bf1cf90be 100644 --- a/lightningd/opening_control.c +++ b/lightningd/opening_control.c @@ -1084,6 +1084,18 @@ static struct command_result *json_fundchannel_complete(struct command *cmd, [*funding_txout_num].amount, fmt_amount_sat(tmpctx, fc->funding_sats)); + /* Unsigned non-segwit inputs have malleable txids. */ + for (size_t i = 0; i < funding_psbt->num_inputs; i++) { + struct wally_psbt_input *in = &funding_psbt->inputs[i]; + if (!in->witness_utxo + && !wally_map_get_integer(&in->psbt_fields, + /* PSBT_IN_FINAL_SCRIPTSIG */ 0x07)) + return command_fail(cmd, FUNDING_PSBT_INVALID, + "Input %zu is non-segwit and unsigned:" + " txid is unknown until signed", + i); + } + funding_txid = tal(cmd, struct bitcoin_txid); psbt_txid(NULL, funding_psbt, funding_txid, NULL); diff --git a/tests/test_connection.py b/tests/test_connection.py index dece3e1a87d2..216a3f36ca34 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1775,6 +1775,39 @@ def test_funding_external_wallet(node_factory, bitcoind): l3.rpc.close(l2.info["id"]) +@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +@pytest.mark.openchannel('v1') +def test_fundchannel_complete_rejects_nonsegwit_unsigned(node_factory, bitcoind): + l1, l2 = node_factory.get_nodes(2) + l1.connect(l2) + + amount = 1_000_000 + + start = l1.rpc.fundchannel_start(l2.info['id'], amount) + funding_addr = start['funding_address'] + + # P2PKH (non-segwit) input. + legacy_addr = bitcoind.rpc.getnewaddress("", "legacy") + bitcoind.rpc.sendtoaddress(legacy_addr, 0.05) + bitcoind.generate_block(1) + + utxos = bitcoind.rpc.listunspent(1, 9999999, [legacy_addr]) + assert len(utxos) > 0 + utxo = utxos[0] + + psbt = bitcoind.rpc.walletcreatefundedpsbt( + [{"txid": utxo['txid'], "vout": utxo['vout']}], + [{funding_addr: amount / 10**8}], + 0, + {"add_inputs": False} + )['psbt'] + + with pytest.raises(RpcError, match=r'non-segwit and unsigned'): + l1.rpc.fundchannel_complete(l2.info['id'], psbt) + + l1.rpc.fundchannel_cancel(l2.info['id']) + + @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') @pytest.mark.openchannel('v1') # We manually turn on dual-funding for select nodes def test_multifunding_v1_v2_mixed(node_factory, bitcoind):