commit 756dd8eb6669af37fad90f83475b414be15a6829
parent f8b736c908e155c3c6fcc35416e8d651aa69ea66
Author: ThomasV <thomasv@electrum.org>
Date: Thu, 21 May 2020 10:36:22 +0200
submarine swaps: add forward swaps
Diffstat:
2 files changed, 91 insertions(+), 3 deletions(-)
diff --git a/electrum/commands.py b/electrum/commands.py
@@ -1100,6 +1100,12 @@ class Commands:
""" return the local watchtower's ctn of channel. used in regtests """
return await self.network.local_watchtower.sweepstore.get_ctn(channel_point, None)
+ @command('wnp')
+ async def submarine_swap(self, amount, password=None, wallet: Abstract_Wallet = None):
+ from .submarine_swaps import submarine_swap
+ amount_sat = satoshis(amount)
+ return await submarine_swap(amount_sat, wallet, self.network, password)
+
@command('wn')
async def reverse_swap(self, amount, wallet: Abstract_Wallet = None):
from .submarine_swaps import reverse_swap
diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py
@@ -3,17 +3,33 @@ import json
import os
from .crypto import sha256, hash_160
from .ecc import ECPrivkey
-from .bitcoin import address_to_script, script_to_p2wsh, opcodes
+from .bitcoin import address_to_script, script_to_p2wsh, redeem_script_to_address, opcodes
from .transaction import TxOutpoint, PartialTxInput, PartialTxOutput, PartialTransaction, construct_witness
from .transaction import script_GetOp, match_script_against_template, OPPushDataGeneric, OPPushDataPubkey
from .transaction import Transaction
from .util import log_exceptions
-
API_URL = 'http://ecdsa.org:9001'
+
WITNESS_TEMPLATE_SWAP = [
+ opcodes.OP_HASH160,
+ OPPushDataGeneric(lambda x: x == 20),
+ opcodes.OP_EQUAL,
+ opcodes.OP_IF,
+ OPPushDataPubkey,
+ opcodes.OP_ELSE,
+ OPPushDataGeneric(None),
+ opcodes.OP_CHECKLOCKTIMEVERIFY,
+ opcodes.OP_DROP,
+ OPPushDataPubkey,
+ opcodes.OP_ENDIF,
+ opcodes.OP_CHECKSIG
+]
+
+
+WITNESS_TEMPLATE_REVERSE_SWAP = [
opcodes.OP_SIZE,
OPPushDataGeneric(None),
opcodes.OP_EQUAL,
@@ -78,6 +94,72 @@ async def claim_swap(key, wallet):
@log_exceptions
+async def submarine_swap(amount_sat, wallet: 'Abstract_Wallet', network: 'Network', password):
+ #
+ lnworker = wallet.lnworker
+ privkey = os.urandom(32)
+ pubkey = ECPrivkey(privkey).get_public_key_bytes(compressed=True)
+ key = await lnworker._add_request_coro(amount_sat, 'swap', expiry=3600)
+ request = wallet.get_request(key)
+ invoice = request['invoice']
+ lnaddr = lnworker._check_invoice(invoice, amount_sat)
+ payment_hash = lnaddr.paymenthash
+ preimage = lnworker.get_preimage(payment_hash)
+ request_data = {
+ "type": "submarine",
+ "pairId": "BTC/BTC",
+ "orderSide": "sell",
+ "invoice": invoice,
+ "refundPublicKey": pubkey.hex()
+ }
+ response = await network._send_http_on_proxy(
+ 'post',
+ 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"]
+
+ print(data)
+ # 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)]
+ assert match_script_against_template(redeem_script, WITNESS_TEMPLATE_SWAP)
+ #assert script_to_p2wsh(redeem_script.hex()) == lockup_address
+ assert redeem_script_to_address('p2wsh-p2sh', redeem_script.hex()) == lockup_address
+ assert hash_160(preimage) == parsed_script[1][1]
+ assert pubkey == parsed_script[9][1]
+ # verify that we will have enought time to get our tx confirmed
+ cltv = int.from_bytes(parsed_script[6][1], byteorder='little')
+ assert cltv - network.get_local_height() == 140
+
+ # save swap data in wallet in case we need a refund
+ data['privkey'] = privkey.hex()
+ data['preimage'] = preimage.hex()
+ swaps = wallet.db.get_dict('submarine_swaps')
+ swaps[response_id] = data
+ # todo: add callback to lnwatcher (with cltv)
+ #callback = lambda: _claim_refund(lnworker, lockup_address, redeem_script, preimage, privkey, onchain_amount, address)
+ #lnworker.lnwatcher.add_callback(lockup_address, callback)
+
+ # pay onchain. must not be RBF
+ outputs = [PartialTxOutput.from_address_and_value(lockup_address, onchain_amount)]
+ tx = wallet.create_transaction(outputs=outputs, rbf=False, password=password)
+ await network.broadcast_transaction(tx)
+ #
+ attempt = await lnworker.await_payment(payment_hash)
+ return {
+ 'id':response_id,
+ 'success':attempt.success,
+ }
+
+
+@log_exceptions
async def reverse_swap(amount_sat, wallet: 'Abstract_Wallet', network: 'Network'):
privkey = os.urandom(32)
pubkey = ECPrivkey(privkey).get_public_key_bytes(compressed=True)
@@ -107,7 +189,7 @@ async def reverse_swap(amount_sat, wallet: 'Abstract_Wallet', network: 'Network'
# 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)]
- assert match_script_against_template(redeem_script, WITNESS_TEMPLATE_SWAP)
+ assert match_script_against_template(redeem_script, WITNESS_TEMPLATE_REVERSE_SWAP)
assert script_to_p2wsh(redeem_script.hex()) == lockup_address
assert hash_160(preimage) == parsed_script[5][1]
assert pubkey == parsed_script[7][1]