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,
[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,
@ -385,9 +389,12 @@ 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",
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",
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_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 ...
*/

View File

@ -1,10 +1,63 @@
#include <bitcoin/script.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)
|| is_p2sh(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:
* - 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 */

View File

@ -227,6 +227,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",
@ -238,7 +241,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",

View File

@ -809,6 +809,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
};

View File

@ -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",

View File

@ -5,6 +5,7 @@ from shutil import copyfile
from utils import (
only_one, sync_blockheight, wait_for, DEVELOPER, TIMEOUT,
account_balance, first_channel_id, basic_fee, TEST_NETWORK,
EXPERIMENTAL_FEATURES,
)
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)
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")
@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:
expected += ['option_anchor_outputs/odd']
expected += ['option_shutdown_anysegwit/odd']
expected += ['option_unknown_102/odd']
assert features == expected

View File

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