Merge branch 'master' of github.com:spesmilo/electrum
This commit is contained in:
3
contrib/build-linux/appimage/.dockerignore
Normal file
3
contrib/build-linux/appimage/.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
||||
build/
|
||||
.cache/
|
||||
fresh_clone/
|
||||
1
contrib/build-linux/sdist/.dockerignore
Normal file
1
contrib/build-linux/sdist/.dockerignore
Normal file
@@ -0,0 +1 @@
|
||||
fresh_clone/
|
||||
6
contrib/build-wine/.dockerignore
Normal file
6
contrib/build-wine/.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
||||
tmp/
|
||||
build/
|
||||
.cache/
|
||||
dist/
|
||||
signed/
|
||||
fresh_clone/
|
||||
@@ -371,9 +371,17 @@ class Commands:
|
||||
|
||||
@command('')
|
||||
async def serialize(self, jsontx):
|
||||
"""Create a transaction from json inputs.
|
||||
Inputs must have a redeemPubkey.
|
||||
Outputs must be a list of {'address':address, 'value':satoshi_amount}.
|
||||
"""Create a signed raw transaction from a json tx template.
|
||||
|
||||
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 = {}
|
||||
inputs = [] # type: List[PartialTxInput]
|
||||
@@ -386,7 +394,10 @@ class Commands:
|
||||
else:
|
||||
raise Exception("missing prevout for txin")
|
||||
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)
|
||||
if nsequence is not None:
|
||||
txin.nsequence = nsequence
|
||||
@@ -399,8 +410,19 @@ class Commands:
|
||||
txin.script_descriptor = desc
|
||||
inputs.append(txin)
|
||||
|
||||
outputs = [PartialTxOutput.from_address_and_value(txout['address'], int(txout.get('value', txout['value_sats'])))
|
||||
for txout in jsontx.get('outputs')]
|
||||
outputs = [] # type: List[PartialTxOutput]
|
||||
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.sign(keypairs)
|
||||
return tx.serialize()
|
||||
|
||||
@@ -16,7 +16,7 @@ from .util import list_enabled_bits
|
||||
from .util import ShortID as ShortChannelID
|
||||
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,
|
||||
PartialTxOutput, opcodes, TxOutput)
|
||||
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:
|
||||
vds = BCDataStream()
|
||||
vds.write_int16(CHANNEL_BACKUP_VERSION)
|
||||
vds.write_uint16(CHANNEL_BACKUP_VERSION)
|
||||
vds.write_boolean(self.is_initiator)
|
||||
vds.write_bytes(self.privkey, 32)
|
||||
vds.write_bytes(self.channel_seed, 32)
|
||||
vds.write_bytes(self.node_id, 33)
|
||||
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_bytes(self.remote_payment_pubkey, 33)
|
||||
vds.write_bytes(self.remote_revocation_pubkey, 33)
|
||||
vds.write_int16(self.local_delay)
|
||||
vds.write_int16(self.remote_delay)
|
||||
vds.write_uint16(self.local_delay)
|
||||
vds.write_uint16(self.remote_delay)
|
||||
vds.write_string(self.host)
|
||||
vds.write_int16(self.port)
|
||||
vds.write_uint16(self.port)
|
||||
return bytes(vds.input)
|
||||
|
||||
@staticmethod
|
||||
def from_bytes(s):
|
||||
def from_bytes(s: bytes) -> "ImportedChannelBackupStorage":
|
||||
vds = BCDataStream()
|
||||
vds.write(s)
|
||||
version = vds.read_int16()
|
||||
version = vds.read_uint16()
|
||||
if version != CHANNEL_BACKUP_VERSION:
|
||||
raise Exception(f"unknown version for channel backup: {version}")
|
||||
return ImportedChannelBackupStorage(
|
||||
is_initiator = vds.read_boolean(),
|
||||
privkey = vds.read_bytes(32).hex(),
|
||||
channel_seed = vds.read_bytes(32).hex(),
|
||||
node_id = vds.read_bytes(33).hex(),
|
||||
funding_txid = vds.read_bytes(32).hex(),
|
||||
funding_index = vds.read_int16(),
|
||||
funding_address = vds.read_string(),
|
||||
remote_payment_pubkey = vds.read_bytes(33).hex(),
|
||||
remote_revocation_pubkey = vds.read_bytes(33).hex(),
|
||||
local_delay = vds.read_int16(),
|
||||
remote_delay = vds.read_int16(),
|
||||
host = vds.read_string(),
|
||||
port = vds.read_int16())
|
||||
is_initiator=vds.read_boolean(),
|
||||
privkey=vds.read_bytes(32),
|
||||
channel_seed=vds.read_bytes(32),
|
||||
node_id=vds.read_bytes(33),
|
||||
funding_txid=vds.read_bytes(32).hex(),
|
||||
funding_index=vds.read_uint16(),
|
||||
funding_address=vds.read_string(),
|
||||
remote_payment_pubkey=vds.read_bytes(33),
|
||||
remote_revocation_pubkey=vds.read_bytes(33),
|
||||
local_delay=vds.read_uint16(),
|
||||
remote_delay=vds.read_uint16(),
|
||||
host=vds.read_string(),
|
||||
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):
|
||||
|
||||
@@ -2452,11 +2452,8 @@ class LNWallet(LNWorker):
|
||||
raise Exception(f'Unknown channel {channel_id.hex()}')
|
||||
|
||||
def import_channel_backup(self, data):
|
||||
assert data.startswith('channel_backup:')
|
||||
encrypted = data[15:]
|
||||
xpub = self.wallet.get_fingerprint()
|
||||
decrypted = pw_decode_with_version_and_mac(encrypted, xpub)
|
||||
cb_storage = ImportedChannelBackupStorage.from_bytes(decrypted)
|
||||
cb_storage = ImportedChannelBackupStorage.from_encrypted_str(data, password=xpub)
|
||||
channel_id = cb_storage.channel_id()
|
||||
if channel_id.hex() in self.db.get_dict("channels"):
|
||||
raise Exception('Channel already in wallet')
|
||||
|
||||
@@ -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,
|
||||
get_compressed_pubkey_from_bech32, split_host_port, ConnStringFormatError,
|
||||
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.transaction import Transaction, PartialTransaction, Sighash
|
||||
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'
|
||||
@@ -903,3 +906,29 @@ class TestLNUtil(ElectrumTestCase):
|
||||
# ignore unknown channel types
|
||||
channel_type = ChannelType(0b10000000001000000000010).discard_unknown_and_check()
|
||||
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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user