Browse Source

transaction: tx.sign API change: rm hex usage

master
SomberNight 2 years ago
parent
commit
13d9677e53
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 8
      electrum/commands.py
  2. 22
      electrum/gui/qt/main_window.py
  3. 4
      electrum/gui/qt/send_tab.py
  4. 6
      electrum/gui/qt/transaction_dialog.py
  5. 14
      electrum/keystore.py
  6. 2
      electrum/lnchannel.py
  7. 6
      electrum/lnsweep.py
  8. 2
      electrum/lnutil.py
  9. 15
      electrum/transaction.py
  10. 18
      electrum/wallet.py
  11. 4
      tests/test_lnutil.py

8
electrum/commands.py

@ -422,9 +422,9 @@ class Commands:
sec = txin_dict.get('privkey') sec = txin_dict.get('privkey')
if sec: if sec:
txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec) txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec)
pubkey = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed) pubkey = ecc.ECPrivkey(privkey).get_public_key_bytes(compressed=compressed)
keypairs[pubkey] = privkey, compressed keypairs[pubkey] = privkey
desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=pubkey, script_type=txin_type) desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=pubkey.hex(), script_type=txin_type)
txin.script_descriptor = desc txin.script_descriptor = desc
inputs.append(txin) inputs.append(txin)
@ -465,7 +465,7 @@ class Commands:
if address in txins_dict.keys(): if address in txins_dict.keys():
for txin in txins_dict[address]: for txin in txins_dict[address]:
txin.script_descriptor = desc txin.script_descriptor = desc
tx.sign({pubkey.hex(): (priv2, compressed)}) tx.sign({pubkey: priv2})
return tx.serialize() return tx.serialize()

22
electrum/gui/qt/main_window.py

@ -34,7 +34,7 @@ import base64
from functools import partial from functools import partial
import queue import queue
import asyncio import asyncio
from typing import Optional, TYPE_CHECKING, Sequence, List, Union, Dict, Set from typing import Optional, TYPE_CHECKING, Sequence, List, Union, Dict, Set, Mapping
import concurrent.futures import concurrent.futures
from PyQt5.QtGui import QPixmap, QKeySequence, QIcon, QCursor, QFont, QFontMetrics from PyQt5.QtGui import QPixmap, QKeySequence, QIcon, QCursor, QFont, QFontMetrics
@ -1106,7 +1106,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self, self,
tx: Transaction, tx: Transaction,
*, *,
external_keypairs=None, external_keypairs: Mapping[bytes, bytes] = None,
payment_identifier: PaymentIdentifier = None, payment_identifier: PaymentIdentifier = None,
): ):
show_transaction(tx, parent=self, external_keypairs=external_keypairs, payment_identifier=payment_identifier) show_transaction(tx, parent=self, external_keypairs=external_keypairs, payment_identifier=payment_identifier)
@ -1269,10 +1269,24 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self.send_tab.broadcast_transaction(tx, payment_identifier=payment_identifier) self.send_tab.broadcast_transaction(tx, payment_identifier=payment_identifier)
@protected @protected
def sign_tx(self, tx, *, callback, external_keypairs, password): def sign_tx(
self,
tx: PartialTransaction,
*,
callback,
external_keypairs: Optional[Mapping[bytes, bytes]],
password,
):
self.sign_tx_with_password(tx, callback=callback, password=password, external_keypairs=external_keypairs) self.sign_tx_with_password(tx, callback=callback, password=password, external_keypairs=external_keypairs)
def sign_tx_with_password(self, tx: PartialTransaction, *, callback, password, external_keypairs=None): def sign_tx_with_password(
self,
tx: PartialTransaction,
*,
callback,
password,
external_keypairs: Mapping[bytes, bytes] = None,
):
'''Sign the transaction in a separate thread. When done, calls '''Sign the transaction in a separate thread. When done, calls
the callback with a success code of True or False. the callback with a success code of True or False.
''' '''

4
electrum/gui/qt/send_tab.py

