From 645571e47c5b786229a868c82c5193e279a15187 Mon Sep 17 00:00:00 2001 From: tecnovert Date: Thu, 10 Dec 2020 12:07:26 +0200 Subject: [PATCH] Check for duplicate pubkeys. Add test for 'non-BIP68-final'. --- basicswap/basicswap.py | 61 ++++++++++------------------ basicswap/ecc_util.py | 15 +++---- basicswap/interface_btc.py | 2 +- basicswap/interface_xmr.py | 4 +- basicswap/protocols/__init__.py | 0 basicswap/protocols/atomic_swap_1.py | 43 ++++++++++++++++++++ basicswap/protocols/xmr_swap_1.py | 5 +++ tests/basicswap/test_xmr.py | 30 ++++++++++++++ 8 files changed, 111 insertions(+), 49 deletions(-) create mode 100644 basicswap/protocols/__init__.py create mode 100644 basicswap/protocols/atomic_swap_1.py create mode 100644 basicswap/protocols/xmr_swap_1.py diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index fb3bef8..a3caf09 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -36,7 +36,6 @@ from .util import ( format_amount, encodeAddress, decodeAddress, - SerialiseNum, DeserialiseNum, decodeWif, toWIF, @@ -80,10 +79,14 @@ from .db import ( XmrSwap, XmrSplitData, ) - -from .explorers import ExplorerInsight, ExplorerBitAps, ExplorerChainz -import basicswap.config as cfg from .base import BaseApp +from .explorers import ( + ExplorerInsight, + ExplorerBitAps, + ExplorerChainz, +) +import basicswap.config as cfg +import basicswap.protocols.atomic_swap_1 as atomic_swap_1 MIN_OFFER_VALID_TIME = 60 * 10 @@ -348,37 +351,6 @@ def decodeSequence(lock_value): return lock_value & SEQUENCE_LOCKTIME_MASK -def buildContractScript(lock_val, secret_hash, pkh_redeem, pkh_refund, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY): - script = bytearray([ - OpCodes.OP_IF, - OpCodes.OP_SIZE, - 0x01, 0x20, # 32 - OpCodes.OP_EQUALVERIFY, - OpCodes.OP_SHA256, - 0x20]) \ - + secret_hash \ - + bytearray([ - OpCodes.OP_EQUALVERIFY, - OpCodes.OP_DUP, - OpCodes.OP_HASH160, - 0x14]) \ - + pkh_redeem \ - + bytearray([OpCodes.OP_ELSE, ]) \ - + SerialiseNum(lock_val) \ - + bytearray([ - op_lock, - OpCodes.OP_DROP, - OpCodes.OP_DUP, - OpCodes.OP_HASH160, - 0x14]) \ - + pkh_refund \ - + bytearray([ - OpCodes.OP_ENDIF, - OpCodes.OP_EQUALVERIFY, - OpCodes.OP_CHECKSIG]) - return script - - def extractScriptSecretHash(script): return script[7:39] @@ -748,7 +720,7 @@ class BasicSwap(BaseApp): self.db_version = db_version kv = session.query(DBKVInt).filter_by(key='db_version').first() if not kv: - kv = DBKVInt(key=str_key, value=db_version) + kv = DBKVInt(key='db_version', value=db_version) else: kv.value = db_version session.add(kv) @@ -1658,14 +1630,14 @@ class BasicSwap(BaseApp): else: if offer.lock_type < ABS_LOCK_BLOCKS: sequence = getExpectedSequence(offer.lock_type, offer.lock_value, coin_from) - script = buildContractScript(sequence, secret_hash, bid.pkhash_buyer, pkhash_refund) + script = atomic_swap_1.buildContractScript(sequence, secret_hash, bid.pkhash_buyer, pkhash_refund) else: if offer.lock_type == ABS_LOCK_BLOCKS: lock_value = self.callcoinrpc(coin_from, 'getblockchaininfo')['blocks'] + offer.lock_value else: lock_value = int(time.time()) + offer.lock_value self.log.debug('Initiate %s lock_value %d %d', coin_from, offer.lock_value, lock_value) - script = buildContractScript(lock_value, secret_hash, bid.pkhash_buyer, pkhash_refund, OpCodes.OP_CHECKLOCKTIMEVERIFY) + script = atomic_swap_1.buildContractScript(lock_value, secret_hash, bid.pkhash_buyer, pkhash_refund, OpCodes.OP_CHECKLOCKTIMEVERIFY) p2sh = self.callcoinrpc(Coins.PART, 'decodescript', [script.hex()])['p2sh'] @@ -2061,7 +2033,7 @@ class BasicSwap(BaseApp): lock_value = offer.lock_value // 2 if offer.lock_type < ABS_LOCK_BLOCKS: sequence = getExpectedSequence(offer.lock_type, lock_value, coin_to) - participate_script = buildContractScript(sequence, secret_hash, pkhash_seller, pkhash_buyer_refund) + participate_script = atomic_swap_1.buildContractScript(sequence, secret_hash, pkhash_seller, pkhash_buyer_refund) else: # Lock from the height or time of the block containing the initiate txn coin_from = Coins(offer.coin_from) @@ -2092,7 +2064,7 @@ class BasicSwap(BaseApp): self.log.debug('Setting lock value from time of block %s %s', coin_from, initiate_tx_block_hash) contract_lock_value = initiate_tx_block_time + lock_value self.log.debug('participate %s lock_value %d %d', coin_to, lock_value, contract_lock_value) - participate_script = buildContractScript(contract_lock_value, secret_hash, pkhash_seller, pkhash_buyer_refund, OpCodes.OP_CHECKLOCKTIMEVERIFY) + participate_script = atomic_swap_1.buildContractScript(contract_lock_value, secret_hash, pkhash_seller, pkhash_buyer_refund, OpCodes.OP_CHECKLOCKTIMEVERIFY) return participate_script def createParticipateTxn(self, bid_id, bid, offer, participate_script): @@ -3630,6 +3602,15 @@ class BasicSwap(BaseApp): if not ci_from.verifyPubkey(xmr_swap.pkarl): raise ValueError('Invalid pubkey.') + if xmr_swap.pkbvl == xmr_swap.pkbvf: + raise ValueError('Duplicate scriptless view pubkey.') + if xmr_swap.pkbsl == xmr_swap.pkbsf: + raise ValueError('Duplicate scriptless spend pubkey.') + if xmr_swap.pkal == xmr_swap.pkaf: + raise ValueError('Duplicate script spend pubkey.') + if xmr_swap.pkarl == xmr_swap.pkarf: + raise ValueError('Duplicate script spend pubkey.') + bid.setState(BidStates.SWAP_DELAYING) self.saveBidInSession(bid.bid_id, bid, session, xmr_swap) diff --git a/basicswap/ecc_util.py b/basicswap/ecc_util.py index e88a010..e8b9d99 100644 --- a/basicswap/ecc_util.py +++ b/basicswap/ecc_util.py @@ -19,13 +19,14 @@ class ECCParameters(): self.o = o -ep = ECCParameters( \ - p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f, \ - a = 0x0, \ - b = 0x7, \ - Gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, \ - Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8, \ - o = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141) # noqa: E221,E251,E502 +ep = ECCParameters( + p=0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f, + a=0x0, + b=0x7, + Gx=0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, + Gy=0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8, + o=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141) + curve_secp256k1 = CurveFp(ep.p, ep.a, ep.b) G = Point(curve_secp256k1, ep.Gx, ep.Gy, ep.o) diff --git a/basicswap/interface_btc.py b/basicswap/interface_btc.py index 4cf4283..0026e6b 100644 --- a/basicswap/interface_btc.py +++ b/basicswap/interface_btc.py @@ -670,7 +670,7 @@ class BTCInterface(CoinInterface): assert_cond(tx.vin[0].prevout.hash == b2i(lock_tx_id) and tx.vin[0].prevout.n == locked_n, 'Input prevout mismatch') assert_cond(len(tx.vout) == 1, 'tx doesn\'t have one output') - p2wpkh = self.getScriptForPubkeyHash(a_pkhash_f) + p2wpkh = self.getScriptForPubkeyHash(a_pkhash_f) assert_cond(tx.vout[0].scriptPubKey == p2wpkh, 'Bad output destination') fee_paid = locked_coin - tx.vout[0].nValue diff --git a/basicswap/interface_xmr.py b/basicswap/interface_xmr.py index 7a3d8d8..01f266e 100644 --- a/basicswap/interface_xmr.py +++ b/basicswap/interface_xmr.py @@ -159,6 +159,8 @@ class XMRInterface(CoinInterface): return(i < edf.l and i > 8) def verifyPubkey(self, pubkey_bytes): + # Calls ed25519_decode_check_point() in secp256k1 + # Checks for small order return verify_ed25519_point(pubkey_bytes) def proveDLEAG(self, key): @@ -240,13 +242,13 @@ class XMRInterface(CoinInterface): except Exception as e: logging.info('rpc_cb failed %s', str(e)) current_height = None # If the transfer is available it will be deep enough + # and (current_height is None or current_height - transfer['block_height'] > cb_block_confirmed): ''' params = {'transfer_type': 'available'} rv = self.rpc_wallet_cb('incoming_transfers', params) if 'transfers' in rv: for transfer in rv['transfers']: if transfer['amount'] == cb_swap_value: - # and (current_height is None or current_height - transfer['block_height'] > cb_block_confirmed): return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': 0 if 'block_height' not in transfer else transfer['block_height']} else: logging.warning('Incorrect amount detected for coin b lock txn: {}'.format(transfer['tx_hash'])) diff --git a/basicswap/protocols/__init__.py b/basicswap/protocols/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/basicswap/protocols/atomic_swap_1.py b/basicswap/protocols/atomic_swap_1.py new file mode 100644 index 0000000..9490be6 --- /dev/null +++ b/basicswap/protocols/atomic_swap_1.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 tecnovert +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +from basicswap.util import ( + SerialiseNum, +) +from basicswap.script import ( + OpCodes, +) + + +def buildContractScript(lock_val, secret_hash, pkh_redeem, pkh_refund, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY): + script = bytearray([ + OpCodes.OP_IF, + OpCodes.OP_SIZE, + 0x01, 0x20, # 32 + OpCodes.OP_EQUALVERIFY, + OpCodes.OP_SHA256, + 0x20]) \ + + secret_hash \ + + bytearray([ + OpCodes.OP_EQUALVERIFY, + OpCodes.OP_DUP, + OpCodes.OP_HASH160, + 0x14]) \ + + pkh_redeem \ + + bytearray([OpCodes.OP_ELSE, ]) \ + + SerialiseNum(lock_val) \ + + bytearray([ + op_lock, + OpCodes.OP_DROP, + OpCodes.OP_DUP, + OpCodes.OP_HASH160, + 0x14]) \ + + pkh_refund \ + + bytearray([ + OpCodes.OP_ENDIF, + OpCodes.OP_EQUALVERIFY, + OpCodes.OP_CHECKSIG]) + return script diff --git a/basicswap/protocols/xmr_swap_1.py b/basicswap/protocols/xmr_swap_1.py new file mode 100644 index 0000000..8c1d940 --- /dev/null +++ b/basicswap/protocols/xmr_swap_1.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 tecnovert +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py index e532b4a..ddecbc8 100644 --- a/tests/basicswap/test_xmr.py +++ b/tests/basicswap/test_xmr.py @@ -760,6 +760,36 @@ class Test(unittest.TestCase): self.wait_for_bid(swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=180) self.wait_for_bid(swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True) + def test_10_locked_refundtx(self): + logging.info('---------- Test Refund tx is locked') + swap_clients = self.swap_clients + offer_id = swap_clients[0].postOffer(Coins.BTC, Coins.XMR, 10 * COIN, 100 * XMR_COIN, 10 * COIN, SwapTypes.XMR_SWAP) + self.wait_for_offer(swap_clients[1], offer_id) + offers = swap_clients[1].listOffers(filters={'offer_id': offer_id}) + offer = offers[0] + + bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from) + + self.wait_for_bid(swap_clients[0], bid_id, BidStates.BID_RECEIVED) + + bid, xmr_swap = swap_clients[0].getXmrBid(bid_id) + assert(xmr_swap) + + swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK) + + swap_clients[0].acceptXmrBid(bid_id) + + self.wait_for_bid(swap_clients[0], bid_id, BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED, wait_for=180) + + bid, xmr_swap = swap_clients[0].getXmrBid(bid_id) + assert(xmr_swap) + + try: + swap_clients[0].ci(Coins.BTC).publishTx(xmr_swap.a_lock_refund_tx) + assert(False), 'Lock refund tx should be locked' + except Exception as e: + assert('non-BIP68-final' in str(e)) + if __name__ == '__main__': unittest.main()