Browse Source

Merge branch 'master' of github.com:spesmilo/electrum

master
ThomasV 3 years ago
parent
commit
2e797b4d77
  1. 3
      contrib/build-linux/appimage/.dockerignore
  2. 1
      contrib/build-linux/sdist/.dockerignore
  3. 6
      contrib/build-wine/.dockerignore
  4. 34
      electrum/commands.py
  5. 50
      electrum/lnutil.py
  6. 5
      electrum/lnworker.py
  7. 33
      electrum/tests/test_lnutil.py

3
contrib/build-linux/appimage/.dockerignore

@ -0,0 +1,3 @@
build/
.cache/
fresh_clone/

1
contrib/build-linux/sdist/.dockerignore vendored

@ -0,0 +1 @@
fresh_clone/

6
contrib/build-wine/.dockerignore

@ -0,0 +1,6 @@
tmp/
build/
.cache/
dist/
signed/
fresh_clone/

34
electrum/commands.py

@ -371,9 +371,17 @@ class Commands:
@command('') @command('')
async def serialize(self, jsontx): async def serialize(self, jsontx):
"""Create a transaction from json inputs. """Create a signed raw transaction from a json tx template.
Inputs must have a redeemPubkey.
Outputs must be a list of {'address':address, 'value':satoshi_amount}. Example value for "jsontx" arg: {
"inputs": [
{"prevout_hash": "9d221a69ca3997cbeaf5624d723e7dc5f829b1023078c177d37bdae95f37c539", "prevout_n": 1,
"value_sats": 1000000, "privkey": "p2wpkh:cVDXzzQg6RoCTfiKpe8MBvmm5d5cJc6JLuFApsFDKwWa6F5TVHpD"}
],
"outputs": [
{"address": "tb1q4s8z6g5jqzllkgt8a4har94wl8tg0k9m8kv5zd", "value_sats": 990000}
]
}
""" """
keypairs = {} keypairs = {}
inputs = [] # type: List[PartialTxInput] inputs = [] # type: List[PartialTxInput]
@ -386,7 +394,10 @@ class Commands:
else: else:
raise Exception("missing prevout for txin") raise Exception("missing prevout for txin")
txin = PartialTxInput(prevout=prevout) txin = PartialTxInput(prevout=prevout)
txin._trusted_value_sats = int(txin_dict.get('value', txin_dict['value_sats'])) try:
txin._trusted_value_sats = int(txin_dict.get('value') or txin_dict['value_sats'])
except KeyError:
raise Exception("missing 'value_sats' field for txin")
nsequence = txin_dict.get('nsequence', None) nsequence = txin_dict.get('nsequence', None)
if nsequence is not None: if nsequence is not None:
txin.nsequence = nsequence txin.nsequence = nsequence
@ -399,8 +410,19 @@ class Commands:
txin.script_descriptor = desc txin.script_descriptor = desc
inputs.append(txin) inputs.append(txin)
outputs = [PartialTxOutput.from_address_and_value(txout['address'], int(txout.get('value', txout['value_sats']))) outputs = [] # type: List[PartialTxOutput]
for txout in jsontx.get('outputs')] for txout_dict in jsontx.get('outputs'):
try:
txout_addr = txout_dict['address']
except KeyError:
raise Exception("missing 'address' field for txout")
try:
txout_val = int(txout_dict.get('value') or txout_dict['value_sats'])
except KeyError:
raise Exception("missing 'value_sats' field for txout")
txout = PartialTxOutput.from_address_and_value(txout_addr, txout_val)
outputs.append(txout)
tx = PartialTransaction.from_io(inputs, outputs, locktime=locktime) tx = PartialTransaction.from_io(inputs, outputs, locktime=locktime)
tx.sign(keypairs) tx.sign(keypairs)
return tx.serialize() return tx.serialize()

50
electrum/lnutil.py

