From 0e2011e085fabe1adc2da73c2bcbb44fa51c18d3 Mon Sep 17 00:00:00 2001 From: tecnovert Date: Sat, 21 Nov 2020 15:16:27 +0200 Subject: [PATCH] XMR successful swap works. --- basicswap/basicswap.py | 507 +++++++++++++++++++++++++++++++-- basicswap/contrib/Keccak.py | 348 ++++++++++++++++++++++ basicswap/db.py | 10 + basicswap/interface_btc.py | 52 +++- basicswap/interface_part.py | 1 + basicswap/interface_xmr.py | 87 ++++-- basicswap/messages.proto | 5 + basicswap/messages_pb2.py | 49 +++- basicswap/util_xmr.py | 2 +- tests/basicswap/test_nmc.py | 2 +- tests/basicswap/test_reload.py | 2 +- tests/basicswap/test_run.py | 2 +- tests/basicswap/test_xmr.py | 42 ++- 13 files changed, 1029 insertions(+), 80 deletions(-) create mode 100644 basicswap/contrib/Keccak.py diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 7fd94a7..5f55f01 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -59,6 +59,7 @@ from .messages_pb2 import ( XmrSplitMessage, XmrBidLockTxSigsMessage, XmrBidLockSpendTxMessage, + XmrBidSecretMessage, ) from .db import ( CURRENT_DB_VERSION, @@ -80,6 +81,8 @@ from .db import ( from .explorers import ExplorerInsight, ExplorerBitAps, ExplorerChainz import basicswap.config as cfg from .base import BaseApp +from .ecc_util import ( + b2i, i2b) MIN_OFFER_VALID_TIME = 60 * 10 @@ -94,11 +97,12 @@ class MessageTypes(IntEnum): BID_ACCEPT = auto() XMR_OFFER = auto() - XMR_BID = auto() + XMR_BID_FL = auto() XMR_BID_SPLIT = auto() - XMR_BID_ACCEPT = auto() + XMR_BID_ACCEPT_LF = auto() XMR_BID_TXN_SIGS_FL = auto() - XMR_BID_LOCK_REFUND_SPEND_TX_LF = auto() + XMR_BID_LOCK_SPEND_TX_LF = auto() + XMR_BID_SECRET_LF = auto() class SwapTypes(IntEnum): @@ -125,6 +129,11 @@ class BidStates(IntEnum): SWAP_PARTICIPATING = auto() # Participate txn validated SWAP_COMPLETED = auto() # All swap txns spent XMR_SWAP_SCRIPT_COIN_LOCKED = auto() + XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX = auto() + XMR_SWAP_NOSCRIPT_COIN_LOCKED = auto() + XMR_SWAP_SECRET_SHARED = auto() + XMR_SWAP_SCRIPT_TX_REDEEMED = auto() + XMR_SWAP_NOSCRIPT_TX_REDEEMED = auto() SWAP_DELAYING = auto() SWAP_TIMEDOUT = auto() BID_ABANDONED = auto() # Bid will no longer be processed @@ -148,14 +157,20 @@ class TxTypes(IntEnum): PTX_REFUND = auto() XMR_SWAP_A_LOCK = auto() + XMR_SWAP_A_LOCK_SPEND = auto() XMR_SWAP_A_LOCK_REFUND = auto() XMR_SWAP_A_LOCK_REFUND_SPEND = auto() + XMR_SWAP_B_LOCK = auto() class EventTypes(IntEnum): ACCEPT_BID = auto() SIGN_XMR_SWAP_LOCK_TX_A = auto() SEND_XMR_SWAP_LOCK_TX_A = auto() + SEND_XMR_SWAP_LOCK_TX_B = auto() + SEND_XMR_SECRET = auto() + REDEEM_XMR_SWAP_LOCK_TX_A = auto() # Follower + REDEEM_XMR_SWAP_LOCK_TX_B = auto() # Leader class XmrSplitMsgTypes(IntEnum): @@ -487,7 +502,10 @@ class BasicSwap(BaseApp): elif coin == Coins.NMC: return NMCInterface(self.coin_clients[coin], self.chain) elif coin == Coins.XMR: - return XMRInterface(self.coin_clients[coin], self.chain) + xmr_i = XMRInterface(self.coin_clients[coin], self.chain) + chain_client_settings = self.getChainClientSettings(coin) + xmr_i.setWalletFilename(chain_client_settings['walletfile']) + return xmr_i else: raise ValueError('Unknown coin type') @@ -1076,6 +1094,10 @@ class BasicSwap(BaseApp): session.add(bid.participate_tx) if bid.xmr_a_lock_tx: session.add(bid.xmr_a_lock_tx) + if bid.xmr_b_lock_tx: + session.add(bid.xmr_b_lock_tx) + if bid.xmr_a_lock_spend_tx: + session.add(bid.xmr_a_lock_spend_tx) if xmr_swap is not None: session.add(xmr_swap) @@ -1208,6 +1230,20 @@ class BasicSwap(BaseApp): session.remove() self.mxDB.release() + def loadBidTxns(self, bid, session): + for stx in session.query(SwapTx).filter(sa.and_(SwapTx.bid_id == bid.bid_id)): + if stx.tx_type == TxTypes.ITX: + bid.initiate_tx = stx + elif stx.tx_type == TxTypes.PTX: + bid.participate_tx = stx + elif stx.tx_type == TxTypes.XMR_SWAP_A_LOCK: + bid.xmr_a_lock_tx = stx + elif stx.tx_type == TxTypes.XMR_SWAP_A_LOCK_SPEND: + bid.xmr_a_lock_spend_tx = stx + elif stx.tx_type == TxTypes.XMR_SWAP_B_LOCK: + bid.xmr_b_lock_tx = stx + else: + self.log.warning('Unknown transaction type: {}'.format(stx.tx_type)) def getXmrBid(self, bid_id, sent=False): self.mxDB.acquire() try: @@ -1216,6 +1252,7 @@ class BasicSwap(BaseApp): xmr_swap = None if bid: xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid_id).first() + self.loadBidTxns(bid, session) return bid, xmr_swap finally: session.close() @@ -1242,8 +1279,7 @@ class BasicSwap(BaseApp): session = scoped_session(self.session_factory) bid = session.query(Bid).filter_by(bid_id=bid_id).first() if bid: - bid.initiate_tx = session.query(SwapTx).filter(sa.and_(SwapTx.bid_id == bid_id, SwapTx.tx_type == TxTypes.ITX)).first() - bid.participate_tx = session.query(SwapTx).filter(sa.and_(SwapTx.bid_id == bid_id, SwapTx.tx_type == TxTypes.PTX)).first() + self.loadBidTxns(bid, session) return bid finally: session.close() @@ -1399,10 +1435,16 @@ class BasicSwap(BaseApp): kaf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 3) karf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 4) + xmr_swap.vkbvf = kbvf + xmr_swap.pkbvf = ci_to.getPubkey(kbvf) + xmr_swap.pkbsf = ci_to.getPubkey(kbsf) + xmr_swap.pkaf = ci_from.getPubkey(kaf) xmr_swap.pkarf = ci_from.getPubkey(karf) xmr_swap.kbsf_dleag = ci_to.proveDLEAG(kbsf) + xmr_swap.pkasf = xmr_swap.kbsf_dleag[0: 33] + assert(xmr_swap.pkasf == ci_from.getPubkey(kbsf)) msg_buf.pkaf = xmr_swap.pkaf msg_buf.pkarf = xmr_swap.pkarf @@ -1410,7 +1452,7 @@ class BasicSwap(BaseApp): msg_buf.kbsf_dleag = xmr_swap.kbsf_dleag[:16000] bid_bytes = msg_buf.SerializeToString() - payload_hex = str.format('{:02x}', MessageTypes.XMR_BID) + bid_bytes.hex() + payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_FL) + bid_bytes.hex() if addr_send_from is None: bid_addr = self.callrpc('getnewaddress') @@ -1469,7 +1511,7 @@ class BasicSwap(BaseApp): session.close() session.remove() - self.log.info('Sent XMR_BID %s', xmr_swap.bid_id.hex()) + self.log.info('Sent XMR_BID_FL %s', xmr_swap.bid_id.hex()) return xmr_swap.bid_id finally: self.mxDB.release() @@ -1496,7 +1538,6 @@ class BasicSwap(BaseApp): ci_from = self.ci(coin_from) ci_to = self.ci(coin_to) - self.log.debug('[rm] acceptXmrBid bid.created_at %d', bid.created_at) if xmr_swap.contract_count is None: xmr_swap.contract_count = self.getNewContractId() @@ -1508,6 +1549,14 @@ class BasicSwap(BaseApp): kal = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 4) karl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 5) + xmr_swap.vkbvl = kbvl + xmr_swap.pkbvl = ci_to.getPubkey(kbvl) + xmr_swap.pkbsl = ci_to.getPubkey(kbsl) + + xmr_swap.vkbv = ci_to.sumKeys(kbvl, xmr_swap.vkbvf) + xmr_swap.pkbv = ci_to.sumPubkeys(xmr_swap.pkbvl, xmr_swap.pkbvf) + xmr_swap.pkbs = ci_to.sumPubkeys(xmr_swap.pkbsl, xmr_swap.pkbsf) + xmr_swap.sh = hashlib.sha256(contract_secret).digest() xmr_swap.pkal = ci_from.getPubkey(kal) xmr_swap.pkarl = ci_from.getPubkey(karl) @@ -1525,7 +1574,7 @@ class BasicSwap(BaseApp): xmr_swap.a_lock_tx = ci_from.fundTx(xmr_swap.a_lock_tx, xmr_offer.a_fee_rate) xmr_swap.a_lock_tx_id = ci_from.getTxHash(xmr_swap.a_lock_tx) - xmr_swap.a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script) + a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script) xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value = ci_from.createScriptLockRefundTx( xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script, @@ -1563,8 +1612,7 @@ class BasicSwap(BaseApp): msg_buf.al_lock_refund_tx_sig = xmr_swap.al_lock_refund_tx_sig msg_bytes = msg_buf.SerializeToString() - self.log.debug('[rm] acceptXmrBid len(msg_bytes) %d', len(msg_bytes)) - payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_ACCEPT) + msg_bytes.hex() + payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_ACCEPT_LF) + msg_bytes.hex() options = {'decodehex': True, 'ttl_is_seconds': True} msg_valid = self.SMSG_SECONDS_IN_HOUR * 48 ro = self.callrpc('smsgsend', [offer.addr_from, bid.bid_addr, payload_hex, False, msg_valid, False, options]) @@ -1604,7 +1652,7 @@ class BasicSwap(BaseApp): #self.swaps_in_progress[bid_id] = (bid, offer) # Add to swaps_in_progress only when waiting on txns - self.log.info('Sent XMR_BID_ACCEPT %s', bid_id.hex()) + self.log.info('Sent XMR_BID_ACCEPT_LF %s', bid_id.hex()) return bid_id finally: self.mxDB.release() @@ -2138,7 +2186,155 @@ class BasicSwap(BaseApp): return None def checkXmrBidState(self, bid_id, bid, offer): - pass + rv = False + state = BidStates(bid.state) + if state == BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX: + if bid.xmr_a_lock_tx is None: + return + ci_from = self.ci(Coins(offer.coin_from)) + ci_to = self.ci(Coins(offer.coin_to)) + + try: + self.mxDB.acquire() + session = scoped_session(self.session_factory) + xmr_offer = session.query(XmrOffer).filter_by(offer_id=offer.offer_id).first() + assert(xmr_offer), 'XMR offer not found: {}.'.format(offer.offer_id.hex()) + xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first() + assert(xmr_swap), 'XMR swap not found: {}.'.format(bid.bid_id.hex()) + + # TODO: Timeout waiting for transactions + + a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script) + utxos = ci_from.getOutput(bid.xmr_a_lock_tx.txid, a_lock_tx_dest, bid.amount) + + if len(utxos) < 1: + return + + if len(utxos) > 1: + raise ValueError('Too many outputs for chain A lock tx') + + utxo = utxos[0] + if utxo['depth'] >= ci_from.blocks_confirmed: + bid.xmr_a_lock_tx.setState(TxStates.TX_CONFIRMED) + bid.setState(BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED) + self.saveBidInSession(bid_id, bid, session, xmr_swap) + + if bid.was_sent: + delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept) + self.log.info('Sending xmr swap chain B lock tx for bid %s in %d seconds', bid_id.hex(), delay) + self.createEventInSession(delay, EventTypes.SEND_XMR_SWAP_LOCK_TX_B, bid_id, session) + #bid.setState(BidStates.SWAP_DELAYING) + + session.commit() + + session.close() + session.remove() + except Exception as ex: + session.close() + session.remove() + raise ex + finally: + self.mxDB.release() + elif state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED: + if bid.was_sent and bid.xmr_b_lock_tx is None: + return + ci_from = self.ci(Coins(offer.coin_from)) + ci_to = self.ci(Coins(offer.coin_to)) + + try: + self.mxDB.acquire() + session = scoped_session(self.session_factory) + xmr_offer = session.query(XmrOffer).filter_by(offer_id=offer.offer_id).first() + assert(xmr_offer), 'XMR offer not found: {}.'.format(offer.offer_id.hex()) + xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first() + assert(xmr_swap), 'XMR swap not found: {}.'.format(bid.bid_id.hex()) + + rv = ci_to.findTxB(xmr_swap.vkbv, xmr_swap.pkbs, bid.amount_to, ci_to.blocks_confirmed, xmr_swap.b_restore_height) + + if rv is not None: + + if bid.xmr_b_lock_tx is None: + b_lock_tx_id = bytes.fromhex(rv['txid']) + bid.xmr_b_lock_tx = SwapTx( + bid_id=bid_id, + tx_type=TxTypes.XMR_SWAP_B_LOCK, + txid=b_lock_tx_id, + ) + + bid.xmr_b_lock_tx.setState(TxStates.TX_CONFIRMED) + bid.setState(BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED) + self.saveBidInSession(bid_id, bid, session, xmr_swap) + + if bid.was_received: + delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept) + self.log.info('Releasing xmr swap secret for bid %s in %d seconds', bid_id.hex(), delay) + self.createEventInSession(delay, EventTypes.SEND_XMR_SECRET, bid_id, session) + + session.commit() + + session.close() + session.remove() + except Exception as ex: + session.close() + session.remove() + raise ex + finally: + self.mxDB.release() + # Waiting for initiate txn to be confirmed in 'from' chain + #initiate_txnid_hex = bid.initiate_tx.txid.hex() + #p2sh = self.getScriptAddress(coin_from, bid.initiate_tx.script) + elif state == BidStates.XMR_SWAP_SECRET_SHARED: + # Wait for script spend tx to confirm + ci_from = self.ci(Coins(offer.coin_from)) + ci_to = self.ci(Coins(offer.coin_to)) + + try: + self.mxDB.acquire() + session = scoped_session(self.session_factory) + xmr_offer = session.query(XmrOffer).filter_by(offer_id=offer.offer_id).first() + assert(xmr_offer), 'XMR offer not found: {}.'.format(offer.offer_id.hex()) + xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first() + assert(xmr_swap), 'XMR swap not found: {}.'.format(bid.bid_id.hex()) + + # TODO: Use explorer to get tx / block hash for getrawtransaction + rv = ci_from.getTransaction(xmr_swap.a_lock_spend_tx_id) + if rv is not None: + xmr_swap.a_lock_spend_tx = rv + + #bid.xmr_a_lock_spend_tx.setState(TxStates.TX_CONFIRMED) + bid.setState(BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED) # TODO: Wait for confirmation? + + if not bid.was_received: + rv = True # Remove from return False + bid.setState(BidStates.SWAP_COMPLETED) + self.saveBidInSession(bid_id, bid, session, xmr_swap) + if bid.was_received: + delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept) + self.log.info('Redeeming coin b lock tx for bid %s in %d seconds', bid_id.hex(), delay) + self.createEventInSession(delay, EventTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id, session) + + session.commit() + + session.close() + session.remove() + except Exception as ex: + session.close() + session.remove() + raise ex + finally: + self.mxDB.release() + elif state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED: + ci_to = self.ci(Coins(offer.coin_to)) + txid_hex = bid.xmr_b_lock_tx.spend_txid.hex() + + rv = ci_to.findTxnByHash(txid_hex) + if rv is not None: + rv = True # Remove from return False + bid.setState(BidStates.SWAP_COMPLETED) + self.saveBid(bid_id, bid) + + return rv + def checkBidState(self, bid_id, bid, offer): # assert(self.mxDB.locked()) @@ -2464,6 +2660,14 @@ class BasicSwap(BaseApp): self.sendXmrBidTxnSigsFtoL(row.linked_id, session) elif row.event_type == EventTypes.SEND_XMR_SWAP_LOCK_TX_A: self.sendXmrBidCoinALockTx(row.linked_id, session) + elif row.event_type == EventTypes.SEND_XMR_SWAP_LOCK_TX_B: + self.sendXmrBidCoinBLockTx(row.linked_id, session) + elif row.event_type == EventTypes.SEND_XMR_SECRET: + self.sendXmrBidSecret(row.linked_id, session) + elif row.event_type == EventTypes.REDEEM_XMR_SWAP_LOCK_TX_A: + self.redeemXmrBidCoinALockTx(row.linked_id, session) + elif row.event_type == EventTypes.REDEEM_XMR_SWAP_LOCK_TX_B: + self.redeemXmrBidCoinBLockTx(row.linked_id, session) else: self.log.warning('Unknown event type: %d', row.event_type) except Exception as ex: @@ -2808,6 +3012,7 @@ class BasicSwap(BaseApp): bid.setState(BidStates.BID_RECEIVED) self.saveBidInSession(bid.bid_id, bid, session, xmr_swap) + def receiveXmrBidAccept(self, bid, session): # Follower receiving MSG1F and MSG2F self.log.debug('Receiving xmr bid accept %s', bid.bid_id.hex()) @@ -2843,6 +3048,10 @@ class BasicSwap(BaseApp): if not ci_to.verifyPubkey(xmr_swap.pkbsl): raise ValueError('Invalid coin b pubkey.') + xmr_swap.vkbv = ci_to.sumKeys(xmr_swap.vkbvl, xmr_swap.vkbvf) + xmr_swap.pkbv = ci_to.sumPubkeys(xmr_swap.pkbvl, xmr_swap.pkbvf) + xmr_swap.pkbs = ci_to.sumPubkeys(xmr_swap.pkbsl, xmr_swap.pkbsf) + if not ci_from.verifyPubkey(xmr_swap.pkal): raise ValueError('Invalid pubkey.') if not ci_from.verifyPubkey(xmr_swap.pkarl): @@ -2857,6 +3066,7 @@ class BasicSwap(BaseApp): self.createEventInSession(delay, EventTypes.SIGN_XMR_SWAP_LOCK_TX_A, bid.bid_id, session) def processXmrBid(self, msg): + # MSG1L self.log.debug('Processing xmr bid msg %s', msg['msgid']) now = int(time.time()) bid_bytes = bytes.fromhex(msg['hex'][2:-2]) @@ -2873,6 +3083,8 @@ class BasicSwap(BaseApp): assert(xmr_offer), 'XMR offer not found: {}.'.format(offer_id.hex()) coin_from = Coins(offer.coin_from) coin_to = Coins(offer.coin_to) + ci_from = self.ci(coin_from) + ci_to = self.ci(coin_to) logging.debug('TODO: xmr bid validation') @@ -2899,6 +3111,7 @@ class BasicSwap(BaseApp): pkaf=bid_data.pkaf, pkarf=bid_data.pkarf, vkbvf=bid_data.kbvf, + pkbvf=ci_to.getPubkey(bid_data.kbvf), kbsf_dleag=bid_data.kbsf_dleag, b_restore_height=self.ci(coin_to).getChainHeight(), ) @@ -2942,6 +3155,7 @@ class BasicSwap(BaseApp): xmr_swap.pkal = msg_data.pkal xmr_swap.pkarl = msg_data.pkarl xmr_swap.vkbvl = msg_data.kbvl + xmr_swap.pkbvl = ci_to.getPubkey(msg_data.kbvl) xmr_swap.kbsl_dleag = msg_data.kbsl_dleag xmr_swap.a_lock_tx = msg_data.a_lock_tx @@ -2961,7 +3175,7 @@ class BasicSwap(BaseApp): xmr_swap.pkarl, xmr_swap.pkarf, check_a_lock_tx_inputs ) - xmr_swap.a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script) + a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script) lock_refund_tx_id, xmr_swap.a_swap_refund_value = ci_from.verifyLockRefundTx( xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script, @@ -3008,7 +3222,6 @@ class BasicSwap(BaseApp): try: karf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 4) - print('[rm] xmr_swap.a_swap_refund_value', xmr_swap.a_swap_refund_value) xmr_swap.af_lock_refund_spend_tx_esig = ci_from.signTxOtVES(karf, xmr_swap.pkasl, xmr_swap.a_lock_refund_spend_tx, 0, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value) xmr_swap.af_lock_refund_tx_sig = ci_from.signTx(karf, xmr_swap.a_lock_refund_tx, 0, xmr_swap.a_lock_tx_script, bid.amount) @@ -3018,8 +3231,8 @@ class BasicSwap(BaseApp): af_lock_refund_tx_sig=xmr_swap.af_lock_refund_tx_sig ) - mag_bytes = msg_buf.SerializeToString() - payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_TXN_SIGS_FL) + mag_bytes.hex() + msg_bytes = msg_buf.SerializeToString() + payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_TXN_SIGS_FL) + msg_bytes.hex() options = {'decodehex': True, 'ttl_is_seconds': True} # TODO: set msg_valid based on bid / offer parameters @@ -3029,7 +3242,16 @@ class BasicSwap(BaseApp): self.log.info('Sent XMR_BID_TXN_SIGS_FL %s', xmr_swap.coin_a_lock_tx_sigs_l_msg_id.hex()) - bid.setState(BidStates.BID_ACCEPTED) + a_lock_tx_id = ci_from.getTxHash(xmr_swap.a_lock_tx) + self.log.debug('Waiting for lock txn %s to %s chain for bid %s', a_lock_tx_id.hex(), chainparams[coin_from]['name'], bid_id.hex()) + bid.xmr_a_lock_tx = SwapTx( + bid_id=bid_id, + tx_type=TxTypes.XMR_SWAP_A_LOCK, + txid=a_lock_tx_id, + ) + bid.xmr_a_lock_tx.setState(TxStates.TX_NONE) + + bid.setState(BidStates.BID_ACCEPTED) # XMR self.saveBidInSession(bid_id, bid, session, xmr_swap) self.swaps_in_progress[bid_id] = (bid, offer) except Exception as ex: @@ -3059,6 +3281,7 @@ class BasicSwap(BaseApp): xmr_swap.dest_af, xmr_offer.a_fee_rate) + xmr_swap.a_lock_spend_tx_id = ci_from.getTxHash(xmr_swap.a_lock_spend_tx) xmr_swap.al_lock_spend_tx_esig = ci_from.signTxOtVES(kal, xmr_swap.pkasf, xmr_swap.a_lock_spend_tx, 0, xmr_swap.a_lock_tx_script, bid.amount) # self.a_swap_value msg_buf = XmrBidLockSpendTxMessage( @@ -3066,8 +3289,8 @@ class BasicSwap(BaseApp): a_lock_spend_tx=xmr_swap.a_lock_spend_tx, al_lock_spend_tx_esig=xmr_swap.al_lock_spend_tx_esig) - mag_bytes = msg_buf.SerializeToString() - payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_LOCK_REFUND_SPEND_TX_LF) + mag_bytes.hex() + msg_bytes = msg_buf.SerializeToString() + payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_LOCK_SPEND_TX_LF) + msg_bytes.hex() options = {'decodehex': True, 'ttl_is_seconds': True} # TODO: set msg_valid based on bid / offer parameters @@ -3089,10 +3312,164 @@ class BasicSwap(BaseApp): ) bid.xmr_a_lock_tx.setState(TxStates.TX_SENT) - bid.setState(BidStates.BID_ACCEPTED) + bid.setState(BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX) self.saveBidInSession(bid_id, bid, session, xmr_swap) self.swaps_in_progress[bid_id] = (bid, offer) + def sendXmrBidCoinBLockTx(self, bid_id, session): + # Follower sending coin B lock tx + self.log.debug('Sending coin B lock tx for xmr bid %s', bid_id.hex()) + + bid, xmr_swap = self.getXmrBid(bid_id) + assert(bid), 'Bid not found: {}.'.format(bid_id.hex()) + assert(xmr_swap), 'XMR swap not found: {}.'.format(bid_id.hex()) + + offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False) + assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex()) + assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex()) + coin_from = Coins(offer.coin_from) + coin_to = Coins(offer.coin_to) + ci_from = self.ci(coin_from) + ci_to = self.ci(coin_to) + + b_lock_tx_id = ci_to.publishBLockTx(xmr_swap.pkbv, xmr_swap.pkbs, bid.amount_to, xmr_offer.b_fee_rate) + + self.log.debug('Submitted lock txn %s to %s chain for bid %s', b_lock_tx_id.hex(), chainparams[coin_to]['name'], bid_id.hex()) + bid.xmr_b_lock_tx = SwapTx( + bid_id=bid_id, + tx_type=TxTypes.XMR_SWAP_B_LOCK, + txid=b_lock_tx_id, + ) + bid.xmr_b_lock_tx.setState(TxStates.TX_NONE) + + self.saveBidInSession(bid_id, bid, session, xmr_swap) + + # Update copy of bid in swaps_in_progress + self.swaps_in_progress[bid_id] = (bid, offer) + + def sendXmrBidSecret(self, bid_id, session): + # Leader sending lock tx a release secret (MSG5F) + self.log.debug('Sending bid secret for xmr bid %s', bid_id.hex()) + + bid, xmr_swap = self.getXmrBid(bid_id) + assert(bid), 'Bid not found: {}.'.format(bid_id.hex()) + assert(xmr_swap), 'XMR swap not found: {}.'.format(bid_id.hex()) + + offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False) + assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex()) + assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex()) + coin_from = Coins(offer.coin_from) + coin_to = Coins(offer.coin_to) + + contract_secret = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 1) + + msg_buf = XmrBidSecretMessage( + bid_msg_id=bid_id, + secret_value=contract_secret) + + msg_bytes = msg_buf.SerializeToString() + payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_SECRET_LF) + msg_bytes.hex() + + options = {'decodehex': True, 'ttl_is_seconds': True} + # TODO: set msg_valid based on bid / offer parameters + msg_valid = self.SMSG_SECONDS_IN_HOUR * 48 + ro = self.callrpc('smsgsend', [offer.addr_from, bid.bid_addr, payload_hex, False, msg_valid, False, options]) + xmr_swap.coin_a_lock_refund_spend_tx_msg_id = bytes.fromhex(ro['msgid']) + + bid.setState(BidStates.XMR_SWAP_SECRET_SHARED) + self.saveBidInSession(bid_id, bid, session, xmr_swap) + # Update copy of bid in swaps_in_progress + self.swaps_in_progress[bid_id] = (bid, offer) + + def redeemXmrBidCoinALockTx(self, bid_id, session): + # Follower redeeming A lock tx + self.log.debug('Redeeming coin A lock tx for xmr bid %s', bid_id.hex()) + + bid, xmr_swap = self.getXmrBid(bid_id) + assert(bid), 'Bid not found: {}.'.format(bid_id.hex()) + assert(xmr_swap), 'XMR swap not found: {}.'.format(bid_id.hex()) + + offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False) + assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex()) + assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex()) + coin_from = Coins(offer.coin_from) + coin_to = Coins(offer.coin_to) + ci_from = self.ci(coin_from) + ci_to = self.ci(coin_to) + + kbsf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 2, for_xmr=True) + kaf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 3) + + al_lock_spend_sig = ci_from.decryptOtVES(kbsf, xmr_swap.al_lock_spend_tx_esig) + v = ci_from.verifyTxSig(xmr_swap.a_lock_spend_tx, al_lock_spend_sig, xmr_swap.pkal, 0, xmr_swap.a_lock_tx_script, bid.amount) + assert(v) + + af_lock_spend_sig = ci_from.signTx(kaf, xmr_swap.a_lock_spend_tx, 0, xmr_swap.a_lock_tx_script, bid.amount) + v = ci_from.verifyTxSig(xmr_swap.a_lock_spend_tx, af_lock_spend_sig, xmr_swap.pkaf, 0, xmr_swap.a_lock_tx_script, bid.amount) + assert(v) + + witness_stack = [ + b'', + al_lock_spend_sig, + af_lock_spend_sig, + xmr_swap.sv, + bytes((1,)), + xmr_swap.a_lock_tx_script, + ] + + xmr_swap.a_lock_spend_tx = ci_from.setTxSignature(xmr_swap.a_lock_spend_tx, witness_stack) + + txid = bytes.fromhex(ci_from.publishTx(xmr_swap.a_lock_spend_tx)) + self.log.debug('Submitted lock spend txn %s to %s chain for bid %s', txid.hex(), chainparams[coin_from]['name'], bid_id.hex()) + bid.xmr_a_lock_spend_tx = SwapTx( + bid_id=bid_id, + tx_type=TxTypes.XMR_SWAP_A_LOCK_SPEND, + txid=txid, + ) + bid.xmr_a_lock_spend_tx.setState(TxStates.TX_NONE) + + self.saveBidInSession(bid_id, bid, session, xmr_swap) + # Update copy of bid in swaps_in_progress + self.swaps_in_progress[bid_id] = (bid, offer) + + def redeemXmrBidCoinBLockTx(self, bid_id, session): + # Leader redeeming B lock tx + self.log.debug('Redeeming coin B lock tx for xmr bid %s', bid_id.hex()) + + bid, xmr_swap = self.getXmrBid(bid_id) + assert(bid), 'Bid not found: {}.'.format(bid_id.hex()) + assert(xmr_swap), 'XMR swap not found: {}.'.format(bid_id.hex()) + + offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False) + assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex()) + assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex()) + coin_from = Coins(offer.coin_from) + coin_to = Coins(offer.coin_to) + ci_from = self.ci(coin_from) + ci_to = self.ci(coin_to) + + # Extract the leader's decrypted signature and use it to recover the follower's privatekey + xmr_swap.al_lock_spend_tx_sig = ci_from.extractLeaderSig(xmr_swap.a_lock_spend_tx) + + xmr_swap.kbsf = ci_from.recoverEncKey(xmr_swap.al_lock_spend_tx_esig, xmr_swap.al_lock_spend_tx_sig, xmr_swap.pkasf) + assert(xmr_swap.kbsf is not None) + + kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 3, for_xmr=True) + + vkbs = ci_to.sumKeys(kbsl, xmr_swap.kbsf) + Kbs_test = ci_to.getPubkey(vkbs) + print('Kbs_test', Kbs_test.hex()) + print('Kbs', xmr_swap.pkbsf.hex()) + + address_to = ci_to.getMainWalletAddress() + + txid = ci_to.spendBLockTx(address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, xmr_swap.b_restore_height) + + bid.xmr_b_lock_tx.spend_txid = txid + self.saveBidInSession(bid_id, bid, session, xmr_swap) + # Update copy of bid in swaps_in_progress + self.swaps_in_progress[bid_id] = (bid, offer) + def processXmrBidCoinALockSigs(self, msg): # Leader processing MSG3L self.log.debug('Processing xmr coin a follower lock sigs msg %s', msg['msgid']) @@ -3123,7 +3500,6 @@ class BasicSwap(BaseApp): kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 3, for_xmr=True) xmr_swap.af_lock_refund_spend_tx_sig = ci_from.decryptOtVES(kbsl, xmr_swap.af_lock_refund_spend_tx_esig) - print('[rm] xmr_swap.a_swap_refund_value', xmr_swap.a_swap_refund_value) v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_spend_tx, xmr_swap.af_lock_refund_spend_tx_sig, xmr_swap.pkarf, 0, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value) assert(v), 'Invalid signature for lock refund spend txn' @@ -3138,6 +3514,54 @@ class BasicSwap(BaseApp): traceback.print_exc() self.setBidError(bid_id, bid, str(ex)) + def processXmrBidLockSpendTx(self, msg): + # Follower receiving MSG4F + self.log.debug('Processing xmr bid lock spend tx msg %s', msg['msgid']) + now = int(time.time()) + msg_bytes = bytes.fromhex(msg['hex'][2:-2]) + msg_data = XmrBidLockSpendTxMessage() + msg_data.ParseFromString(msg_bytes) + + assert(len(msg_data.bid_msg_id) == 28), 'Bad bid_msg_id length' + bid_id = msg_data.bid_msg_id + + bid, xmr_swap = self.getXmrBid(bid_id) + assert(bid), 'Bid not found: {}.'.format(bid_id.hex()) + assert(xmr_swap), 'XMR swap not found: {}.'.format(bid_id.hex()) + + offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False) + assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex()) + assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex()) + coin_from = Coins(offer.coin_from) + coin_to = Coins(offer.coin_to) + ci_from = self.ci(coin_from) + ci_to = self.ci(coin_to) + + try: + xmr_swap.a_lock_spend_tx = msg_data.a_lock_spend_tx + xmr_swap.a_lock_spend_tx_id = ci_from.getTxHash(xmr_swap.a_lock_spend_tx) + xmr_swap.al_lock_spend_tx_esig = msg_data.al_lock_spend_tx_esig + + ci_from.verifyLockSpendTx( + xmr_swap.a_lock_spend_tx, + xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script, + xmr_swap.dest_af, xmr_offer.a_fee_rate) + + v = ci_from.verifyTxOtVES( + xmr_swap.a_lock_spend_tx, xmr_swap.al_lock_spend_tx_esig, + xmr_swap.pkal, xmr_swap.pkasf, 0, xmr_swap.a_lock_tx_script, bid.amount) + assert(v), 'verifyTxOtVES failed' + + bid.setState(BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX) + self.saveBid(bid_id, bid, xmr_swap=xmr_swap) + except Exception as ex: + if self.debug: + traceback.print_exc() + self.setBidError(bid_id, bid, str(ex)) + + # Update copy of bid in swaps_in_progress + self.swaps_in_progress[bid_id] = (bid, offer) + def processXmrSplitMessage(self, msg): self.log.debug('Processing xmr split msg %s', msg['msgid']) now = int(time.time()) @@ -3157,6 +3581,35 @@ class BasicSwap(BaseApp): dbr.created_at = now self.saveToDB(dbr) + def processXmrSecretMessage(self, msg): + self.log.debug('Processing xmr secret msg %s', msg['msgid']) + now = int(time.time()) + msg_bytes = bytes.fromhex(msg['hex'][2:-2]) + msg_data = XmrBidSecretMessage() + msg_data.ParseFromString(msg_bytes) + + # Validate data + assert(len(msg_data.bid_msg_id) == 28), 'Bad msg_id length' + + bid_id = msg_data.bid_msg_id + bid, xmr_swap = self.getXmrBid(bid_id) + assert(bid), 'Bid not found: {}.'.format(bid_id.hex()) + assert(xmr_swap), 'XMR swap not found: {}.'.format(bid_id.hex()) + + offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False) + assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex()) + assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex()) + + xmr_swap.sv = msg_data.secret_value + + delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept) + self.log.info('Redeeming coin A lock tx for xmr bid %s in %d seconds', bid_id.hex(), delay) + self.createEvent(delay, EventTypes.REDEEM_XMR_SWAP_LOCK_TX_A, bid_id) + + bid.setState(BidStates.XMR_SWAP_SECRET_SHARED) + self.saveBid(bid_id, bid, xmr_swap=xmr_swap) + self.swaps_in_progress[bid_id] = (bid, offer) + def processMsg(self, msg): self.mxDB.acquire() try: @@ -3169,14 +3622,18 @@ class BasicSwap(BaseApp): self.processBid(msg) elif msg_type == MessageTypes.BID_ACCEPT: self.processBidAccept(msg) - elif msg_type == MessageTypes.XMR_BID: + elif msg_type == MessageTypes.XMR_BID_FL: self.processXmrBid(msg) - elif msg_type == MessageTypes.XMR_BID_ACCEPT: + elif msg_type == MessageTypes.XMR_BID_ACCEPT_LF: self.processXmrBidAccept(msg) elif msg_type == MessageTypes.XMR_BID_TXN_SIGS_FL: self.processXmrBidCoinALockSigs(msg) + elif msg_type == MessageTypes.XMR_BID_LOCK_SPEND_TX_LF: + self.processXmrBidLockSpendTx(msg) elif msg_type == MessageTypes.XMR_BID_SPLIT: self.processXmrSplitMessage(msg) + elif msg_type == MessageTypes.XMR_BID_SECRET_LF: + self.processXmrSecretMessage(msg) except Exception as ex: self.log.error('processMsg %s', str(ex)) diff --git a/basicswap/contrib/Keccak.py b/basicswap/contrib/Keccak.py new file mode 100644 index 0000000..1abbcc1 --- /dev/null +++ b/basicswap/contrib/Keccak.py @@ -0,0 +1,348 @@ +#! /usr/bin/pythonw +# The Keccak sponge function, designed by Guido Bertoni, Joan Daemen, +# questions, please refer to our website: http://keccak.noekeon.org/ +# +# Implementation by Renaud Bauvin, +# hereby denoted as "the implementer". +# +# To the extent possible under law, the implementer has waived all copyright +# and related or neighboring rights to the source code in this file. +# http://creativecommons.org/publicdomain/zero/1.0/ + +import math + +class KeccakError(Exception): + """Class of error used in the Keccak implementation + + Use: raise KeccakError.KeccakError("Text to be displayed")""" + + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + + +class Keccak: + """ + Class implementing the Keccak sponge function + """ + def __init__(self, b=1600): + """Constructor: + + b: parameter b, must be 25, 50, 100, 200, 400, 800 or 1600 (default value)""" + self.setB(b) + + def setB(self,b): + """Set the value of the parameter b (and thus w,l and nr) + + b: parameter b, must be choosen among [25, 50, 100, 200, 400, 800, 1600] + """ + + if b not in [25, 50, 100, 200, 400, 800, 1600]: + raise KeccakError.KeccakError('b value not supported - use 25, 50, 100, 200, 400, 800 or 1600') + + # Update all the parameters based on the used value of b + self.b=b + self.w=b//25 + self.l=int(math.log(self.w,2)) + self.nr=12+2*self.l + + # Constants + + ## Round constants + RC=[0x0000000000000001, + 0x0000000000008082, + 0x800000000000808A, + 0x8000000080008000, + 0x000000000000808B, + 0x0000000080000001, + 0x8000000080008081, + 0x8000000000008009, + 0x000000000000008A, + 0x0000000000000088, + 0x0000000080008009, + 0x000000008000000A, + 0x000000008000808B, + 0x800000000000008B, + 0x8000000000008089, + 0x8000000000008003, + 0x8000000000008002, + 0x8000000000000080, + 0x000000000000800A, + 0x800000008000000A, + 0x8000000080008081, + 0x8000000000008080, + 0x0000000080000001, + 0x8000000080008008] + + ## Rotation offsets + r=[[0, 36, 3, 41, 18] , + [1, 44, 10, 45, 2] , + [62, 6, 43, 15, 61] , + [28, 55, 25, 21, 56] , + [27, 20, 39, 8, 14] ] + + ## Generic utility functions + + def rot(self,x,n): + """Bitwise rotation (to the left) of n bits considering the \ + string of bits is w bits long""" + + n = n%self.w + return ((x>>(self.w-n))+(x< Table (and vice-versa) + + def convertStrToTable(self,string): + + + #Check that input paramaters + if self.w%8!= 0: + raise KeccakError("w is not a multiple of 8") + if len(string)!=2*(self.b)//8: + raise KeccakError.KeccakError("string can't be divided in 25 blocks of w bits\ + i.e. string must have exactly b bits") + + #Convert + output=[[0,0,0,0,0], + [0,0,0,0,0], + [0,0,0,0,0], + [0,0,0,0,0], + [0,0,0,0,0]] + for x in range(5): + for y in range(5): + offset=2*((5*y+x)*self.w)//8 + output[x][y]=self.fromHexStringToLane(string[offset:offset+(2*self.w//8)]) + return output + + def convertTableToStr(self,table): + + #Check input format + if self.w%8!= 0: + raise KeccakError.KeccakError("w is not a multiple of 8") + if (len(table)!=5) or (False in [len(row)==5 for row in table]): + raise KeccakError.KeccakError("table must b") + + #Convert + output=['']*25 + for x in range(5): + for y in range(5): + output[5*y+x]=self.fromLaneToHexString(table[x][y]) + output =''.join(output).upper() + return output + + def Round(self,A,RCfixed): + """Perform one round of computation as defined in the Keccak-f permutation + + """ + + #Initialisation of temporary variables + B=[[0,0,0,0,0], + [0,0,0,0,0], + [0,0,0,0,0], + [0,0,0,0,0], + [0,0,0,0,0]] + C= [0,0,0,0,0] + D= [0,0,0,0,0] + + #Theta step + for x in range(5): + C[x] = A[x][0]^A[x][1]^A[x][2]^A[x][3]^A[x][4] + + for x in range(5): + D[x] = C[(x-1)%5]^self.rot(C[(x+1)%5],1) + + for x in range(5): + for y in range(5): + A[x][y] = A[x][y]^D[x] + + #Rho and Pi steps + for x in range(5): + for y in range(5): + B[y][(2*x+3*y)%5] = self.rot(A[x][y], self.r[x][y]) + + #Chi step + for x in range(5): + for y in range(5): + A[x][y] = B[x][y]^((~B[(x+1)%5][y]) & B[(x+2)%5][y]) + + #Iota step + A[0][0] = A[0][0]^RCfixed + + return A + + def KeccakF(self,A, verbose=False): + """Perform Keccak-f function on the state A + + verbose: a boolean flag activating the printing of intermediate computations + """ + + if verbose: + self.printState(A,"Before first round") + + for i in range(self.nr): + #NB: result is truncated to lane size + A = self.Round(A,self.RC[i]%(1<(len(my_string)//2*8): + raise KeccakError.KeccakError("the string is too short to contain the number of bits announced") + + nr_bytes_filled=my_string_length//8 + nbr_bits_filled=my_string_length%8 + l = my_string_length % n + if ((n-8) <= l <= (n-2)): + if (nbr_bits_filled == 0): + my_byte = 0 + else: + my_byte=int(my_string[nr_bytes_filled*2:nr_bytes_filled*2+2],16) + my_byte=(my_byte>>(8-nbr_bits_filled)) + my_byte=my_byte+2**(nbr_bits_filled)+2**7 + my_byte="%02X" % my_byte + my_string=my_string[0:nr_bytes_filled*2]+my_byte + else: + if (nbr_bits_filled == 0): + my_byte = 0 + else: + my_byte=int(my_string[nr_bytes_filled*2:nr_bytes_filled*2+2],16) + my_byte=(my_byte>>(8-nbr_bits_filled)) + my_byte=my_byte+2**(nbr_bits_filled) + my_byte="%02X" % my_byte + my_string=my_string[0:nr_bytes_filled*2]+my_byte + while((8*len(my_string)//2)%n < (n-8)): + my_string=my_string+'00' + my_string = my_string+'80' + + return my_string + + def Keccak(self,M,r=1024,c=512,n=1024,verbose=False): + """Compute the Keccak[r,c,d] sponge function on message M + + M: message pair (length in bits, string of hex characters ('9AFC...') + r: bitrate in bits (defautl: 1024) + c: capacity in bits (default: 576) + n: length of output in bits (default: 1024), + verbose: print the details of computations(default:False) + """ + + #Check the inputs + if (r<0) or (r%8!=0): + raise KeccakError.KeccakError('r must be a multiple of 8 in this implementation') + if (n%8!=0): + raise KeccakError.KeccakError('outputLength must be a multiple of 8') + self.setB(r+c) + + if verbose: + print("Create a Keccak function with (r=%d, c=%d (i.e. w=%d))" % (r,c,(r+c)//25)) + + #Compute lane length (in bits) + w=(r+c)//25 + + # Initialisation of state + S=[[0,0,0,0,0], + [0,0,0,0,0], + [0,0,0,0,0], + [0,0,0,0,0], + [0,0,0,0,0]] + + #Padding of messages + P = self.pad10star1(M, r) + + if verbose: + print("String ready to be absorbed: %s (will be completed by %d x '00')" % (P, c//8)) + + #Absorbing phase + for i in range((len(P)*8//2)//r): + Pi=self.convertStrToTable(P[i*(2*r//8):(i+1)*(2*r//8)]+'00'*(c//8)) + + for y in range(5): + for x in range(5): + S[x][y] = S[x][y]^Pi[x][y] + S = self.KeccakF(S, verbose) + + if verbose: + print("Value after absorption : %s" % (self.convertTableToStr(S))) + + #Squeezing phase + Z = '' + outputLength = n + while outputLength>0: + string=self.convertTableToStr(S) + Z = Z + string[:r*2//8] + outputLength -= r + if outputLength>0: + S = self.KeccakF(S, verbose) + + # NB: done by block of length r, could have to be cut if outputLength + # is not a multiple of r + + if verbose: + print("Value after squeezing : %s" % (self.convertTableToStr(S))) + + return Z[:2*n//8] diff --git a/basicswap/db.py b/basicswap/db.py index 333ff87..c36005b 100644 --- a/basicswap/db.py +++ b/basicswap/db.py @@ -108,6 +108,8 @@ class Bid(Base): initiate_tx = None participate_tx = None xmr_a_lock_tx = None + xmr_b_lock_tx = None + xmr_a_lock_spend_tx = None def getITxState(self): if self.initiate_tx is None: @@ -236,6 +238,7 @@ class XmrSwap(Base): contract_count = sa.Column(sa.Integer) + sv = sa.Column(sa.LargeBinary) # Secret value sh = sa.Column(sa.LargeBinary) # Secret hash dest_af = sa.Column(sa.LargeBinary) # Destination for coin A amount to follower when swap completes successfully @@ -261,6 +264,10 @@ class XmrSwap(Base): kbsl_dleag = sa.Column(sa.LargeBinary) kbsf_dleag = sa.Column(sa.LargeBinary) + vkbv = sa.Column(sa.LargeBinary) + pkbv = sa.Column(sa.LargeBinary) + pkbs = sa.Column(sa.LargeBinary) + a_lock_tx = sa.Column(sa.LargeBinary) a_lock_tx_script = sa.Column(sa.LargeBinary) @@ -275,8 +282,11 @@ class XmrSwap(Base): af_lock_refund_tx_sig = sa.Column(sa.LargeBinary) a_lock_spend_tx = sa.Column(sa.LargeBinary) + a_lock_spend_tx_id = sa.Column(sa.LargeBinary) al_lock_spend_tx_esig = sa.Column(sa.LargeBinary) + b_lock_tx_id = sa.Column(sa.LargeBinary) + b_restore_height = sa.Column(sa.Integer) # Height of xmr chain before the swap diff --git a/basicswap/interface_btc.py b/basicswap/interface_btc.py index e3f62bc..ab45634 100644 --- a/basicswap/interface_btc.py +++ b/basicswap/interface_btc.py @@ -117,6 +117,7 @@ class BTCInterface(CoinInterface): self.rpc_callback = make_rpc_func(coin_settings['rpcport'], coin_settings['rpcauth']) self.txoType = CTxOut self._network = network + self.blocks_confirmed = coin_settings['blocks_confirmed'] def testDaemonRPC(self): self.rpc_callback('getwalletinfo', []) @@ -596,13 +597,14 @@ class BTCInterface(CoinInterface): return True - def verifyLockSpendTx(self, tx, - lock_tx, lock_tx_script, + def verifyLockSpendTx(self, tx_bytes, + lock_tx_bytes, lock_tx_script, a_pkhash_f, feerate): # Verify: # Must have only one input with correct prevout (n is always 0) and sequence # Must have only one output with destination and amount + tx = self.loadTx(tx_bytes) tx_hash = self.getTxHash(tx) logging.info('Verifying lock spend tx: {}.'.format(b2h(tx_hash))) @@ -610,6 +612,7 @@ class BTCInterface(CoinInterface): assert_cond(tx.nLockTime == 0, 'nLockTime not 0') assert_cond(len(tx.vin) == 1, 'tx doesn\'t have one input') + lock_tx = self.loadTx(lock_tx_bytes) lock_tx_id = self.getTxHash(lock_tx) output_script = CScript([OP_0, hashlib.sha256(lock_tx_script).digest()]) @@ -727,21 +730,24 @@ class BTCInterface(CoinInterface): def getTransaction(self, txid): try: - return self.rpc_callback('getrawtransaction', [txid.hex()]) + return bytes.fromhex(self.rpc_callback('getrawtransaction', [txid.hex()])) except Exception as ex: # TODO: filter errors return None - def setTxSignature(self, tx, stack): + def setTxSignature(self, tx_bytes, stack): + tx = self.loadTx(tx_bytes) tx.wit.vtxinwit.clear() tx.wit.vtxinwit.append(CTxInWitness()) tx.wit.vtxinwit[0].scriptWitness.stack = stack - return True + return tx.serialize() - def extractLeaderSig(self, tx): + def extractLeaderSig(self, tx_bytes): + tx = self.loadTx(tx_bytes) return tx.wit.vtxinwit[0].scriptWitness.stack[1] - def extractFollowerSig(self, tx): + def extractFollowerSig(self, tx_bytes): + tx = self.loadTx(tx_bytes) return tx.wit.vtxinwit[0].scriptWitness.stack[2] def createBLockTx(self, Kbs, output_amount): @@ -761,7 +767,8 @@ class BTCInterface(CoinInterface): return self.publishTx(b_lock_tx) def recoverEncKey(self, esig, sig, K): - return otves.RecoverEncKey(esig, sig[:-1], K) # Strip sighash type + return ecdsaotves_rec_enc_key(K, esig, sig[:-1]) # Strip sighash type + #return otves.RecoverEncKey(esig, sig[:-1], K) # Strip sighash type def getTxVSize(self, tx, add_bytes=0, add_witness_bytes=0): wsf = self.witnessScaleFactor() @@ -781,8 +788,8 @@ class BTCInterface(CoinInterface): if utxo['amount'] * COIN != cb_swap_value: logging.warning('Found output to lock tx pubkey of incorrect value: %s', str(utxo['amount'])) else: - return True - return False + return {'txid': utxo['txid'], 'vout': utxo['vout'], 'amount': utxo['amount'], 'height': utxo['height']} + return None def waitForLockTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed): @@ -805,6 +812,31 @@ class BTCInterface(CoinInterface): def spendBLockTx(self, address_to, kbv, kbs, cb_swap_value, b_fee, restore_height): print('TODO: spendBLockTx') + def getOutput(self, txid, dest_script, expect_value): + # TODO: Use getrawtransaction if txindex is active + utxos = self.rpc_callback('scantxoutset', ['start', ['raw({})'.format(dest_script.hex())]]) + print('utxos', utxos) + + chain_height = utxos['height'] + rv = [] + for utxo in utxos['unspents']: + print('utxo', utxo) + depth = 0 if 'height' not in utxo else utxos['height'] - utxo['height'] + + if txid and txid.hex() != utxo['txid']: + continue + + if expect_value != utxo['amount'] * COIN: + continue + + rv.append({ + 'depth': depth, + 'amount': utxo['amount'] * COIN, + 'txid': utxo['txid'], + 'vout': utxo['vout']}) + return rv + + def testBTCInterface(): print('testBTCInterface') diff --git a/basicswap/interface_part.py b/basicswap/interface_part.py index 6663070..9d39abd 100644 --- a/basicswap/interface_part.py +++ b/basicswap/interface_part.py @@ -31,6 +31,7 @@ class PARTInterface(BTCInterface): self.rpc_callback = make_rpc_func(coin_settings['rpcport'], coin_settings['rpcauth']) self.txoType = CTxOutPart self._network = network + self.blocks_confirmed = coin_settings['blocks_confirmed'] def getNewAddress(self, use_segwit): return self.rpc_callback('getnewaddress', ['swap_receive']) diff --git a/basicswap/interface_xmr.py b/basicswap/interface_xmr.py index 9631fa1..05a18cf 100644 --- a/basicswap/interface_xmr.py +++ b/basicswap/interface_xmr.py @@ -10,6 +10,7 @@ import logging import basicswap.contrib.ed25519_fast as edf import basicswap.ed25519_fast_util as edu +import basicswap.util_xmr as xmr_util from coincurve.ed25519 import ed25519_get_pubkey from coincurve.keys import PrivateKey from coincurve.dleag import ( @@ -19,12 +20,13 @@ from coincurve.dleag import ( dleag_prove) from .util import ( + dumpj, format_amount) from .rpc_xmr import ( make_xmr_rpc_func, make_xmr_wallet_rpc_func) from .ecc_util import ( - b2i) + b2i, i2b, b2h) from .chainparams import CoinInterface, Coins XMR_COIN = 10 ** 12 @@ -54,6 +56,10 @@ class XMRInterface(CoinInterface): self.rpc_cb = rpc_cb self.rpc_wallet_cb = rpc_wallet_cb self._network = network + self.blocks_confirmed = coin_settings['blocks_confirmed'] + + def setWalletFilename(self, wallet_filename): + self._wallet_filename = wallet_filename def testDaemonRPC(self): self.rpc_wallet_cb('get_languages') @@ -77,8 +83,13 @@ class XMRInterface(CoinInterface): rv['unconfirmed_balance'] = format_amount(balance_info['balance'] - balance_info['unlocked_balance'], XMRInterface.exp()) return rv + def getMainWalletAddress(self): + self.rpc_wallet_cb('open_wallet', {'filename': self._wallet_filename}) + return self.rpc_wallet_cb('get_address')['address'] + def getNewAddress(self, placeholder): logging.debug('TODO - subaddress?') + self.rpc_wallet_cb('open_wallet', {'filename': self._wallet_filename}) return self.rpc_wallet_cb('get_address')['address'] def isValidKey(self, key_bytes): @@ -123,36 +134,41 @@ class XMRInterface(CoinInterface): return i def sumKeys(self, ka, kb): - return (ka + kb) % edf.l + return i2b((b2i(ka) + b2i(kb)) % edf.l) def sumPubkeys(self, Ka, Kb): - return edf.edwards_add(Ka, Kb) + Ka_d = edf.decodepoint(Ka) + Kb_d = edf.decodepoint(Kb) + return self.encodePubkey(edf.edwards_add(Ka_d, Kb_d)) def publishBLockTx(self, Kbv, Kbs, output_amount, feerate): - shared_addr = xmr_util.encode_address(self.encodePubkey(Kbv), self.encodePubkey(Kbs)) + shared_addr = xmr_util.encode_address(Kbv, Kbs) # TODO: How to set feerate? params = {'destinations': [{'amount': output_amount, 'address': shared_addr}]} rv = self.rpc_wallet_cb('transfer', params) logging.info('publishBLockTx %s to address_b58 %s', rv['tx_hash'], shared_addr) - return rv['tx_hash'] + return bytes.fromhex(rv['tx_hash']) def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height): - Kbv_enc = self.encodePubkey(self.pubkey(kbv)) - address_b58 = xmr_util.encode_address(Kbv_enc, self.encodePubkey(Kbs)) + #Kbv_enc = self.encodePubkey(self.pubkey(kbv)) + Kbv = self.getPubkey(kbv) + address_b58 = xmr_util.encode_address(Kbv, Kbs) try: self.rpc_wallet_cb('close_wallet') except Exception as e: logging.warning('close_wallet failed %s', str(e)) + kbv_le = kbv[::-1] params = { 'restore_height': restore_height, 'filename': address_b58, 'address': address_b58, - 'viewkey': b2h(intToBytes32_le(kbv)), + #'viewkey': b2h(intToBytes32_le(kbv)), + 'viewkey': b2h(kbv_le), } try: @@ -162,6 +178,8 @@ class XMRInterface(CoinInterface): logging.info('generate_from_keys %s', dumpj(rv)) rv = self.rpc_wallet_cb('open_wallet', {'filename': address_b58}) + rv = self.rpc_wallet_cb('refresh') + # Debug try: current_height = self.rpc_cb('get_block_count')['count'] @@ -170,18 +188,15 @@ class XMRInterface(CoinInterface): logging.info('rpc_cb failed %s', str(e)) current_height = None # If the transfer is available it will be deep enough - # For a while after opening the wallet rpc cmds return empty data - for i in range(5): - 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 True - time.sleep(1 + i) + 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': transfer['block_height']} - return False + return None def waitForLockTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height): @@ -244,11 +259,33 @@ class XMRInterface(CoinInterface): return False + def findTxnByHash(self, txid): + self.rpc_wallet_cb('open_wallet', {'filename': self._wallet_filename}) + self.rpc_wallet_cb('refresh') + + try: + current_height = self.rpc_cb('get_block_count')['count'] + logging.info('findTxnByHash XMR current_height %d\nhash: %s', current_height, txid) + 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 + + params = {'transfer_type': 'available'} + rv = self.rpc_wallet_cb('incoming_transfers', params) + if 'transfers' in rv: + for transfer in rv['transfers']: + if transfer['tx_hash'] == txid \ + and (current_height is None or current_height - transfer['block_height'] > self.blocks_confirmed): + return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': transfer['block_height']} + + return None + + def spendBLockTx(self, address_to, kbv, kbs, cb_swap_value, b_fee_rate, restore_height): - Kbv_enc = self.encodePubkey(self.pubkey(kbv)) - Kbs_enc = self.encodePubkey(self.pubkey(kbs)) - address_b58 = xmr_util.encode_address(Kbv_enc, Kbs_enc) + Kbv = self.getPubkey(kbv) + Kbs = self.getPubkey(kbs) + address_b58 = xmr_util.encode_address(Kbv, Kbs) try: self.rpc_wallet_cb('close_wallet') @@ -260,8 +297,8 @@ class XMRInterface(CoinInterface): params = { 'filename': wallet_filename, 'address': address_b58, - 'viewkey': b2h(intToBytes32_le(kbv)), - 'spendkey': b2h(intToBytes32_le(kbs)), + 'viewkey': b2h(kbv[::-1]), + 'spendkey': b2h(kbs[::-1]), 'restore_height': restore_height, } @@ -298,4 +335,4 @@ class XMRInterface(CoinInterface): b_fee += b_fee_rate logging.info('Raising fee to %d', b_fee) - return rv['tx_hash'] + return bytes.fromhex(rv['tx_hash']) diff --git a/basicswap/messages.proto b/basicswap/messages.proto index f720847..d345799 100644 --- a/basicswap/messages.proto +++ b/basicswap/messages.proto @@ -108,4 +108,9 @@ message XmrBidLockSpendTxMessage { bytes al_lock_spend_tx_esig = 3; } +message XmrBidSecretMessage { + /* MSG5F */ + bytes bid_msg_id = 1; + bytes secret_value = 2; +} diff --git a/basicswap/messages_pb2.py b/basicswap/messages_pb2.py index 6d81e84..d2cf8d4 100644 --- a/basicswap/messages_pb2.py +++ b/basicswap/messages_pb2.py @@ -19,7 +19,7 @@ DESCRIPTOR = _descriptor.FileDescriptor( syntax='proto3', serialized_options=None, create_key=_descriptor._internal_create_key, - serialized_pb=b'\n\x0emessages.proto\x12\tbasicswap\"\xd8\x03\n\x0cOfferMessage\x12\x11\n\tcoin_from\x18\x01 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x02 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x05 \x01(\x04\x12\x12\n\ntime_valid\x18\x06 \x01(\x04\x12\x33\n\tlock_type\x18\x07 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\x08 \x01(\r\x12\x11\n\tswap_type\x18\t \x01(\r\x12\x15\n\rproof_address\x18\n \x01(\t\x12\x17\n\x0fproof_signature\x18\x0b \x01(\t\x12\x15\n\rpkhash_seller\x18\x0c \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\r \x01(\x0c\x12\x15\n\rfee_rate_from\x18\x0e \x01(\x04\x12\x13\n\x0b\x66\x65\x65_rate_to\x18\x0f \x01(\x04\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\"\x8c\x01\n\nBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x04 \x01(\x0c\x12\x15\n\rproof_address\x18\x05 \x01(\t\x12\x17\n\x0fproof_signature\x18\x06 \x01(\t\"V\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\"\x99\x01\n\rXmrBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x0c\n\x04pkaf\x18\x04 \x01(\x0c\x12\r\n\x05pkarf\x18\x05 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x06 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x07 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x08 \x01(\x0c\"T\n\x0fXmrSplitMessage\x12\x0e\n\x06msg_id\x18\x01 \x01(\x0c\x12\x10\n\x08msg_type\x18\x02 \x01(\r\x12\x10\n\x08sequence\x18\x03 \x01(\r\x12\r\n\x05\x64leag\x18\x04 \x01(\x0c\"\x9b\x02\n\x13XmrBidAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\n\n\x02sh\x18\x02 \x01(\x0c\x12\x0c\n\x04pkal\x18\x03 \x01(\x0c\x12\r\n\x05pkarl\x18\x04 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x05 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x06 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x07 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x08 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\t \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\n \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\x0b \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\x0c \x01(\x0c\"r\n\x17XmrBidLockTxSigsMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12$\n\x1c\x61\x66_lock_refund_spend_tx_esig\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61\x66_lock_refund_tx_sig\x18\x03 \x01(\x0c\"f\n\x18XmrBidLockSpendTxMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x17\n\x0f\x61_lock_spend_tx\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61l_lock_spend_tx_esig\x18\x03 \x01(\x0c\x62\x06proto3' + serialized_pb=b'\n\x0emessages.proto\x12\tbasicswap\"\xd8\x03\n\x0cOfferMessage\x12\x11\n\tcoin_from\x18\x01 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x02 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x05 \x01(\x04\x12\x12\n\ntime_valid\x18\x06 \x01(\x04\x12\x33\n\tlock_type\x18\x07 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\x08 \x01(\r\x12\x11\n\tswap_type\x18\t \x01(\r\x12\x15\n\rproof_address\x18\n \x01(\t\x12\x17\n\x0fproof_signature\x18\x0b \x01(\t\x12\x15\n\rpkhash_seller\x18\x0c \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\r \x01(\x0c\x12\x15\n\rfee_rate_from\x18\x0e \x01(\x04\x12\x13\n\x0b\x66\x65\x65_rate_to\x18\x0f \x01(\x04\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\"\x8c\x01\n\nBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x04 \x01(\x0c\x12\x15\n\rproof_address\x18\x05 \x01(\t\x12\x17\n\x0fproof_signature\x18\x06 \x01(\t\"V\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\"\x99\x01\n\rXmrBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x0c\n\x04pkaf\x18\x04 \x01(\x0c\x12\r\n\x05pkarf\x18\x05 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x06 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x07 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x08 \x01(\x0c\"T\n\x0fXmrSplitMessage\x12\x0e\n\x06msg_id\x18\x01 \x01(\x0c\x12\x10\n\x08msg_type\x18\x02 \x01(\r\x12\x10\n\x08sequence\x18\x03 \x01(\r\x12\r\n\x05\x64leag\x18\x04 \x01(\x0c\"\x9b\x02\n\x13XmrBidAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\n\n\x02sh\x18\x02 \x01(\x0c\x12\x0c\n\x04pkal\x18\x03 \x01(\x0c\x12\r\n\x05pkarl\x18\x04 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x05 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x06 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x07 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x08 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\t \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\n \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\x0b \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\x0c \x01(\x0c\"r\n\x17XmrBidLockTxSigsMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12$\n\x1c\x61\x66_lock_refund_spend_tx_esig\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61\x66_lock_refund_tx_sig\x18\x03 \x01(\x0c\"f\n\x18XmrBidLockSpendTxMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x17\n\x0f\x61_lock_spend_tx\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61l_lock_spend_tx_esig\x18\x03 \x01(\x0c\"?\n\x13XmrBidSecretMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x14\n\x0csecret_value\x18\x02 \x01(\x0c\x62\x06proto3' ) @@ -643,6 +643,45 @@ _XMRBIDLOCKSPENDTXMESSAGE = _descriptor.Descriptor( serialized_end=1481, ) + +_XMRBIDSECRETMESSAGE = _descriptor.Descriptor( + name='XmrBidSecretMessage', + full_name='basicswap.XmrBidSecretMessage', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='bid_msg_id', full_name='basicswap.XmrBidSecretMessage.bid_msg_id', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='secret_value', full_name='basicswap.XmrBidSecretMessage.secret_value', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1483, + serialized_end=1546, +) + _OFFERMESSAGE.fields_by_name['lock_type'].enum_type = _OFFERMESSAGE_LOCKTYPE _OFFERMESSAGE_LOCKTYPE.containing_type = _OFFERMESSAGE DESCRIPTOR.message_types_by_name['OfferMessage'] = _OFFERMESSAGE @@ -653,6 +692,7 @@ DESCRIPTOR.message_types_by_name['XmrSplitMessage'] = _XMRSPLITMESSAGE DESCRIPTOR.message_types_by_name['XmrBidAcceptMessage'] = _XMRBIDACCEPTMESSAGE DESCRIPTOR.message_types_by_name['XmrBidLockTxSigsMessage'] = _XMRBIDLOCKTXSIGSMESSAGE DESCRIPTOR.message_types_by_name['XmrBidLockSpendTxMessage'] = _XMRBIDLOCKSPENDTXMESSAGE +DESCRIPTOR.message_types_by_name['XmrBidSecretMessage'] = _XMRBIDSECRETMESSAGE _sym_db.RegisterFileDescriptor(DESCRIPTOR) OfferMessage = _reflection.GeneratedProtocolMessageType('OfferMessage', (_message.Message,), { @@ -711,5 +751,12 @@ XmrBidLockSpendTxMessage = _reflection.GeneratedProtocolMessageType('XmrBidLockS }) _sym_db.RegisterMessage(XmrBidLockSpendTxMessage) +XmrBidSecretMessage = _reflection.GeneratedProtocolMessageType('XmrBidSecretMessage', (_message.Message,), { + 'DESCRIPTOR' : _XMRBIDSECRETMESSAGE, + '__module__' : 'messages_pb2' + # @@protoc_insertion_point(class_scope:basicswap.XmrBidSecretMessage) + }) +_sym_db.RegisterMessage(XmrBidSecretMessage) + # @@protoc_insertion_point(module_scope) diff --git a/basicswap/util_xmr.py b/basicswap/util_xmr.py index 75f5185..a3d93d3 100644 --- a/basicswap/util_xmr.py +++ b/basicswap/util_xmr.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -import xmrswap.contrib.Keccak as Keccak +import basicswap.contrib.Keccak as Keccak from .contrib.MoneroPy.base58 import encode as xmr_b58encode diff --git a/tests/basicswap/test_nmc.py b/tests/basicswap/test_nmc.py index bfde986..0686ebd 100644 --- a/tests/basicswap/test_nmc.py +++ b/tests/basicswap/test_nmc.py @@ -271,7 +271,7 @@ class Test(unittest.TestCase): waitForRPC(btcRpc) cls.btc_addr = btcRpc('getnewaddress mining_addr bech32') - logging.info('Mining %d bitcoin blocks to %s', num_blocks, cls.btc_addr) + logging.info('Mining %d Bitcoin blocks to %s', num_blocks, cls.btc_addr) btcRpc('generatetoaddress {} {}'.format(num_blocks, cls.btc_addr)) ro = btcRpc('getblockchaininfo') diff --git a/tests/basicswap/test_reload.py b/tests/basicswap/test_reload.py index 968c989..1e34977 100644 --- a/tests/basicswap/test_reload.py +++ b/tests/basicswap/test_reload.py @@ -175,7 +175,7 @@ class Test(unittest.TestCase): num_blocks = 500 btc_addr = btcRpc(1, 'getnewaddress mining_addr bech32') - logging.info('Mining %d bitcoin blocks to %s', num_blocks, btc_addr) + logging.info('Mining %d Bitcoin blocks to %s', num_blocks, btc_addr) btcRpc(1, 'generatetoaddress {} {}'.format(num_blocks, btc_addr)) for i in range(20): diff --git a/tests/basicswap/test_run.py b/tests/basicswap/test_run.py index 2052c18..bd37abe 100644 --- a/tests/basicswap/test_run.py +++ b/tests/basicswap/test_run.py @@ -271,7 +271,7 @@ class Test(unittest.TestCase): waitForRPC(btcRpc) cls.btc_addr = btcRpc('getnewaddress mining_addr bech32') - logging.info('Mining %d bitcoin blocks to %s', num_blocks, cls.btc_addr) + logging.info('Mining %d Bitcoin blocks to %s', num_blocks, cls.btc_addr) btcRpc('generatetoaddress {} {}'.format(num_blocks, cls.btc_addr)) ro = btcRpc('getblockchaininfo') diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py index f84b90e..81ad416 100644 --- a/tests/basicswap/test_xmr.py +++ b/tests/basicswap/test_xmr.py @@ -67,7 +67,6 @@ from tests.basicswap.common import ( from basicswap.contrib.rpcauth import generate_salt, password_to_hmac from bin.basicswap_run import startDaemon -from pprint import pprint logger = logging.getLogger() @@ -223,6 +222,7 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey): 'walletrpcport': XMR_BASE_WALLET_RPC_PORT + node_id, 'walletrpcuser': 'test' + str(node_id), 'walletrpcpassword': 'test_pass' + str(node_id), + 'walletfile': 'testwallet', 'datadir': os.path.join(datadir, 'xmr_' + str(node_id)), 'bindir': cfg.XMR_BINDIR, }, @@ -303,18 +303,21 @@ def callnoderpc(node_id, method, params=[], wallet=None, base_rpc_port=BASE_RPC_ auth = 'test{0}:test_pass{0}'.format(node_id) return callrpc(base_rpc_port + node_id, auth, method, params, wallet) +def run_coins_loop(cls): + global stop_test + while not stop_test: + if cls.btc_addr is not None: + btcRpc('generatetoaddress 1 {}'.format(cls.btc_addr)) + + if cls.xmr_addr is not None: + callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1}) + time.sleep(1.0) def run_loop(cls): global stop_test while not stop_test: for c in cls.swap_clients: c.update() - - if cls.btc_addr is not None: - btcRpc('generatetoaddress 1 {}'.format(cls.btc_addr)) - - if cls.xmr_addr is not None: - callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1}) time.sleep(1.0) @@ -325,6 +328,7 @@ class Test(unittest.TestCase): super(Test, cls).setUpClass() cls.update_thread = None + cls.coins_update_thread = None cls.http_threads = [] cls.swap_clients = [] cls.part_daemons = [] @@ -425,7 +429,7 @@ class Test(unittest.TestCase): cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address'] num_blocks = 500 - logging.info('Mining %d bitcoin blocks to %s', num_blocks, cls.btc_addr) + logging.info('Mining %d Bitcoin blocks to %s', num_blocks, cls.btc_addr) callnoderpc(0, 'generatetoaddress', [num_blocks, cls.btc_addr], base_rpc_port=BTC_BASE_RPC_PORT) checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=BTC_BASE_RPC_PORT)) @@ -440,6 +444,9 @@ class Test(unittest.TestCase): signal.signal(signal.SIGINT, signal_handler) cls.update_thread = threading.Thread(target=run_loop, args=(cls,)) cls.update_thread.start() + + cls.coins_update_thread = threading.Thread(target=run_coins_loop, args=(cls,)) + cls.coins_update_thread.start() except Exception: traceback.print_exc() Test.tearDownClass() @@ -455,7 +462,11 @@ class Test(unittest.TestCase): cls.update_thread.join() except Exception: logging.info('Failed to join update_thread') - cls.update_thread = None + if cls.coins_update_thread is not None: + try: + cls.coins_update_thread.join() + except Exception: + logging.info('Failed to join coins_update_thread') for t in cls.http_threads: t.stop() @@ -541,7 +552,6 @@ class Test(unittest.TestCase): offers = swap_clients[1].listOffers(filters={'offer_id': offer_id}) assert(len(offers) == 1) offer = offers[0] - pprint(vars(offer)) bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from) @@ -552,13 +562,15 @@ class Test(unittest.TestCase): swap_clients[0].acceptXmrBid(bid_id) - self.wait_for_bid(swap_clients[1], bid_id, BidStates.BID_ACCEPTED, sent=True, wait_for=40) - - - self.wait_for_bid(swap_clients[0], bid_id, BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED, sent=True) - + #self.wait_for_bid(swap_clients[1], bid_id, BidStates.BID_ACCEPTED, sent=True, wait_for=40) + #self.wait_for_bid(swap_clients[0], bid_id, BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED) + #self.wait_for_bid(swap_clients[1], bid_id, BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED, wait_for=40, sent=True) + #self.wait_for_bid(swap_clients[0], bid_id, BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED, wait_for=80) + #self.wait_for_bid(swap_clients[1], bid_id, BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED, sent=True) + 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)