From d4a2e9634fcd6f9acbf7f5cc341f65972566e2d7 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 19 Apr 2019 00:37:28 +0200 Subject: [PATCH] bitcoin: disallow importing/sweeping segwit scripts with uncompressed pubkey fixes #4638 --- electrum/bitcoin.py | 7 +++++++ electrum/plugins/ledger/ledger.py | 4 ++-- electrum/tests/test_bitcoin.py | 8 +++++++- electrum/transaction.py | 8 ++------ 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/electrum/bitcoin.py b/electrum/bitcoin.py index d39c45bc6..3017dd3d4 100644 --- a/electrum/bitcoin.py +++ b/electrum/bitcoin.py @@ -527,6 +527,9 @@ WIF_SCRIPT_TYPES = { WIF_SCRIPT_TYPES_INV = inv_dict(WIF_SCRIPT_TYPES) +def is_segwit_script_type(txin_type: str) -> bool: + return txin_type in ('p2wpkh', 'p2wpkh-p2sh', 'p2wsh', 'p2wsh-p2sh') + def serialize_privkey(secret: bytes, compressed: bool, txin_type: str, internal_use: bool=False) -> str: @@ -576,6 +579,10 @@ def deserialize_privkey(key: str) -> Tuple[str, bytes, bool]: if len(vch) not in [33, 34]: raise BitcoinException('invalid vch len for WIF key: {}'.format(len(vch))) compressed = len(vch) == 34 + + if is_segwit_script_type(txin_type) and not compressed: + raise BitcoinException('only compressed public keys can be used in segwit scripts') + secret_bytes = vch[1:33] # we accept secrets outside curve range; cast into range here: secret_bytes = ecc.ECPrivkey.normalize_secret_bytes(secret_bytes) diff --git a/electrum/plugins/ledger/ledger.py b/electrum/plugins/ledger/ledger.py index 879280524..ae45374be 100644 --- a/electrum/plugins/ledger/ledger.py +++ b/electrum/plugins/ledger/ledger.py @@ -4,7 +4,7 @@ import sys import traceback from electrum import ecc -from electrum.bitcoin import TYPE_ADDRESS, int_to_hex, var_int +from electrum.bitcoin import TYPE_ADDRESS, int_to_hex, var_int, is_segwit_script_type from electrum.bip32 import BIP32Node from electrum.i18n import _ from electrum.keystore import Hardware_KeyStore @@ -518,7 +518,7 @@ class Ledger_KeyStore(Hardware_KeyStore): client = self.get_client() address_path = self.get_derivation()[2:] + "/%d/%d"%sequence self.handler.show_message(_("Showing address ...")) - segwit = Transaction.is_segwit_inputtype(txin_type) + segwit = is_segwit_script_type(txin_type) segwitNative = txin_type == 'p2wpkh' try: client.getWalletPublicKey(address_path, showOnScreen=True, segwit=segwit, segwitNative=segwitNative) diff --git a/electrum/tests/test_bitcoin.py b/electrum/tests/test_bitcoin.py index d313fbac7..101ec1b62 100644 --- a/electrum/tests/test_bitcoin.py +++ b/electrum/tests/test_bitcoin.py @@ -8,7 +8,7 @@ from electrum.bitcoin import (public_key_to_p2pkh, address_from_private_key, is_b58_address, address_to_scripthash, is_minikey, is_compressed_privkey, EncodeBase58Check, DecodeBase58Check, script_num_to_hex, push_script, add_number_to_script, int_to_hex, - opcodes, base_encode, base_decode) + opcodes, base_encode, base_decode, BitcoinException) from electrum.bip32 import (BIP32Node, convert_bip32_intpath_to_strpath, xpub_from_xprv, xpub_type, is_xprv, is_bip32_derivation, is_xpub, convert_bip32_path_to_list_of_uint32, @@ -736,6 +736,12 @@ class Test_keyImport(SequentialTestCase): self.assertEqual(priv_details['compressed'], is_compressed_privkey(priv_details['priv'])) + @needs_test_with_all_ecc_implementations + def test_segwit_uncompressed_pubkey(self): + with self.assertRaises(BitcoinException): + is_private_key("p2wpkh-p2sh:5JKXxT3wAZHcybJ9YNkuHur9vou6uuAnorBV9A8vVxGNFH5wvTW", + raise_on_error=True) + class TestBaseEncode(SequentialTestCase): diff --git a/electrum/transaction.py b/electrum/transaction.py index acf524670..2ba808532 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -39,7 +39,7 @@ from .bitcoin import (TYPE_ADDRESS, TYPE_PUBKEY, TYPE_SCRIPT, hash_160, hash160_to_p2sh, hash160_to_p2pkh, hash_to_segwit_addr, hash_encode, var_int, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC, COIN, push_script, int_to_hex, push_script, b58_address_to_hash160, - opcodes, add_number_to_script, base_decode) + opcodes, add_number_to_script, base_decode, is_segwit_script_type) from .crypto import sha256d from .keystore import xpubkey_to_address, xpubkey_to_pubkey @@ -815,11 +815,7 @@ class Transaction: if _type == 'address' and guess_for_address: _type = cls.guess_txintype_from_address(txin['address']) has_nonzero_witness = txin.get('witness', '00') not in ('00', None) - return cls.is_segwit_inputtype(_type) or has_nonzero_witness - - @classmethod - def is_segwit_inputtype(cls, txin_type): - return txin_type in ('p2wpkh', 'p2wpkh-p2sh', 'p2wsh', 'p2wsh-p2sh') + return is_segwit_script_type(_type) or has_nonzero_witness @classmethod def guess_txintype_from_address(cls, addr):