@ -16,7 +16,7 @@ from .util import list_enabled_bits
from .util import ShortID as ShortChannelID from .util import ShortID as ShortChannelID
from .util import format_short_id as format_short_channel_id from .util import format_short_id as format_short_channel_id
from .crypto import sha256 from .crypto import sha256, pw_decode_with_version_and_mac
from .transaction import (Transaction, PartialTransaction, PartialTxInput, TxOutpoint, from .transaction import (Transaction, PartialTransaction, PartialTxInput, TxOutpoint,
PartialTxOutput, opcodes, TxOutput) PartialTxOutput, opcodes, TxOutput)
from .ecc import CURVE_ORDER, sig_string_from_der_sig, ECPubkey, string_to_number from .ecc import CURVE_ORDER, sig_string_from_der_sig, ECPubkey, string_to_number
@ -264,44 +264,52 @@ class ImportedChannelBackupStorage(ChannelBackupStorage):
def to_bytes(self) -> bytes: def to_bytes(self) -> bytes:
vds = BCDataStream() vds = BCDataStream()
vds.write_int16(CHANNEL_BACKUP_VERSION) vds.write_uint16(CHANNEL_BACKUP_VERSION)
vds.write_boolean(self.is_initiator) vds.write_boolean(self.is_initiator)
vds.write_bytes(self.privkey, 32) vds.write_bytes(self.privkey, 32)
vds.write_bytes(self.channel_seed, 32) vds.write_bytes(self.channel_seed, 32)
vds.write_bytes(self.node_id, 33) vds.write_bytes(self.node_id, 33)
vds.write_bytes(bfh(self.funding_txid), 32) vds.write_bytes(bfh(self.funding_txid), 32)
vds.write_int16(self.funding_index) vds.write_uint16(self.funding_index)
vds.write_string(self.funding_address) vds.write_string(self.funding_address)
vds.write_bytes(self.remote_payment_pubkey, 33) vds.write_bytes(self.remote_payment_pubkey, 33)
vds.write_bytes(self.remote_revocation_pubkey, 33) vds.write_bytes(self.remote_revocation_pubkey, 33)
vds.write_int16(self.local_delay) vds.write_uint16(self.local_delay)
vds.write_int16(self.remote_delay) vds.write_uint16(self.remote_delay)
vds.write_string(self.host) vds.write_string(self.host)
vds.write_int16(self.port) vds.write_uint16(self.port)
return bytes(vds.input) return bytes(vds.input)
@staticmethod @staticmethod
def from_bytes(s): def from_bytes(s: bytes) -> "ImportedChannelBackupStorage":
vds = BCDataStream() vds = BCDataStream()
vds.write(s) vds.write(s)
version = vds.read_int16() version = vds.read_uint16()
if version != CHANNEL_BACKUP_VERSION: if version != CHANNEL_BACKUP_VERSION:
raise Exception(f"unknown version for channel backup: {version}") raise Exception(f"unknown version for channel backup: {version}")
return ImportedChannelBackupStorage( return ImportedChannelBackupStorage(
is_initiator = vds.read_boolean(), is_initiator=vds.read_boolean(),
privkey = vds.read_bytes(32).hex(), privkey=vds.read_bytes(32),
channel_seed = vds.read_bytes(32).hex(), channel_seed=vds.read_bytes(32),
node_id = vds.read_bytes(33).hex(), node_id=vds.read_bytes(33),
funding_txid = vds.read_bytes(32).hex(), funding_txid=vds.read_bytes(32).hex(),
funding_index = vds.read_int16(), funding_index=vds.read_uint16(),
funding_address = vds.read_string(), funding_address=vds.read_string(),
remote_payment_pubkey = vds.read_bytes(33).hex(), remote_payment_pubkey=vds.read_bytes(33),
remote_revocation_pubkey = vds.read_bytes(33).hex(), remote_revocation_pubkey=vds.read_bytes(33),
local_delay = vds.read_int16(), local_delay=vds.read_uint16(),
remote_delay = vds.read_int16(), remote_delay=vds.read_uint16(),
host = vds.read_string(), host=vds.read_string(),
port = vds.read_int16()) port=vds.read_uint16(),
)
@staticmethod
def from_encrypted_str(data: str, *, password: str) -> "ImportedChannelBackupStorage":
if not data.startswith('channel_backup:'):
raise ValueError("missing or invalid magic bytes")
encrypted = data[15:]
decrypted = pw_decode_with_version_and_mac(encrypted, password)
return ImportedChannelBackupStorage.from_bytes(decrypted)
class ScriptHtlc(NamedTuple): class ScriptHtlc(NamedTuple):

5
electrum/lnworker.py

@ -2452,11 +2452,8 @@ class LNWallet(LNWorker):
raise Exception(f'Unknown channel {channel_id.hex()}') raise Exception(f'Unknown channel {channel_id.hex()}')
def import_channel_backup(self, data): def import_channel_backup(self, data):
assert data.startswith('channel_backup:')
encrypted = data[15:]
xpub = self.wallet.get_fingerprint() xpub = self.wallet.get_fingerprint()
decrypted = pw_decode_with_version_and_mac(encrypted, xpub) cb_storage = ImportedChannelBackupStorage.from_encrypted_str(data, password=xpub)
cb_storage = ImportedChannelBackupStorage.from_bytes(decrypted)
channel_id = cb_storage.channel_id() channel_id = cb_storage.channel_id()
if channel_id.hex() in self.db.get_dict("channels"): if channel_id.hex() in self.db.get_dict("channels"):
raise Exception('Channel already in wallet') raise Exception('Channel already in wallet')