@ -3,7 +3,7 @@
# file LICENCE or http://www.opensource.org/licenses/mit-license.php # file LICENCE or http://www.opensource.org/licenses/mit-license.php
from decimal import Decimal from decimal import Decimal
from typing import Optional, TYPE_CHECKING, Sequence, List, Callable, Union from typing import Optional, TYPE_CHECKING, Sequence, List, Callable, Union, Mapping
from PyQt5.QtCore import pyqtSignal, QPoint, QSize, Qt from PyQt5.QtCore import pyqtSignal, QPoint, QSize, Qt
from PyQt5.QtWidgets import (QLabel, QVBoxLayout, QGridLayout, QHBoxLayout, from PyQt5.QtWidgets import (QLabel, QVBoxLayout, QGridLayout, QHBoxLayout,
QWidget, QToolTip, QPushButton, QApplication) QWidget, QToolTip, QPushButton, QApplication)
@ -295,7 +295,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
outputs: List[PartialTxOutput], outputs: List[PartialTxOutput],
*, *,
nonlocal_only=False, nonlocal_only=False,
external_keypairs=None, external_keypairs: Mapping[bytes, bytes] = None,
get_coins: Callable[..., Sequence[PartialTxInput]] = None, get_coins: Callable[..., Sequence[PartialTxInput]] = None,
invoice: Optional[Invoice] = None invoice: Optional[Invoice] = None
) -> None: ) -> None:

6
electrum/gui/qt/transaction_dialog.py

@ -30,7 +30,7 @@ import copy
import datetime import datetime
import traceback import traceback
import time import time
from typing import TYPE_CHECKING, Callable, Optional, List, Union, Tuple from typing import TYPE_CHECKING, Callable, Optional, List, Union, Tuple, Mapping
from functools import partial from functools import partial
from decimal import Decimal from decimal import Decimal
@ -410,7 +410,7 @@ def show_transaction(
*, *,
parent: 'ElectrumWindow', parent: 'ElectrumWindow',
prompt_if_unsaved: bool = False, prompt_if_unsaved: bool = False,
external_keypairs=None, external_keypairs: Mapping[bytes, bytes] = None,
payment_identifier: 'PaymentIdentifier' = None, payment_identifier: 'PaymentIdentifier' = None,
): ):
try: try:
@ -438,7 +438,7 @@ class TxDialog(QDialog, MessageBoxMixin):
*, *,
parent: 'ElectrumWindow', parent: 'ElectrumWindow',
prompt_if_unsaved: bool, prompt_if_unsaved: bool,
external_keypairs=None, external_keypairs: Mapping[bytes, bytes] = None,
payment_identifier: 'PaymentIdentifier' = None, payment_identifier: 'PaymentIdentifier' = None,
): ):
'''Transactions in the wallet will show their description. '''Transactions in the wallet will show their description.

14
electrum/keystore.py

@ -107,13 +107,13 @@ class KeyStore(Logger, ABC):
"""Returns whether the keystore can be encrypted with a password.""" """Returns whether the keystore can be encrypted with a password."""
pass pass
def _get_tx_derivations(self, tx: 'PartialTransaction') -> Dict[str, Union[Sequence[int], str]]: def _get_tx_derivations(self, tx: 'PartialTransaction') -> Dict[bytes, Union[Sequence[int], str]]:
keypairs = {} keypairs = {}
for txin in tx.inputs(): for txin in tx.inputs():
keypairs.update(self._get_txin_derivations(txin)) keypairs.update(self._get_txin_derivations(txin))
return keypairs return keypairs
def _get_txin_derivations(self, txin: 'PartialTxInput') -> Dict[str, Union[Sequence[int], str]]: def _get_txin_derivations(self, txin: 'PartialTxInput') -> Dict[bytes, Union[Sequence[int], str]]:
if txin.is_complete(): if txin.is_complete():
return {} return {}
keypairs = {} keypairs = {}
@ -124,7 +124,7 @@ class KeyStore(Logger, ABC):
derivation = self.get_pubkey_derivation(pubkey, txin) derivation = self.get_pubkey_derivation(pubkey, txin)
if not derivation: if not derivation:
continue continue
keypairs[pubkey.hex()] = derivation keypairs[pubkey] = derivation
return keypairs return keypairs
def can_sign(self, tx: 'Transaction', *, ignore_watching_only=False) -> bool: def can_sign(self, tx: 'Transaction', *, ignore_watching_only=False) -> bool:
@ -237,9 +237,11 @@ class Software_KeyStore(KeyStore):
# Raise if password is not correct. # Raise if password is not correct.
self.check_password(password) self.check_password(password)
# Add private keys # Add private keys
keypairs = self._get_tx_derivations(tx) keypairs = {}
for k, v in keypairs.items(): pubkey_to_deriv_map = self._get_tx_derivations(tx)
keypairs[k] = self.get_private_key(v, password) for pubkey, deriv in pubkey_to_deriv_map.items():
privkey, is_compressed = self.get_private_key(deriv, password)
keypairs[pubkey] = privkey
# Sign # Sign
if keypairs: if keypairs:
tx.sign(keypairs) tx.sign(keypairs)

