common: support opt_shutdown_anysegwit checks (EXPERIMENTAL_FEATURES).

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2021-02-24 13:23:12 +10:30
parent db2198e7b9
commit d0946b75bc
10 changed files with 164 additions and 6 deletions

View File

@@ -78,6 +78,10 @@ static const struct feature_style feature_styles[] = {
.copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT, .copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT,
[NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT, [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT,
[CHANNEL_FEATURE] = FEATURE_DONT_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, { OPT_DUAL_FUND,
.copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT, .copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT,
[NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT, [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT,
@@ -385,9 +389,12 @@ static const char *feature_name(const tal_t *ctx, size_t f)
"option_basic_mpp", "option_basic_mpp",
"option_support_large_channel", "option_support_large_channel",
"option_anchor_outputs", "option_anchor_outputs",
"option_anchors_zero_fee_htlc_tx",
NULL,
"option_shutdown_anysegwit",
}; };
if (f / 2 >= ARRAY_SIZE(fnames)) if (f / 2 >= ARRAY_SIZE(fnames) || !fnames[f / 2])
return tal_fmt(ctx, "option_unknown_%zu/%s", return tal_fmt(ctx, "option_unknown_%zu/%s",
COMPULSORY_FEATURE(f), (f & 1) ? "odd" : "even"); COMPULSORY_FEATURE(f), (f & 1) ? "odd" : "even");

View File

@@ -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_LARGE_CHANNELS 18
#define OPT_ANCHOR_OUTPUTS 20 #define OPT_ANCHOR_OUTPUTS 20
/* BOLT-4e329271a358ee52bf43ddbd96776943c5d74508 #9:
*
* | 26/27 | `option_shutdown_anysegwit` |... IN ...
*/
#define OPT_SHUTDOWN_ANYSEGWIT 26
/* BOLT-7b04b1461739c5036add61782d58ac490842d98b #9: /* BOLT-7b04b1461739c5036add61782d58ac490842d98b #9:
* | 222/223 | `option_dual_fund` | ... IN9 ... * | 222/223 | `option_dual_fund` | ... IN9 ...
*/ */

View File

@@ -1,10 +1,63 @@
#include <bitcoin/script.h> #include <bitcoin/script.h>
#include <common/shutdown_scriptpubkey.h> #include <common/shutdown_scriptpubkey.h>
bool valid_shutdown_scriptpubkey(const u8 *scriptpubkey) #include <stdio.h>
/* 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:
fprintf(stderr, "op = %u (invalid)\n", scriptpubkey[0]);
return false;
}
pushlen = scriptpubkey[1];
/* Must be all of the rest of scriptpubkey */
if (2 + pushlen != tal_bytelen(scriptpubkey)) {
fprintf(stderr, "2 + %zu != %zu\n", pushlen, tal_bytelen(scriptpubkey));
return false;
}
if (!(pushlen >= 2 && pushlen <= 40))
fprintf(stderr, "pushlen == %zu\n", pushlen);
return pushlen >= 2 && pushlen <= 40;
}
bool valid_shutdown_scriptpubkey(const u8 *scriptpubkey,
bool anysegwit)
{ {
return is_p2pkh(scriptpubkey, NULL) return is_p2pkh(scriptpubkey, NULL)
|| is_p2sh(scriptpubkey, NULL) || is_p2sh(scriptpubkey, NULL)
|| is_p2wpkh(scriptpubkey, NULL) || is_p2wpkh(scriptpubkey, NULL)
|| is_p2wsh(scriptpubkey, NULL); || is_p2wsh(scriptpubkey, NULL)
|| (anysegwit && is_valid_witnessprog(scriptpubkey));
} }

View File

@@ -16,6 +16,7 @@
* - if the `scriptpubkey` is not in one of the above forms: * - if the `scriptpubkey` is not in one of the above forms:
* - SHOULD fail the connection. * - 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 */ #endif /* LIGHTNING_COMMON_SHUTDOWN_SCRIPTPUBKEY_H */

View File

@@ -227,6 +227,9 @@ static void peer_got_shutdown(struct channel *channel, const u8 *msg)
{ {
u8 *scriptpubkey; u8 *scriptpubkey;
struct lightningd *ld = channel->peer->ld; 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)) { if (!fromwire_channeld_got_shutdown(channel, msg, &scriptpubkey)) {
channel_internal_error(channel, "bad channel_got_shutdown %s", channel_internal_error(channel, "bad channel_got_shutdown %s",
@@ -238,7 +241,7 @@ static void peer_got_shutdown(struct channel *channel, const u8 *msg)
tal_free(channel->shutdown_scriptpubkey[REMOTE]); tal_free(channel->shutdown_scriptpubkey[REMOTE]);
channel->shutdown_scriptpubkey[REMOTE] = scriptpubkey; channel->shutdown_scriptpubkey[REMOTE] = scriptpubkey;
if (!valid_shutdown_scriptpubkey(scriptpubkey)) { if (!valid_shutdown_scriptpubkey(scriptpubkey, anysegwit)) {
channel_fail_permanent(channel, channel_fail_permanent(channel,
REASON_PROTOCOL, REASON_PROTOCOL,
"Bad shutdown scriptpubkey %s", "Bad shutdown scriptpubkey %s",

View File

@@ -809,6 +809,7 @@ static struct feature_set *default_features(const tal_t *ctx)
#if EXPERIMENTAL_FEATURES #if EXPERIMENTAL_FEATURES
OPTIONAL_FEATURE(OPT_ANCHOR_OUTPUTS), OPTIONAL_FEATURE(OPT_ANCHOR_OUTPUTS),
OPTIONAL_FEATURE(OPT_ONION_MESSAGES), OPTIONAL_FEATURE(OPT_ONION_MESSAGES),
OPTIONAL_FEATURE(OPT_SHUTDOWN_ANYSEGWIT),
#endif #endif
}; };

View File

@@ -325,11 +325,15 @@ static bool setup_channel_funder(struct state *state)
static void set_remote_upfront_shutdown(struct state *state, static void set_remote_upfront_shutdown(struct state *state,
u8 *shutdown_scriptpubkey STEALS) u8 *shutdown_scriptpubkey STEALS)
{ {
bool anysegwit = feature_negotiated(state->our_features,
state->their_features,
OPT_SHUTDOWN_ANYSEGWIT);
state->upfront_shutdown_script[REMOTE] state->upfront_shutdown_script[REMOTE]
= tal_steal(state, shutdown_scriptpubkey); = tal_steal(state, shutdown_scriptpubkey);
if (shutdown_scriptpubkey if (shutdown_scriptpubkey
&& !valid_shutdown_scriptpubkey(shutdown_scriptpubkey)) && !valid_shutdown_scriptpubkey(shutdown_scriptpubkey, anysegwit))
peer_failed_err(state->pps, peer_failed_err(state->pps,
&state->channel_id, &state->channel_id,
"Unacceptable upfront_shutdown_script %s", "Unacceptable upfront_shutdown_script %s",

View File

@@ -5,6 +5,7 @@ from shutil import copyfile
from utils import ( from utils import (
only_one, sync_blockheight, wait_for, DEVELOPER, TIMEOUT, only_one, sync_blockheight, wait_for, DEVELOPER, TIMEOUT,
account_balance, first_channel_id, basic_fee, TEST_NETWORK, account_balance, first_channel_id, basic_fee, TEST_NETWORK,
EXPERIMENTAL_FEATURES,
) )
import os import os
@@ -2609,3 +2610,80 @@ def test_invalid_upfront_shutdown_script(node_factory, bitcoind, executor):
l1.rpc.connect(l2.info['id'], 'localhost', l2.port) l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
with pytest.raises(RpcError, match=r'Unacceptable upfront_shutdown_script'): with pytest.raises(RpcError, match=r'Unacceptable upfront_shutdown_script'):
l1.fundchannel(l2, 1000000, False) l1.fundchannel(l2, 1000000, False)
@unittest.skipIf(not DEVELOPER, "needs to set upfront_shutdown_script")
@pytest.mark.slow_test
def test_segwit_shutdown_script(node_factory, bitcoind, executor):
"""
Try a range of future segwit versions as shutdown scripts. We create many nodes, so this is quite slow under valgrind
"""
l1 = node_factory.get_node(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', '5128' + '00' * 0x28,
'52020000', '5228' + '00' * 0x28,
'53020000', '5328' + '00' * 0x28,
'54020000', '5428' + '00' * 0x28,
'55020000', '5528' + '00' * 0x28,
'56020000', '5628' + '00' * 0x28,
'57020000', '5728' + '00' * 0x28,
'58020000', '5828' + '00' * 0x28,
'59020000', '5928' + '00' * 0x28,
'5A020000', '5A28' + '00' * 0x28,
'5B020000', '5B28' + '00' * 0x28,
'5C020000', '5C28' + '00' * 0x28,
'5D020000', '5D28' + '00' * 0x28,
'5E020000', '5E28' + '00' * 0x28,
'5F020000', '5F28' + '00' * 0x28,
'60020000', '6028' + '00' * 0x28]
invalid = ['50020000', # Not OP_1-OP_16
'61020000', # Not OP_1-OP_16
'5102000000', # Extra bytes
'510100', # Too short
'5129' + '00' * 0x29] # Too long
if EXPERIMENTAL_FEATURES:
xsuccess = valid
xfail = invalid
else:
xsuccess = []
xfail = valid + invalid
# More efficient to create them all up-front.
nodes = node_factory.get_nodes(len(xfail) + len(xsuccess))
# Give it one UTXO to spend for each node.
addresses = {}
for n in nodes:
addresses[l1.rpc.newaddr()['bech32']] = (10**6 + 100000) / 10**8
bitcoind.rpc.sendmany("", addresses)
bitcoind.generate_block(1)
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == len(addresses))
# 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.stop()
l1.daemon.env["DEV_OPENINGD_UPFRONT_SHUTDOWN_SCRIPT"] = script
l1.start()
l2 = nodes.pop()
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
l1.rpc.fundchannel(l2.info['id'], 10**6)
for script in xfail:
# Insist on upfront script we're not going to match.
l1.stop()
l1.daemon.env["DEV_OPENINGD_UPFRONT_SHUTDOWN_SCRIPT"] = script
l1.start()
l2 = nodes.pop()
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
with pytest.raises(RpcError, match=r'Unacceptable upfront_shutdown_script'):
l1.rpc.fundchannel(l2.info['id'], 10**6)

View File

@@ -1895,6 +1895,7 @@ def test_list_features_only(node_factory):
] ]
if EXPERIMENTAL_FEATURES: if EXPERIMENTAL_FEATURES:
expected += ['option_anchor_outputs/odd'] expected += ['option_anchor_outputs/odd']
expected += ['option_shutdown_anysegwit/odd']
expected += ['option_unknown_102/odd'] expected += ['option_unknown_102/odd']
assert features == expected assert features == expected

View File

@@ -26,6 +26,8 @@ def expected_peer_features(wumbo_channels=False, extra=[]):
features += [103] features += [103]
# option_anchor_outputs # option_anchor_outputs
features += [21] features += [21]
# option_shutdown_anysegwit
features += [27]
if wumbo_channels: if wumbo_channels:
features += [19] features += [19]
return hex_bits(features + extra) return hex_bits(features + extra)
@@ -41,6 +43,8 @@ def expected_node_features(wumbo_channels=False, extra=[]):
features += [103] features += [103]
# option_anchor_outputs # option_anchor_outputs
features += [21] features += [21]
# option_shutdown_anysegwit
features += [27]
if wumbo_channels: if wumbo_channels:
features += [19] features += [19]
return hex_bits(features + extra) return hex_bits(features + extra)