Browse Source

cmd: add sign with privkey and wallet (#7147)

* Remove check for sign with privkey in init_cmdline

* Add with_privkey and with_wallet variants of signtransaction command

* Add unit tests for signtransaction_with_privkey and signtransaction_with_wallet commands
patch-4
Nima 3 years ago
committed by GitHub
parent
commit
13e4424922
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 39
      electrum/commands.py
  2. 26
      electrum/tests/test_commands.py
  3. 4
      run_electrum

39
electrum/commands.py

@ -33,6 +33,7 @@ import base64
import operator
import asyncio
import inspect
from collections import defaultdict
from functools import wraps, partial
from itertools import repeat
from decimal import Decimal
@ -393,21 +394,35 @@ class Commands:
tx.sign(keypairs)
return tx.serialize()
@command('wp')
async def signtransaction(self, tx, privkey=None, password=None, wallet: Abstract_Wallet = None):
"""Sign a transaction. The wallet keys will be used unless a private key is provided."""
# TODO this command should be split in two... (1) *_with_wallet, (2) *_with_privkey
@command('')
async def signtransaction_with_privkey(self, tx, privkey):
"""Sign a transaction. The provided list of private keys will be used to sign the transaction."""
tx = tx_from_any(tx)
if privkey:
txin_type, privkey2, compressed = bitcoin.deserialize_privkey(privkey)
pubkey = ecc.ECPrivkey(privkey2).get_public_key_bytes(compressed=compressed)
for txin in tx.inputs():
if txin.address and txin.address == bitcoin.pubkey_to_address(txin_type, pubkey.hex()):
txins_dict = defaultdict(list)
for txin in tx.inputs():
txins_dict[txin.address].append(txin)
if not isinstance(privkey, list):
privkey = [privkey]
for priv in privkey:
txin_type, priv2, compressed = bitcoin.deserialize_privkey(priv)
pubkey = ecc.ECPrivkey(priv2).get_public_key_bytes(compressed=compressed)
address = bitcoin.pubkey_to_address(txin_type, pubkey.hex())
if address in txins_dict.keys():
for txin in txins_dict[address]:
txin.pubkeys = [pubkey]
txin.script_type = txin_type
tx.sign({pubkey.hex(): (privkey2, compressed)})
else:
wallet.sign_transaction(tx, password)
tx.sign({pubkey.hex(): (priv2, compressed)})
return tx.serialize()
@command('wp')
async def signtransaction(self, tx, password=None, wallet: Abstract_Wallet = None):
"""Sign a transaction. The wallet keys will be used to sign the transaction."""
tx = tx_from_any(tx)
wallet.sign_transaction(tx, password)
return tx.serialize()
@command('')

26
electrum/tests/test_commands.py

@ -11,6 +11,7 @@ from electrum.simple_config import SimpleConfig
from electrum.transaction import Transaction, TxOutput, tx_from_any
from . import TestCaseForTestnet, ElectrumTestCase
from .test_wallet_vertical import WalletIntegrityHelper
class TestCommands(ElectrumTestCase):
@ -249,10 +250,29 @@ class TestCommandsTestnet(TestCaseForTestnet):
@mock.patch.object(wallet.Abstract_Wallet, 'save_db')
def test_signtransaction_without_wallet(self, mock_save_db):
dummy_wallet = restore_wallet_from_text(
'9dk', gap_limit=2, path='if_this_exists_mocking_failed_648151893', config=self.config)['wallet']
cmds = Commands(config=self.config)
unsigned_tx = "70736274ff0100a0020000000221d3645ba44f33fff6fe2666dc080279bc34b531c66888729712a80b204a32a10100000000fdffffffdd7f90d51acf98dc45ad7489316a983868c75e16bf14ffeb9eae01603a7b4da40100000000fdffffff02e8030000000000001976a9149a9ec2b35a7660c80dae38dd806fdf9b0fde68fd88ac74c11000000000001976a914f0dc093f7fb1b76cfd06610d5359d6595676cc2b88aca79b1d00000100e102000000018ba8cf9f0ff0b44c389e4a1cd25c0770636d95ccef161e313542647d435a5fd0000000006a4730440220373b3989905177f2e36d7e3d02b967d03092747fe7bbd3ba7b2c24623a88538c02207be79ee1d981060c2be6783f4946ce1bda1f64671b349ef14a4a6fecc047a71e0121030de43c5ed4c6272d20ce3becf3fb7afd5c3ccfb5d58ddfdf3047981e0b005e0dfdffffff02c0010700000000001976a9141cd3eb65bce2cae9f54544b65e46b3ad1f0b187288ac40420f00000000001976a914f0dc093f7fb1b76cfd06610d5359d6595676cc2b88ac979b1d00000100e102000000014e39236158716e91b0b2170ebe9d6b359d139e9ebfff163f2bafd0bec9890d04000000006a473044022070340deb95ca25ef86c4c7a9539b5c8f7b8351941635450311f914cd9c2f45ea02203fa7576e032ab5ae4763c78f5c2124573213c956286fd766582d9462515dc6540121033f6737e40a3a6087bc58bc5b82b427f9ed26d710b8fe2f70bfdd3d62abebcf74fdffffff02e8030000000000001976a91490350959750b3b38e451df16bd5957b7649bf5d288acac840100000000001976a914f0dc093f7fb1b76cfd06610d5359d6595676cc2b88ac979b1d00000000"
privkey = "cVtE728tULSA4gut4QWxo218q6PRsXHQAv84SXix83cuvScvGd1H"
self.assertEqual("020000000221d3645ba44f33fff6fe2666dc080279bc34b531c66888729712a80b204a32a1010000006a47304402205b30e188e30c846f98dacc714c16b7cd3a58a3fa24973d289683c9d32813e24c0220153855a29e96fb083084417ba3e3873ccaeb08435dad93773ab60716f94a36160121033f6737e40a3a6087bc58bc5b82b427f9ed26d710b8fe2f70bfdd3d62abebcf74fdffffffdd7f90d51acf98dc45ad7489316a983868c75e16bf14ffeb9eae01603a7b4da4010000006a473044022010daa3dadf53bdcb071c6eff6b8787e3f675ed61feb4fef72d0bf9d99c0162f802200e73abd880b6f2ee5fe8c0abab731f1dddeb0f60df5e050a79c365bd718da1c80121033f6737e40a3a6087bc58bc5b82b427f9ed26d710b8fe2f70bfdd3d62abebcf74fdffffff02e8030000000000001976a9149a9ec2b35a7660c80dae38dd806fdf9b0fde68fd88ac74c11000000000001976a914f0dc093f7fb1b76cfd06610d5359d6595676cc2b88aca79b1d00",
cmds._run('signtransaction', (), tx=unsigned_tx, privkey=privkey, wallet=dummy_wallet))
cmds._run('signtransaction_with_privkey', (), tx=unsigned_tx, privkey=privkey))
@mock.patch.object(wallet.Abstract_Wallet, 'save_db')
def test_signtransaction_with_wallet(self, mock_save_db):
wallet = restore_wallet_from_text('bitter grass shiver impose acquire brush forget axis eager alone wine silver',
gap_limit=2,
path='if_this_exists_mocking_failed_648151893',
config=self.config)['wallet']
# bootstrap wallet1
funding_tx = Transaction('01000000014576dacce264c24d81887642b726f5d64aa7825b21b350c7b75a57f337da6845010000006b483045022100a3f8b6155c71a98ad9986edd6161b20d24fad99b6463c23b463856c0ee54826d02200f606017fd987696ebbe5200daedde922eee264325a184d5bbda965ba5160821012102e5c473c051dae31043c335266d0ef89c1daab2f34d885cc7706b267f3269c609ffffffff0240420f00000000001600148a28bddb7f61864bdcf58b2ad13d5aeb3abc3c42a2ddb90e000000001976a914c384950342cb6f8df55175b48586838b03130fad88ac00000000')
funding_txid = funding_tx.txid()
funding_output_value = 1000000
self.assertEqual('add2535aedcbb5ba79cc2260868bb9e57f328738ca192937f2c92e0e94c19203', funding_txid)
wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
cmds = Commands(config=self.config)
unsigned_tx = "cHNidP8BAHECAAAAAQOSwZQOLsnyNykZyjiHMn/luYuGYCLMebq1y+1aU9KtAAAAAAD+////AigjAAAAAAAAFgAUaQtZqBQGAvsjzCkE7OnMTa82EFIwGw8AAAAAABYAFKwOLSKSAL/7IWftb9GWrvnWh9i7AAAAAAABAN8BAAAAAUV22sziZMJNgYh2Qrcm9dZKp4JbIbNQx7daV/M32mhFAQAAAGtIMEUCIQCj+LYVXHGpitmYbt1hYbINJPrZm2RjwjtGOFbA7lSCbQIgD2BgF/2YdpbrvlIA2u3eki7uJkMloYTVu9qWW6UWCCEBIQLlxHPAUdrjEEPDNSZtDvicHaqy802IXMdwayZ/MmnGCf////8CQEIPAAAAAAAWABSKKL3bf2GGS9z1iyrRPVrrOrw8QqLduQ4AAAAAGXapFMOElQNCy2+N9VF1tIWGg4sDEw+tiKwAAAAAIgYDD67ptKJbfbggI8qYkZJxLN1MtT09kzhZHHkJ5YGuHAwQsuNafQAAAIAAAAAAAAAAAAAiAgKFhOeJ459BORsvJ4UsoYq+wGpUEcIb41D+1h7scSDeUxCy41p9AAAAgAEAAAAAAAAAAAA="
self.assertEqual("020000000001010392c1940e2ec9f2372919ca3887327fe5b98b866022cc79bab5cbed5a53d2ad0000000000feffffff022823000000000000160014690b59a8140602fb23cc2904ece9cc4daf361052301b0f0000000000160014ac0e2d229200bffb2167ed6fd196aef9d687d8bb02473044022027e1e37172e52b2d84106663cff5bcf6e447dcb41f6483f99584cfb4de2785f4022005c72f6324ad130c78fca43fe5fc565526d1723f2c9dc3efea78f66d7ae9d4360121030faee9b4a25b7db82023ca989192712cdd4cb53d3d9338591c7909e581ae1c0c00000000",
cmds._run('signtransaction', (), tx=unsigned_tx, wallet=wallet))

4
run_electrum

@ -120,10 +120,6 @@ def init_cmdline(config_options, wallet_path, server, *, config: 'SimpleConfig')
cmdname = config.get('cmd')
cmd = known_commands[cmdname]
if cmdname == 'signtransaction' and config.get('privkey'):
cmd.requires_wallet = False
cmd.requires_password = False
if cmdname in ['payto', 'paytomany'] and config.get('unsigned'):
cmd.requires_password = False

Loading…
Cancel
Save