Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions channeld/channeld.c
Original file line number Diff line number Diff line change
Expand Up @@ -4369,6 +4369,13 @@ static void splice_accepter(struct peer *peer, const u8 *inmsg)
peer_failed_warn(peer->pps, &peer->channel_id,
"Splice internal error: mismatched channelid");

/* We disconnect rather than tx_abort, as for the feerate below. */
if (!chainparams->is_elements
&& !channel_has(peer->channel, OPT_UNIFIED_SIGS))
peer_failed_warn(peer->pps, &peer->channel_id,
"Splice refused: this channel does not use"
" option_unified_sigs");

if (!pubkey_eq(&peer->splicing->remote_funding_pubkey,
&peer->channel->funding_pubkey[REMOTE]))
status_info("Splice peer is rotating funding pubkey");
Expand Down
13 changes: 8 additions & 5 deletions doc/blake2b-upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,15 +169,18 @@ signature" is wrong, and an implementation that signs its own half `0xa3`
produces a transaction that is valid and malleable by a third party.

The opt-in is not a property of the commitment type, and is not the
operator's to choose: it is added to whatever channel type is negotiated,
named or implicit, whenever both peers support it. Taproot channels are the
operator's to choose: it is added to every channel type Core Lightning
proposes or accepts, so it opens no channel with a peer that does not support
it and accepts none from one. Taproot channels are the
exception and are refused, because there the commitment signature is a MuSig2
partial signature over a BIP341 digest, so opting in would be a wire change
rather than a hash type, and two sides would sign different digests.

A channel funded from coins mined before the proof of work changed, on a
channel type without the opt-in, remains replayable through its commitment
transactions. Prefer funding from coins received after the activation.
A channel opened without the opt-in, as every channel opened with
`v26.06.7-blake2b.1` to `.3` was, remains replayable through its commitment
transactions if its funding output is also valid for a node that has not
upgraded. Close it and open a new one: Core Lightning refuses to splice it,
from either side, since a splice would carry it into a new funding output.

## 5. Invoices: `option_blake2b` in the `9` field

Expand Down
12 changes: 12 additions & 0 deletions lightningd/channel_control.c
Original file line number Diff line number Diff line change
Expand Up @@ -2366,6 +2366,18 @@ static struct command_result *json_splice_init(struct command *cmd,
"Currently waiting on previous splice"
" command to finish.");

/* A splice co-signs the old funding output without the unified hash
* on such a channel, so its new funding output would be valid for a
* node that has not upgraded as well. */
if (!chainparams->is_elements
&& !channel_type_has(channel->type, OPT_UNIFIED_SIGS)
&& !cmd->ld->dev_splice_without_unified_sigs)
return command_fail(cmd,
SPLICE_INVALID_CHANNEL_STATE,
"Channel does not use option_unified_sigs:"
" close it and open a new one instead of"
" splicing");

if (command_check_only(cmd))
return command_check_done(cmd);

