electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit d29191b010d3174679ca90ee59c3c850e2bc6096
parent fc0009206b1c15442fff945b3ad44b2092eca4a3
Author: SomberNight <somber.night@protonmail.com>
Date:   Mon, 24 Sep 2018 15:14:00 +0200

rename LNChanAnnVerifier

Diffstat:
Delectrum/lnchanannverifier.py | 160-------------------------------------------------------------------------------
Aelectrum/lnchannelverifier.py | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Melectrum/lnrouter.py | 4++--
Melectrum/tests/test_lnrouter.py | 1-
4 files changed, 162 insertions(+), 163 deletions(-)

diff --git a/electrum/lnchanannverifier.py b/electrum/lnchanannverifier.py @@ -1,160 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Electrum - lightweight Bitcoin client -# Copyright (C) 2018 The Electrum developers -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import asyncio -import threading -from aiorpcx import TaskGroup - -from . import lnbase -from . import bitcoin -from . import ecc -from .util import ThreadJob, bh2u, bfh -from .lnutil import invert_short_channel_id, funding_output_script_from_keys -from .verifier import verify_tx_is_in_block, MerkleVerificationFailure -from .transaction import Transaction - - -class LNChanAnnVerifier(ThreadJob): - """ Verify channel announcements for the Channel DB """ - - def __init__(self, network, channel_db): - self.network = network - self.channel_db = channel_db - self.lock = threading.Lock() - - # items only removed when whole verification succeeds for them. - # fixme: if it fails, it will never succeed - self.started_verifying_channel = set() # short_channel_id - - self.unverified_channel_info = {} # short_channel_id -> channel_info - - def add_new_channel_info(self, channel_info): - short_channel_id = channel_info.channel_id - if short_channel_id in self.unverified_channel_info: - return - if not verify_sigs_for_channel_announcement(channel_info.msg_payload): - return - with self.lock: - self.unverified_channel_info[short_channel_id] = channel_info - - def get_pending_channel_info(self, short_channel_id): - return self.unverified_channel_info.get(short_channel_id, None) - - async def main(self): - while True: - async with TaskGroup() as tg: - await self.iteration(tg) - await asyncio.sleep(0.1) - - async def iteration(self, tg): - interface = self.network.interface - if not interface: - return - - blockchain = interface.blockchain - if not blockchain: - return - - with self.lock: - unverified_channel_info = list(self.unverified_channel_info) - - for short_channel_id in unverified_channel_info: - if short_channel_id in self.started_verifying_channel: - continue - block_height, tx_pos, output_idx = invert_short_channel_id(short_channel_id) - # only resolve short_channel_id if headers are available. - header = blockchain.read_header(block_height) - if header is None: - index = block_height // 2016 - if index < len(blockchain.checkpoints): - await tg.spawn(self.network.request_chunk(block_height, None, can_return_early=True)) - continue - await tg.spawn(self.verify_channel(block_height, tx_pos, short_channel_id)) - #self.print_error('requested short_channel_id', bh2u(short_channel_id)) - - async def verify_channel(self, block_height, tx_pos, short_channel_id): - with self.lock: - self.started_verifying_channel.add(short_channel_id) - result = await self.network.get_txid_from_txpos(block_height, tx_pos, True) - tx_hash = result['tx_hash'] - merkle_branch = result['merkle'] - header = self.network.blockchain().read_header(block_height) - try: - verify_tx_is_in_block(tx_hash, merkle_branch, tx_pos, header, block_height) - except MerkleVerificationFailure as e: - self.print_error(str(e)) - return - tx = Transaction(await self.network.get_transaction(tx_hash)) - try: - tx.deserialize() - except Exception: - self.print_msg("cannot deserialize transaction, skipping", tx_hash) - return - if tx_hash != tx.txid(): - self.print_error("received tx does not match expected txid ({} != {})" - .format(tx_hash, tx.txid())) - return - # check funding output - channel_info = self.unverified_channel_info[short_channel_id] - chan_ann = channel_info.msg_payload - redeem_script = funding_output_script_from_keys(chan_ann['bitcoin_key_1'], chan_ann['bitcoin_key_2']) - expected_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script) - output_idx = invert_short_channel_id(short_channel_id)[2] - try: - actual_output = tx.outputs()[output_idx] - except IndexError: - return - if expected_address != actual_output[1]: - return - # put channel into channel DB - channel_info.set_capacity(actual_output[2]) - self.channel_db.add_verified_channel_info(short_channel_id, channel_info) - # remove channel from unverified - with self.lock: - self.unverified_channel_info.pop(short_channel_id, None) - try: self.started_verifying_channel.remove(short_channel_id) - except KeyError: pass - - -def verify_sigs_for_channel_announcement(chan_ann: dict) -> bool: - msg_bytes = lnbase.gen_msg('channel_announcement', **chan_ann) - pre_hash = msg_bytes[2+256:] - h = bitcoin.Hash(pre_hash) - pubkeys = [chan_ann['node_id_1'], chan_ann['node_id_2'], chan_ann['bitcoin_key_1'], chan_ann['bitcoin_key_2']] - sigs = [chan_ann['node_signature_1'], chan_ann['node_signature_2'], chan_ann['bitcoin_signature_1'], chan_ann['bitcoin_signature_2']] - for pubkey, sig in zip(pubkeys, sigs): - if not ecc.verify_signature(pubkey, sig, h): - return False - return True - - -def verify_sig_for_channel_update(chan_upd: dict, node_id: bytes) -> bool: - msg_bytes = lnbase.gen_msg('channel_update', **chan_upd) - pre_hash = msg_bytes[2+64:] - h = bitcoin.Hash(pre_hash) - sig = chan_upd['signature'] - if not ecc.verify_signature(node_id, sig, h): - return False - return True diff --git a/electrum/lnchannelverifier.py b/electrum/lnchannelverifier.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- +# +# Electrum - lightweight Bitcoin client +# Copyright (C) 2018 The Electrum developers +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import asyncio +import threading +from aiorpcx import TaskGroup + +from . import lnbase +from . import bitcoin +from . import ecc +from .util import ThreadJob, bh2u, bfh +from .lnutil import invert_short_channel_id, funding_output_script_from_keys +from .verifier import verify_tx_is_in_block, MerkleVerificationFailure +from .transaction import Transaction + + +class LNChannelVerifier(ThreadJob): + """ Verify channel announcements for the Channel DB """ + + def __init__(self, network, channel_db): + self.network = network + self.channel_db = channel_db + self.lock = threading.Lock() + + # items only removed when whole verification succeeds for them. + # fixme: if it fails, it will never succeed + self.started_verifying_channel = set() # short_channel_id + + self.unverified_channel_info = {} # short_channel_id -> channel_info + + def add_new_channel_info(self, channel_info): + short_channel_id = channel_info.channel_id + if short_channel_id in self.unverified_channel_info: + return + if not verify_sigs_for_channel_announcement(channel_info.msg_payload): + return + with self.lock: + self.unverified_channel_info[short_channel_id] = channel_info + + def get_pending_channel_info(self, short_channel_id): + return self.unverified_channel_info.get(short_channel_id, None) + + async def main(self): + while True: + async with TaskGroup() as tg: + await self.iteration(tg) + await asyncio.sleep(0.1) + + async def iteration(self, tg): + interface = self.network.interface + if not interface: + return + + blockchain = interface.blockchain + if not blockchain: + return + + with self.lock: + unverified_channel_info = list(self.unverified_channel_info) + + for short_channel_id in unverified_channel_info: + if short_channel_id in self.started_verifying_channel: + continue + block_height, tx_pos, output_idx = invert_short_channel_id(short_channel_id) + # only resolve short_channel_id if headers are available. + header = blockchain.read_header(block_height) + if header is None: + index = block_height // 2016 + if index < len(blockchain.checkpoints): + await tg.spawn(self.network.request_chunk(block_height, None, can_return_early=True)) + continue + await tg.spawn(self.verify_channel(block_height, tx_pos, short_channel_id)) + #self.print_error('requested short_channel_id', bh2u(short_channel_id)) + + async def verify_channel(self, block_height, tx_pos, short_channel_id): + with self.lock: + self.started_verifying_channel.add(short_channel_id) + result = await self.network.get_txid_from_txpos(block_height, tx_pos, True) + tx_hash = result['tx_hash'] + merkle_branch = result['merkle'] + header = self.network.blockchain().read_header(block_height) + try: + verify_tx_is_in_block(tx_hash, merkle_branch, tx_pos, header, block_height) + except MerkleVerificationFailure as e: + self.print_error(str(e)) + return + tx = Transaction(await self.network.get_transaction(tx_hash)) + try: + tx.deserialize() + except Exception: + self.print_msg("cannot deserialize transaction, skipping", tx_hash) + return + if tx_hash != tx.txid(): + self.print_error("received tx does not match expected txid ({} != {})" + .format(tx_hash, tx.txid())) + return + # check funding output + channel_info = self.unverified_channel_info[short_channel_id] + chan_ann = channel_info.msg_payload + redeem_script = funding_output_script_from_keys(chan_ann['bitcoin_key_1'], chan_ann['bitcoin_key_2']) + expected_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script) + output_idx = invert_short_channel_id(short_channel_id)[2] + try: + actual_output = tx.outputs()[output_idx] + except IndexError: + return + if expected_address != actual_output[1]: + return + # put channel into channel DB + channel_info.set_capacity(actual_output[2]) + self.channel_db.add_verified_channel_info(short_channel_id, channel_info) + # remove channel from unverified + with self.lock: + self.unverified_channel_info.pop(short_channel_id, None) + try: self.started_verifying_channel.remove(short_channel_id) + except KeyError: pass + + +def verify_sigs_for_channel_announcement(chan_ann: dict) -> bool: + msg_bytes = lnbase.gen_msg('channel_announcement', **chan_ann) + pre_hash = msg_bytes[2+256:] + h = bitcoin.Hash(pre_hash) + pubkeys = [chan_ann['node_id_1'], chan_ann['node_id_2'], chan_ann['bitcoin_key_1'], chan_ann['bitcoin_key_2']] + sigs = [chan_ann['node_signature_1'], chan_ann['node_signature_2'], chan_ann['bitcoin_signature_1'], chan_ann['bitcoin_signature_2']] + for pubkey, sig in zip(pubkeys, sigs): + if not ecc.verify_signature(pubkey, sig, h): + return False + return True + + +def verify_sig_for_channel_update(chan_upd: dict, node_id: bytes) -> bool: + msg_bytes = lnbase.gen_msg('channel_update', **chan_upd) + pre_hash = msg_bytes[2+64:] + h = bitcoin.Hash(pre_hash) + sig = chan_upd['signature'] + if not ecc.verify_signature(node_id, sig, h): + return False + return True diff --git a/electrum/lnrouter.py b/electrum/lnrouter.py @@ -36,7 +36,7 @@ import asyncio from . import constants from .util import PrintError, bh2u, profiler, get_headers_dir, bfh, is_ip_address, list_enabled_bits from .storage import JsonDB -from .lnchanannverifier import LNChanAnnVerifier, verify_sig_for_channel_update +from .lnchannelverifier import LNChannelVerifier, verify_sig_for_channel_update from .crypto import Hash from . import ecc from .lnutil import LN_GLOBAL_FEATURE_BITS, LNPeerAddr @@ -277,7 +277,7 @@ class ChannelDB(JsonDB): self._recent_peers = [] self._last_good_address = {} # node_id -> LNPeerAddr - self.ca_verifier = LNChanAnnVerifier(network, self) + self.ca_verifier = LNChannelVerifier(network, self) # FIXME if the channel verifier raises, it kills network.main_taskgroup asyncio.run_coroutine_threadsafe(self.network.add_job(self.ca_verifier.main()), network.asyncio_loop) diff --git a/electrum/tests/test_lnrouter.py b/electrum/tests/test_lnrouter.py @@ -8,7 +8,6 @@ from electrum.lnonion import (OnionHopsDataSingle, new_onion_packet, OnionPerHop process_onion_packet, _decode_onion_error) from electrum import bitcoin, lnrouter from electrum.simple_config import SimpleConfig -from electrum import lnchanannverifier from . import TestCaseForTestnet