diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index c9922e9..f3401d8 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -7659,6 +7659,9 @@ class BasicSwap(BaseApp): return bid = self.getBid(bid_id) + if bid is None: + raise ValueError('Bid not found.') + bid.debug_ind = debug_ind # Update in memory copy. TODO: Improve diff --git a/basicswap/db.py b/basicswap/db.py index f45d57a..d946997 100644 --- a/basicswap/db.py +++ b/basicswap/db.py @@ -351,7 +351,7 @@ class XmrSwap(Base): vkbv = sa.Column(sa.LargeBinary) # chain b view private key pkbv = sa.Column(sa.LargeBinary) # chain b view public key - pkbs = sa.Column(sa.LargeBinary) # chain b view spend key + pkbs = sa.Column(sa.LargeBinary) # chain b spend public key a_lock_tx = sa.Column(sa.LargeBinary) a_lock_tx_script = sa.Column(sa.LargeBinary) diff --git a/basicswap/interface/xmr.py b/basicswap/interface/xmr.py index 70c3cee..913ea65 100644 --- a/basicswap/interface/xmr.py +++ b/basicswap/interface/xmr.py @@ -452,6 +452,8 @@ class XMRInterface(CoinInterface): if len(txns) > 0: txid = txns[0]['txid'] self._log.warning(f'spendBLockTx detected spending tx: {txid}.') + + # Should check for address_to, but only the from address is found in the output if txns[0]['address'] == address_b58: return bytes.fromhex(txid) @@ -472,7 +474,6 @@ class XMRInterface(CoinInterface): params['priority'] = self._fee_priority rv = self.rpc_wallet('sweep_all', params) - self._log.debug('sweep_all {}'.format(json.dumps(rv))) return bytes.fromhex(rv['tx_hash_list'][0]) diff --git a/basicswap/protocols/xmr_swap_1.py b/basicswap/protocols/xmr_swap_1.py index 4ce6a14..18b7e73 100644 --- a/basicswap/protocols/xmr_swap_1.py +++ b/basicswap/protocols/xmr_swap_1.py @@ -4,6 +4,8 @@ # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. +import traceback + from basicswap.util import ( ensure, ) @@ -41,7 +43,7 @@ def addLockRefundSigs(self, xmr_swap, ci): def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, session=None): - self.log.info('Manually recovering %s', bid_id.hex()) + self.log.info(f'Manually recovering {bid_id.hex()}') # Manually recover txn if other key is known try: use_session = self.openSession(session) @@ -51,37 +53,57 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, session=None): offer, xmr_offer = self.getXmrOfferFromSession(use_session, bid.offer_id, sent=False) ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) - ci_to = self.ci(offer.coin_to) - for_ed25519 = True if Coins(offer.coin_to) == Coins.XMR else False + # The no-script coin is always the follower + reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from) + ci_from = self.ci(Coins(offer.coin_from)) + ci_to = self.ci(Coins(offer.coin_to)) + ci_leader = ci_to if reverse_bid else ci_from + ci_follower = ci_from if reverse_bid else ci_to try: - decoded_key_half = ci_to.decodeKey(encoded_key) + decoded_key_half = ci_follower.decodeKey(encoded_key) except Exception as e: raise ValueError('Failed to decode provided key-half: ', str(e)) - if bid.was_sent: - kbsl = decoded_key_half - kbsf = self.getPathKey(offer.coin_from, offer.coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KBSF, for_ed25519) - else: - kbsl = self.getPathKey(offer.coin_from, offer.coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KBSL, for_ed25519) - kbsf = decoded_key_half - ensure(ci_to.verifyKey(kbsl), 'Invalid kbsl') - ensure(ci_to.verifyKey(kbsf), 'Invalid kbsf') - vkbs = ci_to.sumKeys(kbsl, kbsf) + was_sent: bool = bid.was_received if reverse_bid else bid.was_sent - if offer.coin_to == Coins.XMR: - address_to = self.getCachedMainWalletAddress(ci_to, use_session) + localkeyhalf = ci_follower.decodeKey(getChainBSplitKey(self, bid, xmr_swap, offer)) + if was_sent: + kbsl = decoded_key_half + kbsf = localkeyhalf else: - address_to = self.getCachedStealthAddressForCoin(offer.coin_to, use_session) + kbsl = localkeyhalf + kbsf = decoded_key_half + + ensure(ci_follower.verifyKey(kbsl), 'Invalid kbsl') + ensure(ci_follower.verifyKey(kbsf), 'Invalid kbsf') + vkbs = ci_follower.sumKeys(kbsl, kbsf) + + # Ensure summed key matches the expected pubkey + summed_pkbs = ci_follower.getPubkey(vkbs) + if (summed_pkbs != xmr_swap.pkbs): + err_msg: str = 'Summed key does not match expected wallet' + have_pk = summed_pkbs.hex() + expect_pk = xmr_swap.pkbs.hex() + self.log.error(f'{err_msg}. Got: {have_pk}, Expect: {expect_pk}') + raise ValueError(err_msg) + + if ci_follower.coin_type() in (Coins.XMR, Coins.WOW): + address_to = self.getCachedMainWalletAddress(ci_follower, use_session) + else: + address_to = self.getCachedStealthAddressForCoin(ci_follower.coin_type(), use_session) amount = bid.amount_to lock_tx_vout = bid.getLockTXBVout() - txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, amount, xmr_offer.b_fee_rate, bid.chain_b_height_start, spend_actual_balance=True, lock_tx_vout=lock_tx_vout) - self.log.debug('Submitted lock B spend txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex()) + txid = ci_follower.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, amount, xmr_offer.b_fee_rate, bid.chain_b_height_start, spend_actual_balance=True, lock_tx_vout=lock_tx_vout) + self.log.debug('Submitted lock B spend txn %s to %s chain for bid %s', txid.hex(), ci_follower.coin_name(), bid_id.hex()) self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, txid.hex(), use_session) use_session.commit() return txid + except Exception as e: + self.log.error(traceback.format_exc()) + raise (e) finally: if session is None: self.closeSession(use_session, commit=False) @@ -89,10 +111,14 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, session=None): def getChainBSplitKey(swap_client, bid, xmr_swap, offer): reverse_bid: bool = offer.bid_reversed + ci_leader = swap_client.ci(offer.coin_to if reverse_bid else offer.coin_from) ci_follower = swap_client.ci(offer.coin_from if reverse_bid else offer.coin_to) - key_type = KeyTypes.KBSF if bid.was_sent else KeyTypes.KBSL - return ci_follower.encodeKey(swap_client.getPathKey(offer.coin_from, offer.coin_to, bid.created_at, xmr_swap.contract_count, key_type, True if ci_follower.coin_type() == Coins.XMR else False)) + for_ed25519: bool = True if ci_follower.curve_type() == Curves.ed25519 else False + was_sent: bool = bid.was_received if reverse_bid else bid.was_sent + + key_type = KeyTypes.KBSF if was_sent else KeyTypes.KBSL + return ci_follower.encodeKey(swap_client.getPathKey(ci_leader.coin_type(), ci_follower.coin_type(), bid.created_at, xmr_swap.contract_count, key_type, for_ed25519)) def getChainBRemoteSplitKey(swap_client, bid, xmr_swap, offer): diff --git a/basicswap/templates/bid_xmr.html b/basicswap/templates/bid_xmr.html index b72c13f..f64dc3a 100644 --- a/basicswap/templates/bid_xmr.html +++ b/basicswap/templates/bid_xmr.html @@ -35,7 +35,7 @@
BID ID: {{ bid_id }}
{% include 'footer.html' %}