Expand Down
1 change: 1 addition & 0 deletions lightningd/lightningd.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ static struct lightningd *new_lightningd(const tal_t *ctx)
ld->dev_disable_commit = -1;
ld->dev_no_ping_timer = false;
ld->dev_any_channel_type = false;
ld->dev_splice_without_unified_sigs = false;
ld->dev_allow_shutdown_destination_change = false;
ld->dev_hsmd_no_preapprove_check = false;
ld->dev_hsmd_fail_preapprove = false;
Expand Down
4 changes: 4 additions & 0 deletions lightningd/lightningd.h
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,10 @@ struct lightningd {
/* Tell openingd/dualopend to accept all, allow sending any. */
bool dev_any_channel_type;

/* Let splice_init start a splice our own check would refuse, so the
* peer's refusal can be tested. */
bool dev_splice_without_unified_sigs;

/* Allow changing of shutdown output point even if dangerous */
bool dev_allow_shutdown_destination_change;

Expand Down
4 changes: 4 additions & 0 deletions lightningd/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,10 @@ static void dev_register_opts(struct lightningd *ld)
opt_set_bool,
&ld->dev_any_channel_type,
"Allow sending any channel type, and accept any");
clnopt_noarg("--dev-splice-without-unified-sigs", OPT_DEV,
opt_set_bool,
&ld->dev_splice_without_unified_sigs,
"Start a splice of a channel without option_unified_sigs");
clnopt_noarg("--dev-allow-shutdown-destination-change", OPT_DEV,
opt_set_bool,
&ld->dev_allow_shutdown_destination_change,
Expand Down
61 changes: 60 additions & 1 deletion tests/test_unified_robustness.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Wallet signing must reject foreign sighashes without taking the node down."""
from fixtures import * # noqa: F401,F403
from utils import TEST_NETWORK
from pyln.client import RpcError
from utils import TEST_NETWORK, only_one, wait_for
from psbt_patch import set_input0_sighash, set_input0_nonwitness_utxo
import pytest

Expand Down Expand Up @@ -53,3 +54,61 @@ def test_signpsbt_nonwitness_utxo_only_is_not_fatal(node_factory, bitcoind):

l1.rpc.signpsbt(patched)
assert l1.rpc.getinfo()['id'] is not None


# Keeps option_blake2b but drops option_unified_sigs, as builds before
# v26.06.7-blake2b.4 did.
NO_UNIFIED = '-514'


@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@pytest.mark.parametrize('opener_lacks_it', [False, True])
def test_new_channel_requires_unified_sigs(node_factory, opener_lacks_it):
"""Neither side opens a channel without option_unified_sigs."""
unified, plain = node_factory.get_nodes(2, opts=[{'allow_warning': True},
{'dev-force-features': NO_UNIFIED,
'allow_warning': True}])
opener, fundee = (plain, unified) if opener_lacks_it else (unified, plain)
opener.rpc.connect(fundee.info['id'], 'localhost', fundee.port)
opener.fundwallet(2000000)

with pytest.raises(RpcError, match='channel_type'):
opener.rpc.fundchannel(fundee.info['id'], 500000)
for n in (unified, plain):
assert not [c for c in n.rpc.listpeerchannels()['channels']
if c['state'] in ('CHANNELD_AWAITING_LOCKIN', 'DUALOPEND_AWAITING_LOCKIN', 'CHANNELD_NORMAL')]


def test_no_splice_without_unified_sigs(node_factory, bitcoind, executor):
"""A channel opened without option_unified_sigs is closed and reopened, never spliced."""
l1, l2 = node_factory.line_graph(2, fundamount=1000000,
opts={'dev-force-features': NO_UNIFIED,
'may_reconnect': True,
'allow_warning': True})
chan_id = l1.get_channel_id(l2)
names = only_one(l1.rpc.listpeerchannels()['channels'])['channel_type']['names']
assert 'unified_sigs/even' not 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

funds = l1.rpc.fundpsbt("111722sat", 0, 0, excess_as_change=True)
with pytest.raises(RpcError, match='does not use option_unified_sigs'):
l1.rpc.splice_init(chan_id, 100000, funds['psbt'])

# The peer refuses it too, when our own check is skipped.
l1.daemon.opts['dev-splice-without-unified-sigs'] = None
l1.restart()
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
wait_for(lambda: only_one(l1.rpc.listpeerchannels()['channels'])['state'] == 'CHANNELD_NORMAL')
# The peer warns and hangs up, which leaves our splice_init waiting, so
# do not wait on it: what matters is that no splice starts.
executor.submit(l1.rpc.splice_init, chan_id, 100000, funds['psbt'])
l2.daemon.wait_for_log('Splice refused: this channel does not use option_unified_sigs')
assert not l2.daemon.is_in_log('peer_out WIRE_SPLICE_ACK')
Loading