commit aaf174ef3efb7744a48bcacd805618c1523766f3
parent c1212307063723e3fa0d13c6d05798e9a5d1a111
Author: SomberNight <somber.night@protonmail.com>
Date: Tue, 25 Feb 2020 17:54:49 +0100
lnpeer: cooperative close: verify scriptpubkey matches templates
Diffstat:
2 files changed, 36 insertions(+), 24 deletions(-)
diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py
@@ -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)
diff --git a/electrum/transaction.py b/electrum/transaction.py
@@ -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