diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 814fad2..00fa6f8 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -25,7 +25,7 @@ from enum import IntEnum, auto from sqlalchemy.orm import sessionmaker, scoped_session from sqlalchemy.orm.session import close_all_sessions -from .interface_part import PARTInterface +from .interface_part import PARTInterface, PARTInterfaceAnon from .interface_btc import BTCInterface from .interface_ltc import LTCInterface from .interface_nmc import NMCInterface @@ -631,6 +631,9 @@ class BasicSwap(BaseApp): 'chain_median_time': None, } + if coin == Coins.PART: + self.coin_clients[Coins.PART_ANON] = self.coin_clients[coin] + if self.coin_clients[coin]['connection_type'] == 'rpc': if coin == Coins.XMR: self.coin_clients[coin]['walletrpchost'] = chain_client_settings.get('walletrpchost', '127.0.0.1') @@ -641,6 +644,8 @@ class BasicSwap(BaseApp): raise ValueError('Missing XMR wallet rpc credentials.') def ci(self, coin): # Coin interface + if coin == Coins.PART_ANON: + return self.coin_clients[Coins.PART]['interface_anon'] return self.coin_clients[coin]['interface'] def createInterface(self, coin): @@ -702,6 +707,8 @@ class BasicSwap(BaseApp): def createCoinInterface(self, coin): if self.coin_clients[coin]['connection_type'] == 'rpc': self.coin_clients[coin]['interface'] = self.createInterface(coin) + if coin == Coins.PART: + self.coin_clients[coin]['interface_anon'] = PARTInterfaceAnon(self.coin_clients[coin], self.chain, self) elif self.coin_clients[coin]['connection_type'] == 'passthrough': self.coin_clients[coin]['interface'] = self.createPassthroughInterface(coin) @@ -1066,13 +1073,15 @@ class BasicSwap(BaseApp): raise ValueError('Invalid swap type for XMR') def validateOfferAmounts(self, coin_from, coin_to, amount, rate, min_bid_amount): + ci_from = self.ci(coin_from) + ci_to = self.ci(coin_to) assert(amount >= min_bid_amount), 'amount < min_bid_amount' - assert(amount > chainparams[coin_from][self.chain]['min_amount']), 'From amount below min value for chain' - assert(amount < chainparams[coin_from][self.chain]['max_amount']), 'From amount above max value for chain' + assert(amount > ci_from.min_amount()), 'From amount below min value for chain' + assert(amount < ci_from.max_amount()), 'From amount above max value for chain' - amount_to = int((amount * rate) // self.ci(coin_from).COIN()) - assert(amount_to > chainparams[coin_to][self.chain]['min_amount']), 'To amount below min value for chain' - assert(amount_to < chainparams[coin_to][self.chain]['max_amount']), 'To amount above max value for chain' + amount_to = int((amount * rate) // ci_from.COIN()) + assert(amount_to > ci_to.min_amount()), 'To amount below min value for chain' + assert(amount_to < ci_to.max_amount()), 'To amount above max value for chain' def validateOfferLockValue(self, coin_from, coin_to, lock_type, lock_value): if lock_type == OfferMessage.SEQUENCE_LOCK_TIME: @@ -1268,7 +1277,7 @@ class BasicSwap(BaseApp): extkey = self.callcoinrpc(Coins.PART, 'extkey', ['info', evkey, key_path])['key_info']['result'] privkey = decodeWif(self.callcoinrpc(Coins.PART, 'extkey', ['info', extkey])['key_info']['privkey']) - if ci.isValidKey(privkey): + if ci.verifyKey(privkey): return privkey nonce += 1 if nonce > 1000: @@ -1501,7 +1510,6 @@ class BasicSwap(BaseApp): def getCachedStealthAddressForCoin(self, coin_type): self.log.debug('getCachedStealthAddressForCoin %s', coin_type) - # TODO: auto refresh after used ci = self.ci(coin_type) key_str = 'stealth_addr_' + ci.coin_name() @@ -1524,6 +1532,37 @@ class BasicSwap(BaseApp): self.mxDB.release() return addr + def getCachedWalletRestoreHeight(self, ci): + self.log.debug('getCachedWalletRestoreHeight %s', ci.coin_name()) + + key_str = 'restore_height_' + ci.coin_name() + self.mxDB.acquire() + try: + session = scoped_session(self.session_factory) + try: + wrh = session.query(DBKVInt).filter_by(key=key_str).first().value + except Exception: + wrh = ci.getWalletRestoreHeight() + self.log.info('Found restore height for %s', ci.coin_name()) + session.add(DBKVInt( + key=key_str, + value=wrh + )) + session.commit() + finally: + session.close() + session.remove() + self.mxDB.release() + return wrh + + def getWalletRestoreHeight(self, ci): + wrh = ci._restore_height + if wrh is not None: + return wrh + found_height = self.getCachedWalletRestoreHeight(ci) + ci.setWalletRestoreHeight(found_height) + return found_height + def getNewContractId(self): self.mxDB.acquire() try: @@ -2017,12 +2056,15 @@ class BasicSwap(BaseApp): xmr_swap.start_chain_a_height = ci_from.getChainHeight() xmr_swap.b_restore_height = ci_to.getChainHeight() - if xmr_swap.b_restore_height < ci_to._restore_height: - xmr_swap.b_restore_height = ci_to._restore_height - self.log.warning('XMR swap restore height clamped to {}'.format(ci_to._restore_height)) + wallet_restore_height = self.getWalletRestoreHeight(ci_to) + if xmr_swap.b_restore_height < wallet_restore_height: + xmr_swap.b_restore_height = wallet_restore_height + self.log.warning('XMR swap restore height clamped to {}'.format(wallet_restore_height)) - kbvf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 1, for_ed25519=True) - kbsf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 2, for_ed25519=True) + + for_ed25519 = True if coin_to == Coins.XMR else False + kbvf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 1, for_ed25519) + kbsf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 2, for_ed25519) kaf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 3) @@ -2032,13 +2074,19 @@ class BasicSwap(BaseApp): xmr_swap.pkaf = ci_from.getPubkey(kaf) - xmr_swap.kbsf_dleag = ci_to.proveDLEAG(kbsf) + if coin_to == Coins.XMR: + xmr_swap.kbsf_dleag = ci_to.proveDLEAG(kbsf) + else: + xmr_swap.kbsf_dleag = xmr_swap.pkbsf 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.kbvf = kbvf - msg_buf.kbsf_dleag = xmr_swap.kbsf_dleag[:16000] + if coin_to == Coins.XMR: + msg_buf.kbsf_dleag = xmr_swap.kbsf_dleag[:16000] + else: + msg_buf.kbsf_dleag = xmr_swap.kbsf_dleag bid_bytes = msg_buf.SerializeToString() payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_FL) + bid_bytes.hex() @@ -2053,27 +2101,28 @@ class BasicSwap(BaseApp): ro = self.callrpc('smsgsend', [bid_addr, offer.addr_from, payload_hex, False, msg_valid, False, options]) xmr_swap.bid_id = bytes.fromhex(ro['msgid']) - msg_buf2 = XmrSplitMessage( - msg_id=xmr_swap.bid_id, - msg_type=XmrSplitMsgTypes.BID, - sequence=2, - dleag=xmr_swap.kbsf_dleag[16000:32000] - ) - msg_bytes = msg_buf2.SerializeToString() - payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_SPLIT) + msg_bytes.hex() - ro = self.callrpc('smsgsend', [bid_addr, offer.addr_from, payload_hex, False, msg_valid, False, options]) - xmr_swap.bid_msg_id2 = bytes.fromhex(ro['msgid']) + if coin_to == Coins.XMR: + msg_buf2 = XmrSplitMessage( + msg_id=xmr_swap.bid_id, + msg_type=XmrSplitMsgTypes.BID, + sequence=2, + dleag=xmr_swap.kbsf_dleag[16000:32000] + ) + msg_bytes = msg_buf2.SerializeToString() + payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_SPLIT) + msg_bytes.hex() + ro = self.callrpc('smsgsend', [bid_addr, offer.addr_from, payload_hex, False, msg_valid, False, options]) + xmr_swap.bid_msg_id2 = bytes.fromhex(ro['msgid']) - msg_buf3 = XmrSplitMessage( - msg_id=xmr_swap.bid_id, - msg_type=XmrSplitMsgTypes.BID, - sequence=3, - dleag=xmr_swap.kbsf_dleag[32000:] - ) - msg_bytes = msg_buf3.SerializeToString() - payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_SPLIT) + msg_bytes.hex() - ro = self.callrpc('smsgsend', [bid_addr, offer.addr_from, payload_hex, False, msg_valid, False, options]) - xmr_swap.bid_msg_id3 = bytes.fromhex(ro['msgid']) + msg_buf3 = XmrSplitMessage( + msg_id=xmr_swap.bid_id, + msg_type=XmrSplitMsgTypes.BID, + sequence=3, + dleag=xmr_swap.kbsf_dleag[32000:] + ) + msg_bytes = msg_buf3.SerializeToString() + payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_SPLIT) + msg_bytes.hex() + ro = self.callrpc('smsgsend', [bid_addr, offer.addr_from, payload_hex, False, msg_valid, False, options]) + xmr_swap.bid_msg_id3 = bytes.fromhex(ro['msgid']) bid = Bid( bid_id=xmr_swap.bid_id, @@ -2129,8 +2178,9 @@ class BasicSwap(BaseApp): if xmr_swap.contract_count is None: xmr_swap.contract_count = self.getNewContractId() - kbvl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 1, for_ed25519=True) - kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 2, for_ed25519=True) + for_ed25519 = True if coin_to == Coins.XMR else False + kbvl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 1, for_ed25519) + kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 2, for_ed25519) kal = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 3) @@ -2144,7 +2194,10 @@ class BasicSwap(BaseApp): xmr_swap.pkal = ci_from.getPubkey(kal) - xmr_swap.kbsl_dleag = ci_to.proveDLEAG(kbsl) + if coin_to == Coins.XMR: + xmr_swap.kbsl_dleag = ci_to.proveDLEAG(kbsl) + else: + xmr_swap.kbsl_dleag = xmr_swap.pkbsl # MSG2F xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script = ci_from.createScriptLockTx( @@ -2180,7 +2233,10 @@ class BasicSwap(BaseApp): msg_buf.bid_msg_id = bid_id msg_buf.pkal = xmr_swap.pkal msg_buf.kbvl = kbvl - msg_buf.kbsl_dleag = xmr_swap.kbsl_dleag[:16000] + if coin_to == Coins.XMR: + msg_buf.kbsl_dleag = xmr_swap.kbsl_dleag[:16000] + else: + msg_buf.kbsl_dleag = xmr_swap.kbsl_dleag # MSG2F msg_buf.a_lock_tx = xmr_swap.a_lock_tx @@ -2199,27 +2255,28 @@ class BasicSwap(BaseApp): bid.accept_msg_id = bytes.fromhex(msg_id) xmr_swap.bid_accept_msg_id = bid.accept_msg_id - msg_buf2 = XmrSplitMessage( - msg_id=bid_id, - msg_type=XmrSplitMsgTypes.BID_ACCEPT, - sequence=2, - dleag=xmr_swap.kbsl_dleag[16000:32000] - ) - msg_bytes = msg_buf2.SerializeToString() - payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_SPLIT) + msg_bytes.hex() - ro = self.callrpc('smsgsend', [offer.addr_from, bid.bid_addr, payload_hex, False, msg_valid, False, options]) - xmr_swap.bid_accept_msg_id2 = bytes.fromhex(ro['msgid']) + if coin_to == Coins.XMR: + msg_buf2 = XmrSplitMessage( + msg_id=bid_id, + msg_type=XmrSplitMsgTypes.BID_ACCEPT, + sequence=2, + dleag=xmr_swap.kbsl_dleag[16000:32000] + ) + msg_bytes = msg_buf2.SerializeToString() + payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_SPLIT) + msg_bytes.hex() + ro = self.callrpc('smsgsend', [offer.addr_from, bid.bid_addr, payload_hex, False, msg_valid, False, options]) + xmr_swap.bid_accept_msg_id2 = bytes.fromhex(ro['msgid']) - msg_buf3 = XmrSplitMessage( - msg_id=bid_id, - msg_type=XmrSplitMsgTypes.BID_ACCEPT, - sequence=3, - dleag=xmr_swap.kbsl_dleag[32000:] - ) - msg_bytes = msg_buf3.SerializeToString() - payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_SPLIT) + msg_bytes.hex() - ro = self.callrpc('smsgsend', [offer.addr_from, bid.bid_addr, payload_hex, False, msg_valid, False, options]) - xmr_swap.bid_accept_msg_id3 = bytes.fromhex(ro['msgid']) + msg_buf3 = XmrSplitMessage( + msg_id=bid_id, + msg_type=XmrSplitMsgTypes.BID_ACCEPT, + sequence=3, + dleag=xmr_swap.kbsl_dleag[32000:] + ) + msg_bytes = msg_buf3.SerializeToString() + payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_SPLIT) + msg_bytes.hex() + ro = self.callrpc('smsgsend', [offer.addr_from, bid.bid_addr, payload_hex, False, msg_valid, False, options]) + xmr_swap.bid_accept_msg_id3 = bytes.fromhex(ro['msgid']) bid.setState(BidStates.BID_ACCEPTED) @@ -3573,7 +3630,8 @@ class BasicSwap(BaseApp): raise ValueError('TODO') elif offer_data.swap_type == SwapTypes.XMR_SWAP: assert(coin_from != Coins.XMR) - assert(coin_to == Coins.XMR) + assert(coin_from != Coins.PART_ANON) + assert(coin_to == Coins.XMR or coin_to == Coins.PART_ANON) self.log.debug('TODO - More restrictions') else: raise ValueError('Unknown swap type {}.'.format(offer_data.swap_type)) @@ -3850,23 +3908,30 @@ class BasicSwap(BaseApp): ci_from = self.ci(Coins(offer.coin_from)) ci_to = self.ci(Coins(offer.coin_to)) - if len(xmr_swap.kbsf_dleag) < ci_to.lengthDLEAG(): - q = session.query(XmrSplitData).filter(sa.and_(XmrSplitData.bid_id == bid.bid_id, XmrSplitData.msg_type == XmrSplitMsgTypes.BID)).order_by(XmrSplitData.msg_sequence.asc()) - for row in q: - xmr_swap.kbsf_dleag += row.dleag + if offer.coin_to == Coins.XMR: + if len(xmr_swap.kbsf_dleag) < ci_to.lengthDLEAG(): + q = session.query(XmrSplitData).filter(sa.and_(XmrSplitData.bid_id == bid.bid_id, XmrSplitData.msg_type == XmrSplitMsgTypes.BID)).order_by(XmrSplitData.msg_sequence.asc()) + for row in q: + xmr_swap.kbsf_dleag += row.dleag + + if not ci_to.verifyDLEAG(xmr_swap.kbsf_dleag): + raise ValueError('Invalid DLEAG proof.') + + # Extract pubkeys from MSG1L DLEAG + xmr_swap.pkasf = xmr_swap.kbsf_dleag[0: 33] + if not ci_from.verifyPubkey(xmr_swap.pkasf): + raise ValueError('Invalid coin a pubkey.') + xmr_swap.pkbsf = xmr_swap.kbsf_dleag[33: 33 + 32] + if not ci_to.verifyPubkey(xmr_swap.pkbsf): + raise ValueError('Invalid coin b pubkey.') + else: + xmr_swap.pkasf = xmr_swap.kbsf_dleag[0: 33] + if not ci_from.verifyPubkey(xmr_swap.pkasf): + raise ValueError('Invalid coin a pubkey.') + xmr_swap.pkbsf = xmr_swap.pkasf if not ci_to.verifyKey(xmr_swap.vkbvf): raise ValueError('Invalid key.') - if not ci_to.verifyDLEAG(xmr_swap.kbsf_dleag): - raise ValueError('Invalid DLEAG proof.') - - # Extract pubkeys from MSG1L DLEAG - xmr_swap.pkasf = xmr_swap.kbsf_dleag[0: 33] - if not ci_from.verifyPubkey(xmr_swap.pkasf): - raise ValueError('Invalid coin a pubkey.') - xmr_swap.pkbsf = xmr_swap.kbsf_dleag[33: 33 + 32] - if not ci_to.verifyPubkey(xmr_swap.pkbsf): - raise ValueError('Invalid coin b pubkey.') if not ci_from.verifyPubkey(xmr_swap.pkaf): raise ValueError('Invalid pubkey.') @@ -3897,26 +3962,32 @@ class BasicSwap(BaseApp): assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.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()) - ci_from = self.ci(Coins(offer.coin_from)) - ci_to = self.ci(Coins(offer.coin_to)) + ci_from = self.ci(offer.coin_from) + ci_to = self.ci(offer.coin_to) - if len(xmr_swap.kbsl_dleag) < ci_to.lengthDLEAG(): - q = session.query(XmrSplitData).filter(sa.and_(XmrSplitData.bid_id == bid.bid_id, XmrSplitData.msg_type == XmrSplitMsgTypes.BID_ACCEPT)).order_by(XmrSplitData.msg_sequence.asc()) - for row in q: - xmr_swap.kbsl_dleag += row.dleag + if offer.coin_to == Coins.XMR: + if len(xmr_swap.kbsl_dleag) < ci_to.lengthDLEAG(): + q = session.query(XmrSplitData).filter(sa.and_(XmrSplitData.bid_id == bid.bid_id, XmrSplitData.msg_type == XmrSplitMsgTypes.BID_ACCEPT)).order_by(XmrSplitData.msg_sequence.asc()) + for row in q: + xmr_swap.kbsl_dleag += row.dleag + if not ci_to.verifyDLEAG(xmr_swap.kbsl_dleag): + raise ValueError('Invalid DLEAG proof.') + + # Extract pubkeys from MSG1F DLEAG + xmr_swap.pkasl = xmr_swap.kbsl_dleag[0: 33] + if not ci_from.verifyPubkey(xmr_swap.pkasl): + raise ValueError('Invalid coin a pubkey.') + xmr_swap.pkbsl = xmr_swap.kbsl_dleag[33: 33 + 32] + if not ci_to.verifyPubkey(xmr_swap.pkbsl): + raise ValueError('Invalid coin b pubkey.') + else: + xmr_swap.pkasl = xmr_swap.kbsl_dleag[0: 33] + if not ci_from.verifyPubkey(xmr_swap.pkasl): + raise ValueError('Invalid coin a pubkey.') + xmr_swap.pkbsl = xmr_swap.pkasl if not ci_to.verifyKey(xmr_swap.vkbvl): raise ValueError('Invalid key.') - if not ci_to.verifyDLEAG(xmr_swap.kbsl_dleag): - raise ValueError('Invalid DLEAG proof.') - - # Extract pubkeys from MSG1F DLEAG - xmr_swap.pkasl = xmr_swap.kbsl_dleag[0: 33] - if not ci_from.verifyPubkey(xmr_swap.pkasl): - raise ValueError('Invalid coin a pubkey.') - xmr_swap.pkbsl = xmr_swap.kbsl_dleag[33: 33 + 32] - 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) @@ -3955,8 +4026,8 @@ class BasicSwap(BaseApp): offer, xmr_offer = self.getXmrOffer(offer_id, sent=True) assert(offer and offer.was_sent), 'Offer not found: {}.'.format(offer_id.hex()) assert(xmr_offer), 'XMR offer not found: {}.'.format(offer_id.hex()) - ci_from = self.ci(Coins(offer.coin_from)) - ci_to = self.ci(Coins(offer.coin_to)) + ci_from = self.ci(offer.coin_from) + ci_to = self.ci(offer.coin_to) assert(offer.state == OfferStates.OFFER_RECEIVED), 'Bad offer state' assert(msg['to'] == offer.addr_from), 'Received on incorrect address' @@ -3995,9 +4066,10 @@ class BasicSwap(BaseApp): b_restore_height=ci_to.getChainHeight(), start_chain_a_height=ci_from.getChainHeight(), ) - if xmr_swap.b_restore_height < ci_to._restore_height: - xmr_swap.b_restore_height = ci_to._restore_height - self.log.warning('XMR swap restore height clamped to {}'.format(ci_to._restore_height)) + wallet_restore_height = self.getWalletRestoreHeight(ci_to) + if xmr_swap.b_restore_height < wallet_restore_height: + xmr_swap.b_restore_height = wallet_restore_height + self.log.warning('XMR swap restore height clamped to {}'.format(wallet_restore_height)) else: assert(bid.state == BidStates.BID_SENT), 'Wrong bid state: {}'.format(str(BidStates(bid.state))) bid.created_at = msg['sent'] @@ -4009,6 +4081,16 @@ class BasicSwap(BaseApp): self.log.info('Receiving xmr bid %s for offer %s', bid_id.hex(), bid_data.offer_msg_id.hex()) self.saveBid(bid_id, bid, xmr_swap=xmr_swap) + if offer.coin_to != Coins.XMR: + with self.mxDB: + try: + session = scoped_session(self.session_factory) + self.receiveXmrBid(bid, session) + session.commit() + finally: + session.close() + session.remove() + def processXmrBidAccept(self, msg): # F receiving MSG1F and MSG2F self.log.debug('Processing xmr bid accept msg %s', msg['msgid']) @@ -4027,8 +4109,8 @@ class BasicSwap(BaseApp): offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=True) assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex()) assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex()) - ci_from = self.ci(Coins(offer.coin_from)) - ci_to = self.ci(Coins(offer.coin_to)) + ci_from = self.ci(offer.coin_from) + ci_to = self.ci(offer.coin_to) try: xmr_swap.pkal = msg_data.pkal @@ -4075,6 +4157,16 @@ class BasicSwap(BaseApp): bid.setState(BidStates.BID_RECEIVING_ACC) self.saveBid(bid.bid_id, bid, xmr_swap=xmr_swap) + + if offer.coin_to != Coins.XMR: + with self.mxDB: + try: + session = scoped_session(self.session_factory) + self.receiveXmrBidAccept(bid, session) + session.commit() + finally: + session.close() + session.remove() except Exception as ex: if self.debug: traceback.print_exc() @@ -4321,7 +4413,8 @@ class BasicSwap(BaseApp): 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_ed25519=True) + for_ed25519 = True if coin_to == Coins.XMR else False + kbsf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 2, for_ed25519) 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) @@ -4374,7 +4467,8 @@ class BasicSwap(BaseApp): kbsf = ci_from.recoverEncKey(xmr_swap.al_lock_spend_tx_esig, xmr_swap.al_lock_spend_tx_sig, xmr_swap.pkasf) assert(kbsf is not None) - kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 2, for_ed25519=True) + for_ed25519 = True if coin_to == Coins.XMR else False + kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 2, for_ed25519) vkbs = ci_to.sumKeys(kbsl, kbsf) address_to = ci_to.getMainWalletAddress() @@ -4429,7 +4523,8 @@ class BasicSwap(BaseApp): kbsl = ci_from.recoverEncKey(xmr_swap.af_lock_refund_spend_tx_esig, af_lock_refund_spend_tx_sig, xmr_swap.pkasl) assert(kbsl is not None) - kbsf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 2, for_ed25519=True) + for_ed25519 = True if coin_to == Coins.XMR else False + kbsf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 2, for_ed25519) vkbs = ci_to.sumKeys(kbsl, kbsf) address_to = ci_to.getMainWalletAddress() @@ -4490,7 +4585,8 @@ class BasicSwap(BaseApp): xmr_swap.af_lock_refund_spend_tx_esig = msg_data.af_lock_refund_spend_tx_esig xmr_swap.af_lock_refund_tx_sig = msg_data.af_lock_refund_tx_sig - kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 2, for_ed25519=True) + for_ed25519 = True if coin_to == Coins.XMR else False + kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 2, for_ed25519) kal = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 3) xmr_swap.af_lock_refund_spend_tx_sig = ci_from.decryptOtVES(kbsl, xmr_swap.af_lock_refund_spend_tx_esig) diff --git a/basicswap/chainparams.py b/basicswap/chainparams.py index 9c33bba..e2365c3 100644 --- a/basicswap/chainparams.py +++ b/basicswap/chainparams.py @@ -203,11 +203,11 @@ chainparams = { class CoinInterface: def __init__(self): - self._unknown_wallet_seed = True self.setDefaults() def setDefaults(self): - pass + self._unknown_wallet_seed = True + self._restore_height = None def make_int(self, amount_in, r=0): return make_int(amount_in, self.exp(), r=r) @@ -222,8 +222,17 @@ class CoinInterface: def ticker(self): return chainparams[self.coin_type()]['ticker'] + def min_amount(self): + return chainparams[self.coin_type()][self._network]['min_amount'] + + def max_amount(self): + return chainparams[self.coin_type()][self._network]['max_amount'] + def setWalletSeedWarning(self, value): self._unknown_wallet_seed = value + def setWalletRestoreHeight(self, value): + self._restore_height = value + def knownWalletSeed(self): return not self._unknown_wallet_seed diff --git a/basicswap/http_server.py b/basicswap/http_server.py index dff3ff9..0f9f0c1 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -58,6 +58,8 @@ env.filters['formatts'] = format_timestamp def getCoinName(c): + if c == Coins.PART_ANON: + return chainparams[Coins.PART]['name'].capitalize() + 'Anon' return chainparams[c]['name'].capitalize() @@ -66,6 +68,9 @@ def listAvailableCoins(swap_client): for k, v in swap_client.coin_clients.items(): if v['connection_type'] == 'rpc': coins.append((int(k), getCoinName(k))) + + if k == Coins.PART: + coins.append((int(Coins.PART_ANON), getCoinName(k))) return coins diff --git a/basicswap/interface_btc.py b/basicswap/interface_btc.py index 0f888a8..76b1722 100644 --- a/basicswap/interface_btc.py +++ b/basicswap/interface_btc.py @@ -200,6 +200,26 @@ class BTCInterface(CoinInterface): def getWalletInfo(self): return self.rpc_callback('getwalletinfo') + def walletRestoreHeight(self): + return self._restore_height + + def getWalletRestoreHeight(self): + start_time = self.rpc_callback('getwalletinfo')['keypoololdest'] + + blockchaininfo = self.rpc_callback('getblockchaininfo') + best_block = blockchaininfo['bestblockhash'] + + chain_synced = round(blockchaininfo['verificationprogress'], 3) + if chain_synced < 1.0: + raise ValueError('{} chain isn\'t synced.'.format(self.chain_name())) + + block_hash = best_block + while True: + block_header = self.rpc_callback('getblockheader', [block_hash]) + if block_header['time'] < start_time: + return block_header['height'] + block_hash = block_header['previousblockhash'] + def getWalletSeedID(self): return self.rpc_callback('getwalletinfo')['hdseedid'] @@ -229,9 +249,6 @@ class BTCInterface(CoinInterface): def getNewSecretKey(self): return getSecretInt() - def pubkey(self, key): - return G * key - def getPubkey(self, privkey): return PublicKey.from_secret(privkey).format() @@ -258,10 +275,11 @@ class BTCInterface(CoinInterface): return i def sumKeys(self, ka, kb): - return (ka + kb) % ep.o + # TODO: Add to coincurve + return i2b((b2i(ka) + b2i(kb)) % ep.o) def sumPubkeys(self, Ka, Kb): - return Ka + Kb + return PublicKey.combine_keys([PublicKey(Ka), PublicKey(Kb)]).format() def getScriptForPubkeyHash(self, pkh): return CScript([OP_0, pkh]) diff --git a/basicswap/interface_part.py b/basicswap/interface_part.py index 82373c1..6a68661 100644 --- a/basicswap/interface_part.py +++ b/basicswap/interface_part.py @@ -97,3 +97,12 @@ class PARTInterfaceAnon(PARTInterface): @staticmethod def balance_type(): return BalanceTypes.ANON + + def publishBLockTx(self, Kbv, Kbs, output_amount, feerate): + raise ValueError('TODO - new core release') + + def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height): + raise ValueError('TODO - new core release') + + def spendBLockTx(self, address_to, kbv, kbs, cb_swap_value, b_fee, restore_height): + raise ValueError('TODO - new core release') diff --git a/basicswap/interface_xmr.py b/basicswap/interface_xmr.py index fc84fc9..56b0f42 100644 --- a/basicswap/interface_xmr.py +++ b/basicswap/interface_xmr.py @@ -134,6 +134,9 @@ class XMRInterface(CoinInterface): rv['unconfirmed_balance'] = format_amount(balance_info['balance'] - balance_info['unlocked_balance'], XMRInterface.exp()) return rv + def walletRestoreHeight(self): + return self._restore_height + def getMainWalletAddress(self): self.rpc_wallet_cb('open_wallet', {'filename': self._wallet_filename}) return self.rpc_wallet_cb('get_address')['address'] @@ -147,10 +150,6 @@ class XMRInterface(CoinInterface): self._log.warning('TODO - estimate fee rate?') return 0.0, 'unused' - def isValidKey(self, key_bytes): - ki = b2i(key_bytes) - return ki < edf.l and ki > 8 - def getNewSecretKey(self): return edu.get_secret() diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py index a694a56..dba5d89 100644 --- a/tests/basicswap/test_xmr.py +++ b/tests/basicswap/test_xmr.py @@ -735,6 +735,31 @@ class Test(unittest.TestCase): if float(js_0['blind_balance']) >= 10.0: raise ValueError('Expect blind balance < 10') + logging.warning('TODO') + return + + amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1) + rate_swap = make_int(random.uniform(2.0, 20.0), scale=8, r=1) + offer_id = swap_clients[0].postOffer(Coins.BTC, Coins.PART_ANON, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP) + wait_for_offer(test_delay_event, 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) + + wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED) + + bid, xmr_swap = swap_clients[0].getXmrBid(bid_id) + assert(xmr_swap) + + swap_clients[0].acceptXmrBid(bid_id) + + wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=180) + wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True) + + js_1 = json.loads(urlopen('http://127.0.0.1:1801/json/wallets/part').read()) + print('[rm] js_1', js_1) + if __name__ == '__main__': unittest.main()