33
electrum/tests/test_lnutil.py

@ -9,12 +9,15 @@ from electrum.lnutil import (RevocationStore, get_per_commitment_secret_from_see
derive_pubkey, make_htlc_tx, extract_ctn_from_tx, UnableToDeriveSecret, derive_pubkey, make_htlc_tx, extract_ctn_from_tx, UnableToDeriveSecret,
get_compressed_pubkey_from_bech32, split_host_port, ConnStringFormatError, get_compressed_pubkey_from_bech32, split_host_port, ConnStringFormatError,
ScriptHtlc, extract_nodeid, calc_fees_for_commitment_tx, UpdateAddHtlc, LnFeatures, ScriptHtlc, extract_nodeid, calc_fees_for_commitment_tx, UpdateAddHtlc, LnFeatures,
ln_compare_features, IncompatibleLightningFeatures, ChannelType) ln_compare_features, IncompatibleLightningFeatures, ChannelType,
ImportedChannelBackupStorage)
from electrum.util import bfh, MyEncoder from electrum.util import bfh, MyEncoder
from electrum.transaction import Transaction, PartialTransaction, Sighash from electrum.transaction import Transaction, PartialTransaction, Sighash
from electrum.lnworker import LNWallet from electrum.lnworker import LNWallet
from electrum.wallet import restore_wallet_from_text, Standard_Wallet
from electrum.simple_config import SimpleConfig
from . import ElectrumTestCase from . import ElectrumTestCase, as_testnet
funding_tx_id = '8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be' funding_tx_id = '8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be'
@ -903,3 +906,29 @@ class TestLNUtil(ElectrumTestCase):
# ignore unknown channel types # ignore unknown channel types
channel_type = ChannelType(0b10000000001000000000010).discard_unknown_and_check() channel_type = ChannelType(0b10000000001000000000010).discard_unknown_and_check()
self.assertEqual(ChannelType(0b10000000001000000000000), channel_type) self.assertEqual(ChannelType(0b10000000001000000000000), channel_type)
@as_testnet
async def test_decode_imported_channel_backup(self):
encrypted_cb = "channel_backup:Adn87xcGIs9H2kfp4VpsOaNKWCHX08wBoqq37l1cLYKGlJamTeoaLEwpJA81l1BXF3GP/mRxqkY+whZG9l51G8izIY/kmMSvnh0DOiZEdwaaT/1/MwEHfsEomruFqs+iW24SFJPHbMM7f80bDtIxcLfZkKmgcKBAOlcqtq+dL3U3yH74S8BDDe2L4snaxxpCjF0JjDMBx1UR/28D+QlIi+lbvv1JMaCGXf+AF1+3jLQf8+lVI+rvFdyArws6Ocsvjf+ANQeSGUwW6Nb2xICQcMRgr1DO7bO4pgGu408eYRr2v3ayJBVtnKwSwd49gF5SDSjTDAO4CCM0uj9H5RxyzH7fqotkd9J80MBr84RiBXAeXKz+Ap8608/FVqgQ9BOcn6LhuAQdE5zXpmbQyw5jUGkPvHuseR+rzthzncy01odUceqTNg=="
config = SimpleConfig({'electrum_path': self.electrum_path})
d = restore_wallet_from_text("9dk", path=None, gap_limit=2, config=config)
wallet1 = d['wallet'] # type: Standard_Wallet
decoded_cb = ImportedChannelBackupStorage.from_encrypted_str(encrypted_cb, password=wallet1.get_fingerprint())
self.assertEqual(
ImportedChannelBackupStorage(
funding_txid='97767fdefef3152319363b772914d71e5eb70e793b835c13dce20037d3ac13fe',
funding_index=1,
funding_address='tb1qfsxllwl2edccpar9jas9wsxd4vhcewlxqwmn0w27kurkme3jvkdqn4msdp',
is_initiator=True,
node_id=bfh('02bf82e22f99dcd7ac1de4aad5152ce48f0694c46ec582567f379e0adbf81e2d0f'),
privkey=bfh('7e634853dc47f0bc2f2e0d1054b302fcb414371ddbd889f29ba8aa4e8b62c772'),
host='lightning.electrum.org',
port=9739,
channel_seed=bfh('ce9bad44ff8521d9f57fd202ad7cdedceb934f0056f42d0f3aa7a576b505332a'),
local_delay=1008,
remote_delay=720,
remote_payment_pubkey=bfh('02a1bbc818e2e88847016a93c223eb4adef7bb8becb3709c75c556b6beb3afe7bd'),
remote_revocation_pubkey=bfh('022f28b7d8d1f05768ada3df1b0966083b8058e1e7197c57393e302ec118d7f0ae'),
),
decoded_cb,
)

Loading…
Cancel
Save