From fd10ae3a3b0c52571755d251b3e087c7aa950050 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 28 Jul 2023 09:28:45 +0200 Subject: [PATCH] New flow for submarine swaps: - client requests payment_hash from the server - client sends an invoice with that hash - client waits to receive HTLCs, then broadcasts funding tx This means that we now use same script for normal and reverse swaps. The new flow is enabled by setting option LIGHTNING_SWAP_HTLC_FIRST in the client. The old protocol is still supported server-side. --- electrum/plugins/swapserver/server.py | 51 ++- electrum/plugins/swapserver/swapserver.py | 1 - electrum/simple_config.py | 1 + electrum/submarine_swaps.py | 402 ++++++++++++++-------- 4 files changed, 308 insertions(+), 147 deletions(-) diff --git a/electrum/plugins/swapserver/server.py b/electrum/plugins/swapserver/server.py index 0fb1ca724..305f26c78 100644 --- a/electrum/plugins/swapserver/server.py +++ b/electrum/plugins/swapserver/server.py @@ -5,11 +5,10 @@ from collections import defaultdict from aiohttp import web from aiorpcx import NetAddress - from electrum.util import log_exceptions, ignore_exceptions from electrum.logging import Logger from electrum.util import EventListener - +from electrum.lnaddr import lndecode class SwapServer(Logger, EventListener): """ @@ -24,6 +23,7 @@ class SwapServer(Logger, EventListener): Logger.__init__(self) self.config = config self.wallet = wallet + self.sm = self.wallet.lnworker.swap_manager self.addr = NetAddress.from_string(self.config.SWAPSERVER_ADDRESS) self.register_callbacks() # eventlistener @@ -36,6 +36,8 @@ class SwapServer(Logger, EventListener): app = web.Application() app.add_routes([web.get('/api/getpairs', self.get_pairs)]) app.add_routes([web.post('/api/createswap', self.create_swap)]) + app.add_routes([web.post('/api/createnormalswap', self.create_normal_swap)]) + app.add_routes([web.post('/api/addswapinvoice', self.add_swap_invoice)]) runner = web.AppRunner(app) await runner.setup() @@ -44,7 +46,7 @@ class SwapServer(Logger, EventListener): self.logger.info(f"now running and listening. addr={self.addr}") async def get_pairs(self, r): - sm = self.wallet.lnworker.swap_manager + sm = self.sm sm.init_pairs() pairs = { "info": [], @@ -84,9 +86,36 @@ class SwapServer(Logger, EventListener): } return web.json_response(pairs) + async def add_swap_invoice(self, r): + request = await r.json() + invoice = request['invoice'] + self.sm.add_invoice(invoice, pay_now=True) + return web.json_response({}) + + async def create_normal_swap(self, r): + # normal for client, reverse for server + request = await r.json() + lightning_amount_sat = request['invoiceAmount'] + their_pubkey = bytes.fromhex(request['refundPublicKey']) + assert len(their_pubkey) == 33 + swap = self.sm.create_reverse_swap( + payment_hash=None, + lightning_amount_sat=lightning_amount_sat, + their_pubkey=their_pubkey + ) + response = { + "id": swap.payment_hash.hex(), + 'preimageHash': swap.payment_hash.hex(), + "acceptZeroConf": False, + "expectedAmount": swap.onchain_amount, + "timeoutBlockHeight": swap.locktime, + "address": swap.lockup_address, + "redeemScript": swap.redeem_script.hex(), + } + return web.json_response(response) + async def create_swap(self, r): - sm = self.wallet.lnworker.swap_manager - sm.init_pairs() + self.sm.init_pairs() request = await r.json() req_type = request['type'] assert request['pairId'] == 'BTC/BTC' @@ -96,7 +125,7 @@ class SwapServer(Logger, EventListener): their_pubkey=bytes.fromhex(request['claimPublicKey']) assert len(payment_hash) == 32 assert len(their_pubkey) == 33 - swap, payment_hash, invoice, prepay_invoice = sm.add_server_swap( + swap, invoice, prepay_invoice = self.sm.create_normal_swap( lightning_amount_sat=lightning_amount_sat, payment_hash=payment_hash, their_pubkey=their_pubkey @@ -111,13 +140,19 @@ class SwapServer(Logger, EventListener): "onchainAmount": swap.onchain_amount, } elif req_type == 'submarine': + # old protocol their_invoice=request['invoice'] their_pubkey=bytes.fromhex(request['refundPublicKey']) assert len(their_pubkey) == 33 - swap, payment_hash, invoice, prepay_invoice = sm.add_server_swap( - invoice=their_invoice, + lnaddr = lndecode(their_invoice) + payment_hash = lnaddr.paymenthash + lightning_amount_sat = int(lnaddr.get_amount_sat()) # should return int + swap = self.sm.create_reverse_swap( + lightning_amount_sat=lightning_amount_sat, + payment_hash=payment_hash, their_pubkey=their_pubkey ) + self.sm.add_invoice(their_invoice, pay_now=False) response = { "id": payment_hash.hex(), "acceptZeroConf": False, diff --git a/electrum/plugins/swapserver/swapserver.py b/electrum/plugins/swapserver/swapserver.py index a4b785347..9a35e9714 100644 --- a/electrum/plugins/swapserver/swapserver.py +++ b/electrum/plugins/swapserver/swapserver.py @@ -55,7 +55,6 @@ class SwapServerPlugin(BasePlugin): self.server = SwapServer(self.config, wallet) sm = wallet.lnworker.swap_manager for coro in [ - sm.pay_pending_invoices(), # FIXME this method can raise, which is not properly handled...? self.server.run(), ]: asyncio.run_coroutine_threadsafe(daemon.taskgroup.spawn(coro), daemon.asyncio_loop) diff --git a/electrum/simple_config.py b/electrum/simple_config.py index 06285f5a8..6cbf67810 100644 --- a/electrum/simple_config.py +++ b/electrum/simple_config.py @@ -890,6 +890,7 @@ class SimpleConfig(Logger): LIGHTNING_USE_GOSSIP = ConfigVar('use_gossip', default=False, type_=bool) LIGHTNING_USE_RECOVERABLE_CHANNELS = ConfigVar('use_recoverable_channels', default=True, type_=bool) LIGHTNING_ALLOW_INSTANT_SWAPS = ConfigVar('allow_instant_swaps', default=False, type_=bool) + LIGHTNING_SWAP_HTLC_FIRST = ConfigVar('swap_htlc_first', default=False, type_=bool) LIGHTNING_TO_SELF_DELAY_CSV = ConfigVar('lightning_to_self_delay', default=7 * 144, type_=int) LIGHTNING_MAX_FUNDING_SAT = ConfigVar('lightning_max_funding_sat', default=LN_MAX_FUNDING_SAT_LEGACY, type_=int) diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py index 58119e887..c23e017a4 100644 --- a/electrum/submarine_swaps.py +++ b/electrum/submarine_swaps.py @@ -24,6 +24,10 @@ from . import constants from .address_synchronizer import TX_HEIGHT_LOCAL from .i18n import _ +from .bitcoin import construct_script +from .crypto import ripemd +from .invoices import Invoice + if TYPE_CHECKING: from .network import Network from .wallet import Abstract_Wallet @@ -81,6 +85,40 @@ WITNESS_TEMPLATE_REVERSE_SWAP = [ opcodes.OP_CHECKSIG ] +def check_reverse_redeem_script(redeem_script, lockup_address, payment_hash, locktime, *, refund_pubkey=None, claim_pubkey=None): + redeem_script = bytes.fromhex(redeem_script) + parsed_script = [x for x in script_GetOp(redeem_script)] + if not match_script_against_template(redeem_script, WITNESS_TEMPLATE_REVERSE_SWAP): + raise Exception("rswap check failed: scriptcode does not match template") + if script_to_p2wsh(redeem_script.hex()) != lockup_address: + raise Exception("rswap check failed: inconsistent scriptcode and address") + if ripemd(payment_hash) != parsed_script[5][1]: + raise Exception("rswap check failed: our preimage not in script") + if claim_pubkey and claim_pubkey != parsed_script[7][1]: + raise Exception("rswap check failed: our pubkey not in script") + if refund_pubkey and refund_pubkey != parsed_script[13][1]: + raise Exception("rswap check failed: our pubkey not in script") + if locktime != int.from_bytes(parsed_script[10][1], byteorder='little'): + raise Exception("rswap check failed: inconsistent locktime and script") + return parsed_script[7][1], parsed_script[13][1] + +def check_normal_redeem_script(redeem_script, lockup_address, payment_hash, locktime, *, refund_pubkey=None, claim_pubkey=None): + redeem_script = bytes.fromhex(redeem_script) + parsed_script = [x for x in script_GetOp(redeem_script)] + if not match_script_against_template(redeem_script, WITNESS_TEMPLATE_SWAP): + raise Exception("fswap check failed: scriptcode does not match template") + if script_to_p2wsh(redeem_script.hex()) != lockup_address: + raise Exception("fswap check failed: inconsistent scriptcode and address") + if ripemd(payment_hash) != parsed_script[1][1]: + raise Exception("fswap check failed: our preimage not in script") + if claim_pubkey and claim_pubkey != parsed_script[4][1]: + raise Exception("fswap check failed: our pubkey not in script") + if refund_pubkey and refund_pubkey != parsed_script[9][1]: + raise Exception("fswap check failed: our pubkey not in script") + if locktime != int.from_bytes(parsed_script[6][1], byteorder='little'): + raise Exception("fswap check failed: inconsistent locktime and script") + return parsed_script[4][1], parsed_script[9][1] + class SwapServerError(Exception): def __str__(self): @@ -174,9 +212,11 @@ class SwapManager(Logger): if swap.is_redeemed: continue self.add_lnwatcher_callback(swap) + coro = self.pay_pending_invoices() + asyncio.run_coroutine_threadsafe(network.taskgroup.spawn(coro), network.asyncio_loop) async def pay_pending_invoices(self): - # for server + # FIXME this method can raise, which is not properly handled...? self.invoices_to_pay = set() while True: await asyncio.sleep(1) @@ -302,77 +342,85 @@ class SwapManager(Logger): self.lnwatcher.add_callback(swap.lockup_address, callback) async def hold_invoice_callback(self, payment_hash): + # note: this assumes the keystore is not encrypted key = payment_hash.hex() if key in self.swaps: swap = self.swaps[key] if swap.funding_txid is None: - await self.start_normal_swap(swap, None, None) - - def add_server_swap(self, *, lightning_amount_sat=None, payment_hash=None, invoice=None, their_pubkey=None): - from .bitcoin import construct_script - from .crypto import ripemd - from .lnaddr import lndecode - from .invoices import Invoice + await self.broadcast_funding_tx(swap, None, None) + def create_normal_swap(self, *, lightning_amount_sat=None, payment_hash=None, their_pubkey=None): + """ server method """ locktime = self.network.get_local_height() + 140 - privkey = os.urandom(32) - our_pubkey = ECPrivkey(privkey).get_public_key_bytes(compressed=True) - is_reverse_for_server = (invoice is not None) - if is_reverse_for_server: - # client is doing a normal swap - lnaddr = lndecode(invoice) - payment_hash = lnaddr.paymenthash - lightning_amount_sat = int(lnaddr.get_amount_sat()) # should return int - onchain_amount_sat = self._get_send_amount(lightning_amount_sat, is_reverse=False) - redeem_script = construct_script( - WITNESS_TEMPLATE_SWAP, - {1:ripemd(payment_hash), 4:our_pubkey, 6:locktime, 9:their_pubkey} - ) - self.wallet.save_invoice(Invoice.from_bech32(invoice)) - prepay_invoice = None - prepay_hash = None - else: - onchain_amount_sat = self._get_recv_amount(lightning_amount_sat, is_reverse=True) + our_privkey = os.urandom(32) + our_pubkey = ECPrivkey(our_privkey).get_public_key_bytes(compressed=True) + onchain_amount_sat = self._get_recv_amount(lightning_amount_sat, is_reverse=True) # what the client is going to receive + redeem_script = construct_script( + WITNESS_TEMPLATE_REVERSE_SWAP, + {1:32, 5:ripemd(payment_hash), 7:their_pubkey, 10:locktime, 13:our_pubkey} + ) + return self.add_normal_swap( + redeem_script=redeem_script, + locktime=locktime, + onchain_amount_sat=onchain_amount_sat, + lightning_amount_sat=lightning_amount_sat, + payment_hash=payment_hash, + our_privkey=our_privkey, + their_pubkey=their_pubkey, + invoice=None, + prepay=True, + ) + + def add_normal_swap(self, *, redeem_script=None, locktime=None, onchain_amount_sat=None, lightning_amount_sat=None, payment_hash=None, our_privkey=None, their_pubkey=None, invoice=None, prepay=None): + """ if invoice is None, create a hold invoice """ + if prepay: prepay_amount_sat = self.get_claim_fee() * 2 - main_amount_sat = lightning_amount_sat - prepay_amount_sat - lnaddr, invoice = self.lnworker.get_bolt11_invoice( + invoice_amount_sat = lightning_amount_sat - prepay_amount_sat + else: + invoice_amount_sat = lightning_amount_sat + + if not invoice: + _, invoice = self.lnworker.get_bolt11_invoice( payment_hash=payment_hash, - amount_msat=main_amount_sat * 1000, + amount_msat=invoice_amount_sat * 1000, message='Submarine swap', expiry=3600 * 24, fallback_address=None, channels=None, ) # add payment info to lnworker - self.lnworker.add_payment_info_for_hold_invoice(payment_hash, main_amount_sat) + self.lnworker.add_payment_info_for_hold_invoice(payment_hash, invoice_amount_sat) self.lnworker.register_callback_for_hold_invoice(payment_hash, self.hold_invoice_callback) + + if prepay: prepay_hash = self.lnworker.create_payment_info(amount_msat=prepay_amount_sat*1000) _, prepay_invoice = self.lnworker.get_bolt11_invoice( payment_hash=prepay_hash, amount_msat=prepay_amount_sat * 1000, - message='prepay', + message='Submarine swap mining fees', expiry=3600 * 24, fallback_address=None, channels=None, ) self.lnworker.bundle_payments([payment_hash, prepay_hash]) - redeem_script = construct_script( - WITNESS_TEMPLATE_REVERSE_SWAP, - {1:32, 5:ripemd(payment_hash), 7:their_pubkey, 10:locktime, 13:our_pubkey} - ) + self.prepayments[prepay_hash] = payment_hash + else: + prepay_invoice = None + prepay_hash = None + lockup_address = script_to_p2wsh(redeem_script) receive_address = self.wallet.get_receiving_address() swap = SwapData( redeem_script = bytes.fromhex(redeem_script), locktime = locktime, - privkey = privkey, + privkey = our_privkey, preimage = None, prepay_hash = prepay_hash, lockup_address = lockup_address, onchain_amount = onchain_amount_sat, receive_address = receive_address, lightning_amount = lightning_amount_sat, - is_reverse = is_reverse_for_server, + is_reverse = False, is_redeemed = False, funding_txid = None, spending_txid = None, @@ -380,7 +428,78 @@ class SwapManager(Logger): swap._payment_hash = payment_hash self._add_or_reindex_swap(swap) self.add_lnwatcher_callback(swap) - return swap, payment_hash, invoice, prepay_invoice + return swap, invoice, prepay_invoice + + def create_reverse_swap(self, *, lightning_amount_sat=None, payment_hash=None, their_pubkey=None): + """ server method. payment_hash is not None for old clients """ + locktime = self.network.get_local_height() + 140 + privkey = os.urandom(32) + our_pubkey = ECPrivkey(privkey).get_public_key_bytes(compressed=True) + onchain_amount_sat = self._get_send_amount(lightning_amount_sat, is_reverse=False) + # + if payment_hash is None: + preimage = os.urandom(32) + assert lightning_amount_sat is not None + payment_hash = sha256(preimage) + redeem_script = construct_script( + WITNESS_TEMPLATE_REVERSE_SWAP, + {1:32, 5:ripemd(payment_hash), 7:our_pubkey, 10:locktime, 13:their_pubkey} + ) + else: + # old client + preimage = None + redeem_script = construct_script( + WITNESS_TEMPLATE_SWAP, + {1:ripemd(payment_hash), 4:our_pubkey, 6:locktime, 9:their_pubkey} + ) + swap = self.add_reverse_swap( + redeem_script=redeem_script, + locktime=locktime, + privkey=privkey, + preimage=preimage, + payment_hash=payment_hash, + prepay_hash=None, + onchain_amount_sat=onchain_amount_sat, + lightning_amount_sat=lightning_amount_sat) + return swap + + def add_reverse_swap(self, *, redeem_script=None, locktime=None, privkey=None, lightning_amount_sat=None, onchain_amount_sat=None, preimage=None, payment_hash=None, prepay_hash=None): + lockup_address = script_to_p2wsh(redeem_script) + receive_address = self.wallet.get_receiving_address() + swap = SwapData( + redeem_script = bytes.fromhex(redeem_script), + locktime = locktime, + privkey = privkey, + preimage = preimage, + prepay_hash = prepay_hash, + lockup_address = lockup_address, + onchain_amount = onchain_amount_sat, + receive_address = receive_address, + lightning_amount = lightning_amount_sat, + is_reverse = True, + is_redeemed = False, + funding_txid = None, + spending_txid = None, + ) + if prepay_hash: + self.prepayments[prepay_hash] = payment_hash + swap._payment_hash = payment_hash + self._add_or_reindex_swap(swap) + self.add_lnwatcher_callback(swap) + return swap + + def add_invoice(self, invoice, pay_now=False): + invoice = Invoice.from_bech32(invoice) + key = invoice.rhash + payment_hash = bytes.fromhex(key) + assert key in self.swaps + self.wallet.save_invoice(invoice) + if pay_now: + # check that we have the preimage + swap = self.get_swap(payment_hash) + assert sha256(swap.preimage) == payment_hash + assert swap.spending_txid is None + self.invoices_to_pay.add(key) async def normal_swap( self, @@ -397,54 +516,73 @@ class SwapManager(Logger): - User creates on-chain output locked to RHASH. - Server pays LN invoice. User reveals preimage. - Server spends the on-chain output using preimage. + + New flow: + - user requests swap + - server creates preimage, sends RHASH to user + - user creates hold invoice, sends it to server + """ assert self.network assert self.lnwatcher - privkey = os.urandom(32) - pubkey = ECPrivkey(privkey).get_public_key_bytes(compressed=True) amount_msat = lightning_amount_sat * 1000 - payment_hash = self.lnworker.create_payment_info(amount_msat=amount_msat) - lnaddr, invoice = self.lnworker.get_bolt11_invoice( - payment_hash=payment_hash, - amount_msat=amount_msat, - message='swap', - expiry=3600 * 24, - fallback_address=None, - channels=channels, - ) - preimage = self.lnworker.get_preimage(payment_hash) - request_data = { - "type": "submarine", - "pairId": "BTC/BTC", - "orderSide": "sell", - "invoice": invoice, - "refundPublicKey": pubkey.hex() - } - response = await self.network.async_send_http_on_proxy( - 'post', - self.api_url + '/createswap', - json=request_data, - timeout=30) - data = json.loads(response) - response_id = data["id"] + refund_privkey = os.urandom(32) + refund_pubkey = ECPrivkey(refund_privkey).get_public_key_bytes(compressed=True) + + if self.wallet.config.LIGHTNING_SWAP_HTLC_FIRST: + self.logger.info('requesting preimage hash for swap') + request_data = { + "invoiceAmount": lightning_amount_sat, + "refundPublicKey": refund_pubkey.hex() + } + response = await self.network.async_send_http_on_proxy( + 'post', + self.api_url + '/createnormalswap', + json=request_data, + timeout=30) + data = json.loads(response) + payment_hash = bytes.fromhex(data["preimageHash"]) + preimage = None + invoice = None + else: + # create invoice, send it to server + payment_hash = self.lnworker.create_payment_info(amount_msat=amount_msat) + preimage = self.lnworker.get_preimage(payment_hash) + _, invoice = self.lnworker.get_bolt11_invoice( + payment_hash=payment_hash, + amount_msat=amount_msat, + message='swap', + expiry=3600 * 24, + fallback_address=None, + channels=channels, + ) + request_data = { + "type": "submarine", + "pairId": "BTC/BTC", + "orderSide": "sell", + "invoice": invoice, + "refundPublicKey": refund_pubkey.hex() + } + response = await self.network.async_send_http_on_proxy( + 'post', + self.api_url + '/createswap', + json=request_data, + timeout=30) + + data = json.loads(response) + response_id = data["id"] + zeroconf = data["acceptZeroConf"] onchain_amount = data["expectedAmount"] locktime = data["timeoutBlockHeight"] lockup_address = data["address"] redeem_script = data["redeemScript"] # verify redeem_script is built with our pubkey and preimage - redeem_script = bytes.fromhex(redeem_script) - parsed_script = [x for x in script_GetOp(redeem_script)] - if not match_script_against_template(redeem_script, WITNESS_TEMPLATE_SWAP): - raise Exception("fswap check failed: scriptcode does not match template") - if script_to_p2wsh(redeem_script.hex()) != lockup_address: - raise Exception("fswap check failed: inconsistent scriptcode and address") - if hash_160(preimage) != parsed_script[1][1]: - raise Exception("fswap check failed: our preimage not in script") - if pubkey != parsed_script[9][1]: - raise Exception("fswap check failed: our pubkey not in script") - if locktime != int.from_bytes(parsed_script[6][1], byteorder='little'): - raise Exception("fswap check failed: inconsistent locktime and script") + if self.wallet.config.LIGHTNING_SWAP_HTLC_FIRST: + claim_pubkey, _ = check_reverse_redeem_script(redeem_script, lockup_address, payment_hash, locktime, refund_pubkey=refund_pubkey) + else: + claim_pubkey, _ = check_normal_redeem_script(redeem_script, lockup_address, payment_hash, locktime, refund_pubkey=refund_pubkey) + # check that onchain_amount is not more than what we estimated if onchain_amount > expected_onchain_amount_sat: raise Exception(f"fswap check failed: onchain_amount is more than what we estimated: " @@ -452,30 +590,41 @@ class SwapManager(Logger): # verify that they are not locking up funds for more than a day if locktime - self.network.get_local_height() >= 144: raise Exception("fswap check failed: locktime too far in future") - # save swap data in wallet in case we need a refund - receive_address = self.wallet.get_receiving_address() - swap = SwapData( - redeem_script = redeem_script, - locktime = locktime, - privkey = privkey, - preimage = preimage, - prepay_hash = None, - lockup_address = lockup_address, - onchain_amount = onchain_amount, - receive_address = receive_address, - lightning_amount = lightning_amount_sat, - is_reverse = False, - is_redeemed = False, - funding_txid = None, - spending_txid = None, - ) - swap._payment_hash = payment_hash - self._add_or_reindex_swap(swap) - self.add_lnwatcher_callback(swap) - return await self.start_normal_swap(swap, tx, password) + + swap, invoice, _ = self.add_normal_swap( + redeem_script=redeem_script, + locktime=locktime, + lightning_amount_sat=lightning_amount_sat, + onchain_amount_sat=onchain_amount, + payment_hash=payment_hash, + our_privkey=refund_privkey, + their_pubkey=claim_pubkey, + invoice=invoice, + prepay=False) + + if self.wallet.config.LIGHTNING_SWAP_HTLC_FIRST: + # send invoice to server and wait for htlcs + request_data = { + "preimageHash": payment_hash.hex(), + "invoice": invoice, + "refundPublicKey": refund_pubkey.hex(), + } + response = await self.network.async_send_http_on_proxy( + 'post', + self.api_url + '/addswapinvoice', + json=request_data, + timeout=30) + data = json.loads(response) + # wait for funding tx + while swap.funding_txid is None: + await asyncio.sleep(0.1) + else: + # broadcast funding tx right away + await self.broadcast_funding_tx(swap, tx, password) + return swap.funding_txid @log_exceptions - async def start_normal_swap(self, swap, tx, password): + async def broadcast_funding_tx(self, swap, tx, password): # create funding tx # note: rbf must not decrease payment # this is taken care of in wallet._is_rbf_allowed_to_touch_tx_output @@ -488,9 +637,9 @@ class SwapManager(Logger): tx.add_outputs([funding_output]) tx.set_rbf(True) self.wallet.sign_transaction(tx, password) + await self.network.broadcast_transaction(tx) swap.funding_txid = tx.txid() - return swap.funding_txid async def reverse_swap( self, @@ -513,16 +662,16 @@ class SwapManager(Logger): assert self.network assert self.lnwatcher privkey = os.urandom(32) - pubkey = ECPrivkey(privkey).get_public_key_bytes(compressed=True) + our_pubkey = ECPrivkey(privkey).get_public_key_bytes(compressed=True) preimage = os.urandom(32) - preimage_hash = sha256(preimage) + payment_hash = sha256(preimage) request_data = { "type": "reversesubmarine", "pairId": "BTC/BTC", "orderSide": "buy", "invoiceAmount": lightning_amount_sat, - "preimageHash": preimage_hash.hex(), - "claimPublicKey": pubkey.hex() + "preimageHash": payment_hash.hex(), + "claimPublicKey": our_pubkey.hex() } response = await self.network.async_send_http_on_proxy( 'post', @@ -538,18 +687,7 @@ class SwapManager(Logger): onchain_amount = data["onchainAmount"] response_id = data['id'] # verify redeem_script is built with our pubkey and preimage - redeem_script = bytes.fromhex(redeem_script) - parsed_script = [x for x in script_GetOp(redeem_script)] - if not match_script_against_template(redeem_script, WITNESS_TEMPLATE_REVERSE_SWAP): - raise Exception("rswap check failed: scriptcode does not match template") - if script_to_p2wsh(redeem_script.hex()) != lockup_address: - raise Exception("rswap check failed: inconsistent scriptcode and address") - if hash_160(preimage) != parsed_script[5][1]: - raise Exception("rswap check failed: our preimage not in script") - if pubkey != parsed_script[7][1]: - raise Exception("rswap check failed: our pubkey not in script") - if locktime != int.from_bytes(parsed_script[10][1], byteorder='little'): - raise Exception("rswap check failed: inconsistent locktime and script") + check_reverse_redeem_script(redeem_script, lockup_address, payment_hash, locktime, refund_pubkey=None, claim_pubkey=our_pubkey) # check that the onchain amount is what we expected if onchain_amount < expected_onchain_amount_sat: raise Exception(f"rswap check failed: onchain_amount is less than what we expected: " @@ -557,10 +695,10 @@ class SwapManager(Logger): # verify that we will have enough time to get our tx confirmed if locktime - self.network.get_local_height() <= MIN_LOCKTIME_DELTA: raise Exception("rswap check failed: locktime too close") - # verify invoice preimage_hash + # verify invoice payment_hash lnaddr = self.lnworker._check_invoice(invoice) invoice_amount = int(lnaddr.get_amount_sat()) - if lnaddr.paymenthash != preimage_hash: + if lnaddr.paymenthash != payment_hash: raise Exception("rswap check failed: inconsistent RHASH and invoice") # check that the lightning amount is what we requested if fee_invoice: @@ -573,29 +711,17 @@ class SwapManager(Logger): raise Exception(f"rswap check failed: invoice_amount ({invoice_amount}) " f"not what we requested ({lightning_amount_sat})") # save swap data to wallet file - receive_address = self.wallet.get_receiving_address() - swap = SwapData( - redeem_script = redeem_script, - locktime = locktime, - privkey = privkey, - preimage = preimage, - prepay_hash = prepay_hash, - lockup_address = lockup_address, - onchain_amount = onchain_amount, - receive_address = receive_address, - lightning_amount = lightning_amount_sat, - is_reverse = True, - is_redeemed = False, - funding_txid = None, - spending_txid = None, - ) - swap._payment_hash = preimage_hash - self._add_or_reindex_swap(swap) - # add callback to lnwatcher - self.add_lnwatcher_callback(swap) + swap = self.add_reverse_swap( + redeem_script=redeem_script, + locktime=locktime, + privkey=privkey, + preimage=preimage, + payment_hash=payment_hash, + prepay_hash=prepay_hash, + onchain_amount_sat=onchain_amount, + lightning_amount_sat=lightning_amount_sat) # initiate fee payment. if fee_invoice: - self.prepayments[prepay_hash] = preimage_hash asyncio.ensure_future(self.lnworker.pay_invoice(fee_invoice, attempts=10)) # we return if we detect funding async def wait_for_funding(swap):