electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

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:
Melectrum/lnpeer.py | 12++++++++----
Melectrum/transaction.py | 48++++++++++++++++++++++++++++--------------------
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