lnpeer: cooperative close: verify scriptpubkey matches templates

This commit is contained in:
SomberNight 2020-02-25 17:54:49 +01:00
parent c121230706
commit aaf174ef3e
No known key found for this signature in database
GPG key ID: B33B5F232C6271E9
2 changed files with 36 additions and 24 deletions

View file

@ -25,7 +25,8 @@ from . import ecc
from .ecc import sig_string_from_r_and_s, get_r_and_s_from_sig_string, der_sig_from_sig_string
from . import constants
from .util import bh2u, bfh, log_exceptions, ignore_exceptions, chunks, SilentTaskGroup
from .transaction import Transaction, TxOutput, PartialTxOutput
from . import transaction
from .transaction import Transaction, TxOutput, PartialTxOutput, match_script_against_template
from .logging import Logger
from .lnonion import (new_onion_packet, decode_onion_error, OnionFailureCode, calc_hops_data_for_payment,
process_onion_packet, OnionPacket, construct_onion_error, OnionRoutingFailureMessage,
@ -1357,9 +1358,12 @@ class Peer(Logger):
@log_exceptions
async def on_shutdown(self, payload):
# length of scripts allowed in BOLT-02
if int.from_bytes(payload['len'], 'big') not in (3+20+2, 2+20+1, 2+20, 2+32):
raise Exception('scriptpubkey length in received shutdown message invalid: ' + str(payload['len']))
their_scriptpubkey = payload['scriptpubkey']
# BOLT-02 restrict the scriptpubkey to some templates:
if not (match_script_against_template(their_scriptpubkey, transaction.SCRIPTPUBKEY_TEMPLATE_WITNESS_V0)
or match_script_against_template(their_scriptpubkey, transaction.SCRIPTPUBKEY_TEMPLATE_P2SH)
or match_script_against_template(their_scriptpubkey, transaction.SCRIPTPUBKEY_TEMPLATE_P2PKH)):
raise Exception(f'scriptpubkey in received shutdown message does not conform to any template: {their_scriptpubkey.hex()}')
chan_id = payload['channel_id']
if chan_id in self.shutdown_received:
self.shutdown_received[chan_id].set_result(payload)

View file

@ -390,20 +390,32 @@ class OPPushDataGeneric:
OPPushDataPubkey = OPPushDataGeneric(lambda x: x in (33, 65))
# note that this does not include x_pubkeys !
SCRIPTPUBKEY_TEMPLATE_P2PKH = [opcodes.OP_DUP, opcodes.OP_HASH160,
OPPushDataGeneric(lambda x: x == 20),
opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG]
SCRIPTPUBKEY_TEMPLATE_P2SH = [opcodes.OP_HASH160, OPPushDataGeneric(lambda x: x == 20), opcodes.OP_EQUAL]
SCRIPTPUBKEY_TEMPLATE_WITNESS_V0 = [opcodes.OP_0, OPPushDataGeneric(lambda x: x in (20, 32))]
def match_decoded(decoded, to_match):
if decoded is None:
def match_script_against_template(script, template) -> bool:
"""Returns whether 'script' matches 'template'."""
if script is None:
return False
if len(decoded) != len(to_match):
# optionally decode script now:
if isinstance(script, (bytes, bytearray)):
try:
script = [x for x in script_GetOp(script)]
except MalformedBitcoinScript:
return False
if len(script) != len(template):
return False
for i in range(len(decoded)):
to_match_item = to_match[i]
decoded_item = decoded[i]
if OPPushDataGeneric.is_instance(to_match_item) and to_match_item.check_data_len(decoded_item[0]):
for i in range(len(script)):
template_item = template[i]
script_item = script[i]
if OPPushDataGeneric.is_instance(template_item) and template_item.check_data_len(script_item[0]):
continue
if to_match_item != decoded_item[0]:
if template_item != script_item[0]:
return False
return True
@ -412,28 +424,25 @@ def get_address_from_output_script(_bytes: bytes, *, net=None) -> Optional[str]:
try:
decoded = [x for x in script_GetOp(_bytes)]
except MalformedBitcoinScript:
decoded = None
return None
# p2pkh
match = [opcodes.OP_DUP, opcodes.OP_HASH160, OPPushDataGeneric(lambda x: x == 20), opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG]
if match_decoded(decoded, match):
if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_P2PKH):
return hash160_to_p2pkh(decoded[2][1], net=net)
# p2sh
match = [opcodes.OP_HASH160, OPPushDataGeneric(lambda x: x == 20), opcodes.OP_EQUAL]
if match_decoded(decoded, match):
if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_P2SH):
return hash160_to_p2sh(decoded[1][1], net=net)
# segwit address (version 0)
match = [opcodes.OP_0, OPPushDataGeneric(lambda x: x in (20, 32))]
if match_decoded(decoded, match):
if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_WITNESS_V0):
return hash_to_segwit_addr(decoded[1][1], witver=0, net=net)
# segwit address (version 1-16)
future_witness_versions = list(range(opcodes.OP_1, opcodes.OP_16 + 1))
for witver, opcode in enumerate(future_witness_versions, start=1):
match = [opcode, OPPushDataGeneric(lambda x: 2 <= x <= 40)]
if match_decoded(decoded, match):
if match_script_against_template(decoded, match):
return hash_to_segwit_addr(decoded[1][1], witver=witver, net=net)
return None
@ -1348,14 +1357,13 @@ class PartialTxInput(TxInput, PSBTSection):
except MalformedBitcoinScript:
decoded = None
# witness version 0
match = [opcodes.OP_0, OPPushDataGeneric(lambda x: x in (20, 32))]
if match_decoded(decoded, match):
if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_WITNESS_V0):
return True
# witness version 1-16
future_witness_versions = list(range(opcodes.OP_1, opcodes.OP_16 + 1))
for witver, opcode in enumerate(future_witness_versions, start=1):
match = [opcode, OPPushDataGeneric(lambda x: 2 <= x <= 40)]
if match_decoded(decoded, match):
if match_script_against_template(decoded, match):
return True
return False