2
electrum/lnchannel.py

@ -1614,7 +1614,7 @@ class Channel(AbstractChannel):
def force_close_tx(self) -> PartialTransaction: def force_close_tx(self) -> PartialTransaction:
tx = self.get_latest_commitment(LOCAL) tx = self.get_latest_commitment(LOCAL)
assert self.signature_fits(tx) assert self.signature_fits(tx)
tx.sign({self.config[LOCAL].multisig_key.pubkey.hex(): (self.config[LOCAL].multisig_key.privkey, True)}) tx.sign({self.config[LOCAL].multisig_key.pubkey: self.config[LOCAL].multisig_key.privkey})
remote_sig = self.config[LOCAL].current_commitment_signature remote_sig = self.config[LOCAL].current_commitment_signature
remote_sig = ecc.ecdsa_der_sig_from_ecdsa_sig64(remote_sig) + Sighash.to_sigbytes(Sighash.ALL) remote_sig = ecc.ecdsa_der_sig_from_ecdsa_sig64(remote_sig) + Sighash.to_sigbytes(Sighash.ALL)
tx.add_signature_to_txin(txin_idx=0, tx.add_signature_to_txin(txin_idx=0,

6
electrum/lnsweep.py

@ -511,12 +511,12 @@ def create_sweeptx_their_ctx_to_remote(
sweep_address: str, ctx: Transaction, output_idx: int, sweep_address: str, ctx: Transaction, output_idx: int,
our_payment_privkey: ecc.ECPrivkey, our_payment_privkey: ecc.ECPrivkey,
config: SimpleConfig) -> Optional[PartialTransaction]: config: SimpleConfig) -> Optional[PartialTransaction]:
our_payment_pubkey = our_payment_privkey.get_public_key_hex(compressed=True) our_payment_pubkey = our_payment_privkey.get_public_key_bytes(compressed=True)
val = ctx.outputs()[output_idx].value val = ctx.outputs()[output_idx].value
prevout = TxOutpoint(txid=bfh(ctx.txid()), out_idx=output_idx) prevout = TxOutpoint(txid=bfh(ctx.txid()), out_idx=output_idx)
txin = PartialTxInput(prevout=prevout) txin = PartialTxInput(prevout=prevout)
txin._trusted_value_sats = val txin._trusted_value_sats = val
desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=our_payment_pubkey, script_type='p2wpkh') desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=our_payment_pubkey.hex(), script_type='p2wpkh')
txin.script_descriptor = desc txin.script_descriptor = desc
sweep_inputs = [txin] sweep_inputs = [txin]
tx_size_bytes = 110 # approx size of p2wpkh->p2wpkh tx_size_bytes = 110 # approx size of p2wpkh->p2wpkh
@ -526,7 +526,7 @@ def create_sweeptx_their_ctx_to_remote(
sweep_outputs = [PartialTxOutput.from_address_and_value(sweep_address, outvalue)] sweep_outputs = [PartialTxOutput.from_address_and_value(sweep_address, outvalue)]
sweep_tx = PartialTransaction.from_io(sweep_inputs, sweep_outputs) sweep_tx = PartialTransaction.from_io(sweep_inputs, sweep_outputs)
sweep_tx.set_rbf(True) sweep_tx.set_rbf(True)
sweep_tx.sign({our_payment_pubkey: (our_payment_privkey.get_secret_bytes(), True)}) sweep_tx.sign({our_payment_pubkey: our_payment_privkey.get_secret_bytes()})
if not sweep_tx.is_complete(): if not sweep_tx.is_complete():
raise Exception('channel close sweep tx is not complete') raise Exception('channel close sweep tx is not complete')
return sweep_tx return sweep_tx

2
electrum/lnutil.py

@ -1084,7 +1084,7 @@ def make_commitment_output_to_remote_address(remote_payment_pubkey: bytes) -> st
return bitcoin.pubkey_to_address('p2wpkh', remote_payment_pubkey.hex()) return bitcoin.pubkey_to_address('p2wpkh', remote_payment_pubkey.hex())
def sign_and_get_sig_string(tx: PartialTransaction, local_config, remote_config): def sign_and_get_sig_string(tx: PartialTransaction, local_config, remote_config):
tx.sign({local_config.multisig_key.pubkey.hex(): (local_config.multisig_key.privkey, True)}) tx.sign({local_config.multisig_key.pubkey: local_config.multisig_key.privkey})
sig = tx.inputs()[0].part_sigs[local_config.multisig_key.pubkey] sig = tx.inputs()[0].part_sigs[local_config.multisig_key.pubkey]
sig_64 = ecdsa_sig64_from_der_sig(sig[:-1]) sig_64 = ecdsa_sig64_from_der_sig(sig[:-1])
return sig_64 return sig_64

15
electrum/transaction.py

@ -33,7 +33,7 @@ import sys
import io import io
import base64 import base64
from typing import (Sequence, Union, NamedTuple, Tuple, Optional, Iterable, from typing import (Sequence, Union, NamedTuple, Tuple, Optional, Iterable,
Callable, List, Dict, Set, TYPE_CHECKING) Callable, List, Dict, Set, TYPE_CHECKING, Mapping)
from collections import defaultdict from collections import defaultdict
from enum import IntEnum from enum import IntEnum
import itertools import itertools
@ -2130,22 +2130,21 @@ class PartialTransaction(Transaction):
preimage = nVersion + txins + txouts + nLocktime + nHashType preimage = nVersion + txins + txouts + nLocktime + nHashType
return preimage return preimage
def sign(self, keypairs) -> None: def sign(self, keypairs: Mapping[bytes, bytes]) -> None:
# keypairs: pubkey_hex -> (secret_bytes, is_compressed) # keypairs: pubkey_bytes -> secret_bytes
bip143_shared_txdigest_fields = self._calc_bip143_shared_txdigest_fields() bip143_shared_txdigest_fields = self._calc_bip143_shared_txdigest_fields()
for i, txin in enumerate(self.inputs()): for i, txin in enumerate(self.inputs()):
pubkeys = [pk.hex() for pk in txin.pubkeys] for pubkey in txin.pubkeys:
for pubkey in pubkeys:
if txin.is_complete(): if txin.is_complete():
break break
if pubkey not in keypairs: if pubkey not in keypairs:
continue continue
_logger.info(f"adding signature for {pubkey}. spending utxo {txin.prevout.to_str()}") _logger.info(f"adding signature for {pubkey}. spending utxo {txin.prevout.to_str()}")
sec, compressed = keypairs[pubkey] sec = keypairs[pubkey]
sig = self.sign_txin(i, sec, bip143_shared_txdigest_fields=bip143_shared_txdigest_fields) sig = self.sign_txin(i, sec, bip143_shared_txdigest_fields=bip143_shared_txdigest_fields)
self.add_signature_to_txin(txin_idx=i, signing_pubkey=bfh(pubkey), sig=sig) self.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey, sig=sig)
_logger.debug(f"is_complete {self.is_complete()}") _logger.debug(f"tx.sign() finished. is_complete={self.is_complete()}")
self.invalidate_ser_cache() self.invalidate_ser_cache()
def sign_txin( def sign_txin(

18
electrum/wallet.py

@ -40,7 +40,7 @@ from functools import partial
from collections import defaultdict from collections import defaultdict
from numbers import Number from numbers import Number
from decimal import Decimal from decimal import Decimal
from typing import TYPE_CHECKING, List, Optional, Tuple, Union, NamedTuple, Sequence, Dict, Any, Set, Iterable from typing import TYPE_CHECKING, List, Optional, Tuple, Union, NamedTuple, Sequence, Dict, Any, Set, Iterable, Mapping
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
import itertools import itertools
import threading import threading
@ -136,20 +136,22 @@ async def _append_utxos_to_inputs(
await group.spawn(append_single_utxo(item)) await group.spawn(append_single_utxo(item))
async def sweep_preparations(privkeys, network: 'Network', imax=100): async def sweep_preparations(
privkeys: Iterable[str], network: 'Network', imax=100,
) -> Tuple[Sequence[PartialTxInput], Mapping[bytes, bytes]]:
async def find_utxos_for_privkey(txin_type, privkey, compressed): async def find_utxos_for_privkey(txin_type: str, privkey: bytes, compressed: bool):
pubkey = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed) pubkey = ecc.ECPrivkey(privkey).get_public_key_bytes(compressed=compressed)
desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=pubkey, script_type=txin_type) desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=pubkey.hex(), script_type=txin_type)
await _append_utxos_to_inputs( await _append_utxos_to_inputs(
inputs=inputs, inputs=inputs,
network=network, network=network,
script_descriptor=desc, script_descriptor=desc,
imax=imax) imax=imax)
keypairs[pubkey] = privkey, compressed keypairs[pubkey] = privkey
inputs = [] # type: List[PartialTxInput] inputs = [] # type: List[PartialTxInput]
keypairs = {} keypairs = {} # type: Dict[bytes, bytes]
async with OldTaskGroup() as group: async with OldTaskGroup() as group:
for sec in privkeys: for sec in privkeys:
txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec) txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec)
@ -169,7 +171,7 @@ async def sweep_preparations(privkeys, network: 'Network', imax=100):
async def sweep( async def sweep(
privkeys, privkeys: Iterable[str],
*, *,
network: 'Network', network: 'Network',
config: 'SimpleConfig', config: 'SimpleConfig',

4
tests/test_lnutil.py

@ -725,7 +725,7 @@ class TestLNUtil(ElectrumTestCase):
ref_commit_tx_str = '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8002c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311054a56a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c383693901483045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220' ref_commit_tx_str = '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8002c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311054a56a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c383693901483045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220'
self.assertEqual(str(our_commit_tx), ref_commit_tx_str) self.assertEqual(str(our_commit_tx), ref_commit_tx_str)
def sign_and_insert_remote_sig(self, tx: PartialTransaction, remote_pubkey, remote_signature, pubkey, privkey): def sign_and_insert_remote_sig(self, tx: PartialTransaction, remote_pubkey: bytes, remote_signature: bytes, pubkey: bytes, privkey: bytes):
assert type(remote_pubkey) is bytes assert type(remote_pubkey) is bytes
assert len(remote_pubkey) == 33 assert len(remote_pubkey) == 33
assert type(remote_signature) is bytes assert type(remote_signature) is bytes
@ -733,7 +733,7 @@ class TestLNUtil(ElectrumTestCase):
assert type(privkey) is bytes assert type(privkey) is bytes
assert len(pubkey) == 33 assert len(pubkey) == 33
assert len(privkey) == 33 assert len(privkey) == 33
tx.sign({pubkey.hex(): (privkey[:-1], True)}) tx.sign({pubkey: privkey[:-1]})
sighash = Sighash.to_sigbytes(Sighash.ALL) sighash = Sighash.to_sigbytes(Sighash.ALL)
tx.add_signature_to_txin(txin_idx=0, signing_pubkey=remote_pubkey, sig=remote_signature + sighash) tx.add_signature_to_txin(txin_idx=0, signing_pubkey=remote_pubkey, sig=remote_signature + sighash)

Loading…
Cancel
Save