diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py index 6bf73ff52..d0670a676 100644 --- a/electrum/lnchannel.py +++ b/electrum/lnchannel.py @@ -39,7 +39,7 @@ from .util import bfh, bh2u, chunks, TxMinedInfo from .invoices import PR_PAID from .bitcoin import redeem_script_to_address from .crypto import sha256, sha256d -from .transaction import Transaction, PartialTransaction, TxInput +from .transaction import Transaction, PartialTransaction, TxInput, Sighash from .logging import Logger from .lnonion import decode_onion_error, OnionFailureCode, OnionRoutingFailure from . import lnutil @@ -1101,7 +1101,7 @@ class Channel(AbstractChannel): data = self.config[LOCAL].current_htlc_signatures htlc_sigs = list(chunks(data, 64)) htlc_sig = htlc_sigs[htlc_relative_idx] - remote_htlc_sig = ecc.der_sig_from_sig_string(htlc_sig) + b'\x01' + remote_htlc_sig = ecc.der_sig_from_sig_string(htlc_sig) + Sighash.to_sigbytes(Sighash.ALL) return remote_htlc_sig def revoke_current_commitment(self): @@ -1554,7 +1554,7 @@ class Channel(AbstractChannel): assert self.signature_fits(tx) tx.sign({bh2u(self.config[LOCAL].multisig_key.pubkey): (self.config[LOCAL].multisig_key.privkey, True)}) remote_sig = self.config[LOCAL].current_commitment_signature - remote_sig = ecc.der_sig_from_sig_string(remote_sig) + b"\x01" + remote_sig = ecc.der_sig_from_sig_string(remote_sig) + Sighash.to_sigbytes(Sighash.ALL) tx.add_signature_to_txin(txin_idx=0, signing_pubkey=self.config[REMOTE].multisig_key.pubkey.hex(), sig=remote_sig.hex()) diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index b69f1783c..301269ba2 100644 --- a/electrum/lnpeer.py +++ b/electrum/lnpeer.py @@ -25,7 +25,7 @@ from .util import (bh2u, bfh, log_exceptions, ignore_exceptions, chunks, OldTask UnrelatedTransactionException) from . import transaction from .bitcoin import make_op_return -from .transaction import PartialTxOutput, match_script_against_template +from .transaction import PartialTxOutput, match_script_against_template, Sighash from .logging import Logger from .lnonion import (new_onion_packet, OnionFailureCode, calc_hops_data_for_payment, process_onion_packet, OnionPacket, construct_onion_error, OnionRoutingFailure, @@ -2039,8 +2039,8 @@ class Peer(Logger): assert our_scriptpubkey # estimate fee of closing tx dummy_sig, dummy_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=0) - our_sig = None - closing_tx = None + our_sig = None # type: Optional[bytes] + closing_tx = None # type: Optional[PartialTransaction] is_initiator = chan.constraints.is_initiator our_fee, our_fee_range = self.get_shutdown_fee_range(chan, dummy_tx, is_local) @@ -2185,11 +2185,11 @@ class Peer(Logger): closing_tx.add_signature_to_txin( txin_idx=0, signing_pubkey=chan.config[LOCAL].multisig_key.pubkey.hex(), - sig=bh2u(der_sig_from_sig_string(our_sig) + b'\x01')) + sig=bh2u(der_sig_from_sig_string(our_sig) + Sighash.to_sigbytes(Sighash.ALL))) closing_tx.add_signature_to_txin( txin_idx=0, signing_pubkey=chan.config[REMOTE].multisig_key.pubkey.hex(), - sig=bh2u(der_sig_from_sig_string(their_sig) + b'\x01')) + sig=bh2u(der_sig_from_sig_string(their_sig) + Sighash.to_sigbytes(Sighash.ALL))) # save local transaction and set state try: self.lnworker.wallet.adb.add_transaction(closing_tx) diff --git a/electrum/plugins/bitbox02/bitbox02.py b/electrum/plugins/bitbox02/bitbox02.py index 50f2e9115..6f8462295 100644 --- a/electrum/plugins/bitbox02/bitbox02.py +++ b/electrum/plugins/bitbox02/bitbox02.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Dict, Tuple, Optional, List, Any, Callable from electrum import bip32, constants from electrum.i18n import _ from electrum.keystore import Hardware_KeyStore -from electrum.transaction import PartialTransaction +from electrum.transaction import PartialTransaction, Sighash from electrum.wallet import Standard_Wallet, Multisig_Wallet, Deterministic_Wallet from electrum.util import bh2u, UserFacingException from electrum.base_wizard import ScriptTypeNotSupported, BaseWizard @@ -523,7 +523,8 @@ class BitBox02Client(HardwareClientBase): # Fill signatures if len(sigs) != len(tx.inputs()): raise Exception("Incorrect number of inputs signed.") # Should never occur - signatures = [bh2u(ecc.der_sig_from_sig_string(x[1])) + "01" for x in sigs] + sighash = Sighash.to_sigbytes(Sighash.ALL).hex() + signatures = [bh2u(ecc.der_sig_from_sig_string(x[1])) + sighash for x in sigs] tx.update_signatures(signatures) def sign_message(self, keypath: str, message: bytes, script_type: str) -> bytes: diff --git a/electrum/plugins/digitalbitbox/digitalbitbox.py b/electrum/plugins/digitalbitbox/digitalbitbox.py index 629d35b8a..2d64b2f66 100644 --- a/electrum/plugins/digitalbitbox/digitalbitbox.py +++ b/electrum/plugins/digitalbitbox/digitalbitbox.py @@ -23,7 +23,7 @@ from electrum import ecc from electrum.ecc import msg_magic from electrum.wallet import Standard_Wallet from electrum import constants -from electrum.transaction import Transaction, PartialTransaction, PartialTxInput +from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, Sighash from electrum.i18n import _ from electrum.keystore import Hardware_KeyStore from electrum.util import to_string, UserCancelled, UserFacingException, bfh @@ -645,7 +645,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): sig_r = int(signed['sig'][:64], 16) sig_s = int(signed['sig'][64:], 16) sig = ecc.der_sig_from_r_and_s(sig_r, sig_s) - sig = to_hexstr(sig) + '01' + sig = to_hexstr(sig) + Sighash.to_sigbytes(Sighash.ALL).hex() tx.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey_bytes.hex(), sig=sig) except UserCancelled: raise diff --git a/electrum/plugins/keepkey/keepkey.py b/electrum/plugins/keepkey/keepkey.py index 33ede8f6d..a2807fbcf 100644 --- a/electrum/plugins/keepkey/keepkey.py +++ b/electrum/plugins/keepkey/keepkey.py @@ -7,7 +7,7 @@ from electrum.util import bfh, bh2u, UserCancelled, UserFacingException from electrum.bip32 import BIP32Node from electrum import constants from electrum.i18n import _ -from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput +from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput, Sighash from electrum.keystore import Hardware_KeyStore from electrum.plugin import Device, runs_in_hwd_thread from electrum.base_wizard import ScriptTypeNotSupported @@ -330,7 +330,8 @@ class KeepKeyPlugin(HW_PluginBase): outputs = self.tx_outputs(tx, keystore=keystore) signatures = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime, version=tx.version)[0] - signatures = [(bh2u(x) + '01') for x in signatures] + sighash = Sighash.to_sigbytes(Sighash.ALL).hex() + signatures = [(bh2u(x) + sighash) for x in signatures] tx.update_signatures(signatures) @runs_in_hwd_thread diff --git a/electrum/plugins/safe_t/safe_t.py b/electrum/plugins/safe_t/safe_t.py index 62af36bc0..5b1bae5c1 100644 --- a/electrum/plugins/safe_t/safe_t.py +++ b/electrum/plugins/safe_t/safe_t.py @@ -8,7 +8,7 @@ from electrum.bip32 import BIP32Node from electrum import constants from electrum.i18n import _ from electrum.plugin import Device, runs_in_hwd_thread -from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput +from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput, Sighash from electrum.keystore import Hardware_KeyStore from electrum.base_wizard import ScriptTypeNotSupported @@ -300,7 +300,8 @@ class SafeTPlugin(HW_PluginBase): outputs = self.tx_outputs(tx, keystore=keystore) signatures = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime, version=tx.version)[0] - signatures = [(bh2u(x) + '01') for x in signatures] + sighash = Sighash.to_sigbytes(Sighash.ALL).hex() + signatures = [(bh2u(x) + sighash) for x in signatures] tx.update_signatures(signatures) @runs_in_hwd_thread diff --git a/electrum/plugins/trezor/trezor.py b/electrum/plugins/trezor/trezor.py index 03aa61f3d..5fdb3f50a 100644 --- a/electrum/plugins/trezor/trezor.py +++ b/electrum/plugins/trezor/trezor.py @@ -7,7 +7,7 @@ from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32 as pa from electrum import constants from electrum.i18n import _ from electrum.plugin import Device, runs_in_hwd_thread -from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput +from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput, Sighash from electrum.keystore import Hardware_KeyStore from electrum.base_wizard import ScriptTypeNotSupported, HWD_SETUP_NEW_WALLET from electrum.logging import get_logger @@ -370,7 +370,8 @@ class TrezorPlugin(HW_PluginBase): amount_unit=self.get_trezor_amount_unit(), serialize=False, prev_txes=prev_tx) - signatures = [(bh2u(x) + '01') for x in signatures] + sighash = Sighash.to_sigbytes(Sighash.ALL).hex() + signatures = [(bh2u(x) + sighash) for x in signatures] tx.update_signatures(signatures) @runs_in_hwd_thread diff --git a/electrum/tests/test_lnutil.py b/electrum/tests/test_lnutil.py index 1fb395ab4..299c8e557 100644 --- a/electrum/tests/test_lnutil.py +++ b/electrum/tests/test_lnutil.py @@ -11,7 +11,7 @@ from electrum.lnutil import (RevocationStore, get_per_commitment_secret_from_see ScriptHtlc, extract_nodeid, calc_fees_for_commitment_tx, UpdateAddHtlc, LnFeatures, ln_compare_features, IncompatibleLightningFeatures, ChannelType) from electrum.util import bh2u, bfh, MyEncoder -from electrum.transaction import Transaction, PartialTransaction +from electrum.transaction import Transaction, PartialTransaction, Sighash from electrum.lnworker import LNWallet from . import ElectrumTestCase @@ -725,7 +725,8 @@ class TestLNUtil(ElectrumTestCase): assert len(pubkey) == 33 assert len(privkey) == 33 tx.sign({bh2u(pubkey): (privkey[:-1], True)}) - tx.add_signature_to_txin(txin_idx=0, signing_pubkey=remote_pubkey.hex(), sig=remote_signature + "01") + sighash = Sighash.to_sigbytes(Sighash.ALL).hex() + tx.add_signature_to_txin(txin_idx=0, signing_pubkey=remote_pubkey.hex(), sig=remote_signature + sighash) def test_get_compressed_pubkey_from_bech32(self): self.assertEqual(b'\x03\x84\xef\x87\xd9d\xa2\xaaa7=\xff\xb8\xfe=t8[}>;\n\x13\xa8e\x8eo:\xf5Mi\xb5H', diff --git a/electrum/transaction.py b/electrum/transaction.py index 95b6ae7c4..e718841ed 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -90,19 +90,25 @@ class MissingTxInputAmount(Exception): class Sighash(IntEnum): + # note: this is not an IntFlag, as ALL|NONE != SINGLE + ALL = 1 NONE = 2 SINGLE = 3 ANYONECANPAY = 0x80 @classmethod - def is_valid(cls, sighash) -> bool: + def is_valid(cls, sighash: int) -> bool: for flag in Sighash: for base_flag in [Sighash.ALL, Sighash.NONE, Sighash.SINGLE]: if (flag & ~0x1f | base_flag) == sighash: return True return False + @classmethod + def to_sigbytes(cls, sighash: int) -> bytes: + return sighash.to_bytes(length=1, byteorder="big") + class TxOutput: scriptpubkey: bytes @@ -1940,7 +1946,7 @@ class PartialTransaction(Transaction): txin = inputs[txin_index] sighash = txin.sighash if txin.sighash is not None else Sighash.ALL if not Sighash.is_valid(sighash): - raise Exception("SIGHASH_FLAG not supported!") + raise Exception(f"SIGHASH_FLAG ({sighash}) not supported!") nHashType = int_to_hex(sighash, 4) preimage_script = self.get_preimage_script(txin) if txin.is_segwit(): @@ -1966,6 +1972,8 @@ class PartialTransaction(Transaction): nSequence = int_to_hex(txin.nsequence, 4) preimage = nVersion + hashPrevouts + hashSequence + outpoint + scriptCode + amount + nSequence + hashOutputs + nLocktime + nHashType else: + if sighash != Sighash.ALL: + raise Exception(f"SIGHASH_FLAG ({sighash}) not supported! (for legacy sighash)") txins = var_int(len(inputs)) + ''.join(txin.serialize_to_network(script_sig=bfh(preimage_script) if txin_index==k else b"").hex() for k, txin in enumerate(inputs)) txouts = var_int(len(outputs)) + ''.join(o.serialize_to_network().hex() for o in outputs) @@ -1994,12 +2002,11 @@ class PartialTransaction(Transaction): txin = self.inputs()[txin_index] txin.validate_data(for_signing=True) sighash = txin.sighash if txin.sighash is not None else Sighash.ALL - sighash_type = sighash.to_bytes(length=1, byteorder="big").hex() pre_hash = sha256d(bfh(self.serialize_preimage(txin_index, bip143_shared_txdigest_fields=bip143_shared_txdigest_fields))) privkey = ecc.ECPrivkey(privkey_bytes) sig = privkey.sign_transaction(pre_hash) - sig = bh2u(sig) + sighash_type + sig = bh2u(sig) + Sighash.to_sigbytes(sighash).hex() return sig def is_complete(self) -> bool: