Skip to content

Commit

Permalink
common: support opt_shutdown_anysegwit checks (EXPERIMENTAL_FEATURES).
Browse files Browse the repository at this point in the history
Signed-off-by: Rusty Russell <[email protected]>
  • Loading branch information
rustyrussell committed Feb 22, 2021
1 parent 25e5c65 commit ca88cd0
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 5 deletions.
6 changes: 6 additions & 0 deletions common/features.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ static const struct feature_style feature_styles[] = {
.copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT,
[NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT,
[CHANNEL_FEATURE] = FEATURE_DONT_REPRESENT } },
{ OPT_SHUTDOWN_ANYSEGWIT,
.copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT,
[NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT,
[CHANNEL_FEATURE] = FEATURE_DONT_REPRESENT } },
{ OPT_DUAL_FUND,
.copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT,
[NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT,
Expand Down Expand Up @@ -385,6 +389,8 @@ static const char *feature_name(const tal_t *ctx, size_t f)
"option_basic_mpp",
"option_support_large_channel",
"option_anchor_outputs",
"option_anchors_zero_fee_htlc_tx",
"opt_shutdown_anysegwit",
};

if (f / 2 >= ARRAY_SIZE(fnames))
Expand Down
6 changes: 6 additions & 0 deletions common/features.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ u8 *featurebits_or(const tal_t *ctx, const u8 *f1 TAKES, const u8 *f2 TAKES);
#define OPT_LARGE_CHANNELS 18
#define OPT_ANCHOR_OUTPUTS 20

/* BOLT-4e329271a358ee52bf43ddbd96776943c5d74508 #9:
*
* | 26/27 | `option_shutdown_anysegwit` |... IN ...
*/
#define OPT_SHUTDOWN_ANYSEGWIT 26

/* BOLT-7b04b1461739c5036add61782d58ac490842d98b #9:
* | 222/223 | `option_dual_fund` | ... IN9 ...
*/
Expand Down
49 changes: 47 additions & 2 deletions common/shutdown_scriptpubkey.c
Original file line number Diff line number Diff line change
@@ -1,10 +1,55 @@
#include <bitcoin/script.h>
#include <common/shutdown_scriptpubkey.h>

bool valid_shutdown_scriptpubkey(const u8 *scriptpubkey)
/* BOLT-4e329271a358ee52bf43ddbd96776943c5d74508 #2:
* 5. if (and only if) `option_shutdown_anysegwit` is negotiated:
* * `OP_1` through `OP_16` inclusive, followed by a single
* push of 2 to 40 bytes
* (witness program versions 1 through 16)
*/
static bool is_valid_witnessprog(const u8 *scriptpubkey)
{
size_t pushlen;

if (tal_bytelen(scriptpubkey) < 2)
return false;

switch (scriptpubkey[0]) {
case OP_1:
case OP_2:
case OP_3:
case OP_4:
case OP_5:
case OP_6:
case OP_7:
case OP_8:
case OP_9:
case OP_10:
case OP_11:
case OP_12:
case OP_13:
case OP_14:
case OP_15:
case OP_16:
break;
default:
return false;
}

pushlen = scriptpubkey[1];
/* Must be all of the rest of scriptpubkey */
if (2 + pushlen != tal_bytelen(scriptpubkey))
return false;

return pushlen >= 2 && pushlen <= 40;
}

bool valid_shutdown_scriptpubkey(const u8 *scriptpubkey,
bool anysegwit)
{
return is_p2pkh(scriptpubkey, NULL)
|| is_p2sh(scriptpubkey, NULL)
|| is_p2wpkh(scriptpubkey, NULL)
|| is_p2wsh(scriptpubkey, NULL);
|| is_p2wsh(scriptpubkey, NULL)
|| (anysegwit && is_valid_witnessprog(scriptpubkey));
}
3 changes: 2 additions & 1 deletion common/shutdown_scriptpubkey.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* - if the `scriptpubkey` is not in one of the above forms:
* - SHOULD fail the connection.
*/
bool valid_shutdown_scriptpubkey(const u8 *scriptpubkey);
bool valid_shutdown_scriptpubkey(const u8 *scriptpubkey,
bool anysegwit);

#endif /* LIGHTNING_COMMON_SHUTDOWN_SCRIPTPUBKEY_H */
5 changes: 4 additions & 1 deletion lightningd/channel_control.c
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ static void peer_got_shutdown(struct channel *channel, const u8 *msg)
{
u8 *scriptpubkey;
struct lightningd *ld = channel->peer->ld;
bool anysegwit = feature_negotiated(ld->our_features,
channel->peer->their_features,
OPT_SHUTDOWN_ANYSEGWIT);

if (!fromwire_channeld_got_shutdown(channel, msg, &scriptpubkey)) {
channel_internal_error(channel, "bad channel_got_shutdown %s",
Expand All @@ -231,7 +234,7 @@ static void peer_got_shutdown(struct channel *channel, const u8 *msg)
tal_free(channel->shutdown_scriptpubkey[REMOTE]);
channel->shutdown_scriptpubkey[REMOTE] = scriptpubkey;

if (!valid_shutdown_scriptpubkey(scriptpubkey)) {
if (!valid_shutdown_scriptpubkey(scriptpubkey, anysegwit)) {
channel_fail_permanent(channel,
REASON_PROTOCOL,
"Bad shutdown scriptpubkey %s",
Expand Down
1 change: 1 addition & 0 deletions lightningd/lightningd.c
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,7 @@ static struct feature_set *default_features(const tal_t *ctx)
#if EXPERIMENTAL_FEATURES
OPTIONAL_FEATURE(OPT_ANCHOR_OUTPUTS),
OPTIONAL_FEATURE(OPT_ONION_MESSAGES),
OPTIONAL_FEATURE(OPT_SHUTDOWN_ANYSEGWIT),
#endif
};

Expand Down
6 changes: 5 additions & 1 deletion openingd/openingd.c
Original file line number Diff line number Diff line change
Expand Up @@ -325,11 +325,15 @@ static bool setup_channel_funder(struct state *state)
static void set_remote_upfront_shutdown(struct state *state,
u8 *shutdown_scriptpubkey STEALS)
{
bool anysegwit = feature_negotiated(state->our_features,
state->their_features,
OPT_SHUTDOWN_ANYSEGWIT);

state->upfront_shutdown_script[REMOTE]
= tal_steal(state, shutdown_scriptpubkey);

if (shutdown_scriptpubkey
&& !valid_shutdown_scriptpubkey(shutdown_scriptpubkey))
&& !valid_shutdown_scriptpubkey(shutdown_scriptpubkey, anysegwit))
peer_failed_err(state->pps,
&state->channel_id,
"Unacceptable upfront_shutdown_script %s",
Expand Down
65 changes: 65 additions & 0 deletions tests/test_closing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from utils import (
only_one, sync_blockheight, wait_for, DEVELOPER, TIMEOUT,
account_balance, first_channel_id, basic_fee, TEST_NETWORK,
EXPERIMENTAL_FEATURES,
)

import os
Expand Down Expand Up @@ -2609,3 +2610,67 @@ def test_invalid_upfront_shutdown_script(node_factory, bitcoind, executor):
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
with pytest.raises(RpcError, match=r'Unacceptable upfront_shutdown_script'):
l1.fundchannel(l2, 1000000, False)


@unittest.skipIf(not DEVELOPER, "needs to set upfront_shutdown_script")
def test_segwit_shutdown_script(node_factory, bitcoind, executor):
l1, l2 = node_factory.line_graph(2, fundchannel=False)

l1 = node_factory.get_node(start=False, allow_warning=True)

# BOLT-4e329271a358ee52bf43ddbd96776943c5d74508 #2:
# 5. if (and only if) `option_shutdown_anysegwit` is negotiated:
# * `OP_1` through `OP_16` inclusive, followed by a single push of 2 to 40 bytes
# (witness program versions 1 through 16)
valid = ['51020000', '51280000000000000000000000000000000000000000',
'52020000', '52280000000000000000000000000000000000000000',
'53020000', '53280000000000000000000000000000000000000000',
'54020000', '54280000000000000000000000000000000000000000',
'55020000', '55280000000000000000000000000000000000000000',
'56020000', '56280000000000000000000000000000000000000000',
'57020000', '57280000000000000000000000000000000000000000',
'58020000', '58280000000000000000000000000000000000000000',
'59020000', '59280000000000000000000000000000000000000000',
'5A020000', '5A280000000000000000000000000000000000000000',
'5B020000', '5B280000000000000000000000000000000000000000',
'5C020000', '5C280000000000000000000000000000000000000000',
'5D020000', '5D280000000000000000000000000000000000000000',
'5E020000', '5E280000000000000000000000000000000000000000',
'5F020000', '5F280000000000000000000000000000000000000000',
'60020000', '60280000000000000000000000000000000000000000']
invalid = ['50020000', # Not OP_1-OP_16
'61020000', # Not OP_1-OP_16
'5102000000', # Extra bytes
'510100', # Too short
'5129000000000000000000000000000000000000000000'] # Too long

if EXPERIMENTAL_FEATURES:
xsuccess = valid
xfail = invalid
else:
xsuccess = []
xfail = valid + invalid

# FIXME: Since we don't support other non-v0 encodings, we need a protocol
# test for this (we're actually testing our upfront check, not the real
# shutdown one!),
for script in xsuccess:
# Insist on upfront script we're not going to match.
l1.daemon.env["DEV_OPENINGD_UPFRONT_SHUTDOWN_SCRIPT"] = script
l1.start()

l2 = node_factory.get_node()
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
l1.fundchannel(l2, 1000000, False)
l1.stop()

for script in xfail:
# Insist on upfront script we're not going to match.
l1.daemon.env["DEV_OPENINGD_UPFRONT_SHUTDOWN_SCRIPT"] = script
l1.start()

l2 = node_factory.get_node()
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
with pytest.raises(RpcError, match=r'Unacceptable upfront_shutdown_script'):
l1.fundchannel(l2, 1000000, False)
l1.stop()

0 comments on commit ca88cd0

Please sign in to comment.