From 009729aa96b433ce4241641cfb878e8945bdda89 Mon Sep 17 00:00:00 2001 From: tecnovert Date: Sun, 15 Nov 2020 00:13:11 +0200 Subject: [PATCH] Send MSG2F --- basicswap/basicswap.py | 682 ++++++++++++++++++++++++++++-- basicswap/contrib/ed25519_fast.py | 356 ++++++++++++++++ basicswap/db.py | 80 +++- basicswap/ed25519_fast_util.py | 36 ++ basicswap/interface_btc.py | 79 +++- basicswap/interface_xmr.py | 48 ++- basicswap/messages.proto | 62 ++- basicswap/messages_pb2.py | 369 ++++++++++------ basicswap/util.py | 1 + tests/basicswap/test_other.py | 20 +- tests/basicswap/test_xmr.py | 52 ++- 11 files changed, 1551 insertions(+), 234 deletions(-) create mode 100644 basicswap/contrib/ed25519_fast.py create mode 100644 basicswap/ed25519_fast_util.py diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index e3e328e..d5ab3b4 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -6,18 +6,20 @@ import os import re -import time -import datetime as dt import zmq -import traceback -import hashlib -import sqlalchemy as sa -import shutil import json +import time +import shutil import random +import logging import secrets -from sqlalchemy.orm import sessionmaker, scoped_session +import hashlib +import datetime as dt +import traceback +import sqlalchemy as sa + from enum import IntEnum, auto +from sqlalchemy.orm import sessionmaker, scoped_session from .interface_part import PARTInterface from .interface_btc import BTCInterface @@ -39,6 +41,7 @@ from .util import ( toWIF, getKeyID, make_int, + dumpj ) from .chainparams import ( chainparams, @@ -51,6 +54,9 @@ from .messages_pb2 import ( OfferMessage, BidMessage, BidAcceptMessage, + XmrBidMessage, + XmrBidAcceptMessage, + XmrSplitMessage, ) from .db import ( CURRENT_DB_VERSION, @@ -64,6 +70,9 @@ from .db import ( SentOffer, SmsgAddress, EventQueue, + XmrOffer, + XmrSwap, + XmrSplitData, ) from .explorers import ExplorerInsight, ExplorerBitAps, ExplorerChainz @@ -83,6 +92,9 @@ class MessageTypes(IntEnum): BID_ACCEPT = auto() XMR_OFFER = auto() + XMR_BID = auto() + XMR_BID_SPLIT = auto() + XMR_BID_ACCEPT = auto() class SwapTypes(IntEnum): @@ -101,7 +113,9 @@ class OfferStates(IntEnum): class BidStates(IntEnum): BID_SENT = auto() + BID_RECEIVING = auto() # Partially received BID_RECEIVED = auto() + BID_RECEIVING_ACC = auto() # Partially received accept message BID_ACCEPTED = auto() # BidAcceptMessage received/sent SWAP_INITIATED = auto() # Initiate txn validated SWAP_PARTICIPATING = auto() # Participate txn validated @@ -132,6 +146,11 @@ class EventTypes(IntEnum): ACCEPT_BID = auto() +class XmrSplitMsgTypes(IntEnum): + BID = auto() + BID_ACCEPT = auto() + + SEQUENCE_LOCK_BLOCKS = 1 SEQUENCE_LOCK_TIME = 2 ABS_LOCK_BLOCKS = 3 @@ -298,10 +317,12 @@ class BasicSwap(BaseApp): self.check_watched_seconds = self.settings.get('check_watched_seconds', 60) self.check_expired_seconds = self.settings.get('check_expired_seconds', 60 * 5) self.check_events_seconds = self.settings.get('check_events_seconds', 10) - self.last_checked_progress = 0 - self.last_checked_watched = 0 - self.last_checked_expired = 0 - self.last_checked_events = 0 + self.check_xmr_swaps_seconds = self.settings.get('check_xmr_swaps_seconds', 20) + self._last_checked_progress = 0 + self._last_checked_watched = 0 + self._last_checked_expired = 0 + self._last_checked_events = 0 + self._last_checked_xmr_swaps = 0 # TODO: Adjust ranges self.min_delay_auto_accept = self.settings.get('min_delay_auto_accept', 10) @@ -321,7 +342,7 @@ class BasicSwap(BaseApp): self.network_key = toWIF(wif_prefix, decodeWif(self.settings['network_key'])) self.network_pubkey = self.settings['network_pubkey'] - self.network_addr = pubkeyToAddress(chainparams[Coins.PART][self.chain]['pubkey_address'], bytearray.fromhex(self.network_pubkey)) + self.network_addr = pubkeyToAddress(chainparams[Coins.PART][self.chain]['pubkey_address'], bytes.fromhex(self.network_pubkey)) #self.wallet = self.settings.get('wallet', None) # TODO: Move to coin_clients self.sqlite_file = os.path.join(self.data_dir, 'db{}.sqlite'.format('' if self.chain == 'mainnet' else ('_' + self.chain))) @@ -351,15 +372,6 @@ class BasicSwap(BaseApp): value=self._contract_count )) session.commit() - try: - self._offer_count = session.query(DBKVInt).filter_by(key='offer_count').first().value - except Exception: - self._offer_count = 0 - session.add(DBKVInt( - key='offer_count', - value=self._offer_count - )) - session.commit() session.close() session.remove() @@ -450,6 +462,9 @@ class BasicSwap(BaseApp): raise ValueError('Missing XMR wallet rpc credentials.') self.coin_clients[coin]['interface'] = self.createInterface(coin) + def ci(self, coin): # coin interface + return self.coin_clients[coin]['interface'] + def createInterface(self, coin): if coin == Coins.PART: return PARTInterface(self.coin_clients[coin]) @@ -741,13 +756,8 @@ class BasicSwap(BaseApp): offer_addr = self.callrpc('getnewaddress') if addr_send_from is None else addr_send_from offer_created_at = int(time.time()) - if swap_type == SwapTypes.XMR_SWAP: - msg_buf = XmrOfferMessage() - key_path = "44445555h/999999/{}/{}/{}/{}".format(int(coin_from), int(coin_to), offer_created_at, ) - - else: - msg_buf = OfferMessage() + msg_buf = OfferMessage() msg_buf.coin_from = int(coin_from) msg_buf.coin_to = int(coin_to) @@ -760,6 +770,18 @@ class BasicSwap(BaseApp): msg_buf.lock_value = lock_value msg_buf.swap_type = swap_type + if swap_type == SwapTypes.XMR_SWAP: + xmr_offer = XmrOffer() + + xmr_offer.lock_time_1 = lock_value # Delay before the chain a lock refund tx can be mined + xmr_offer.lock_time_2 = lock_value # Delay before the follower can spend from the chain a lock refund tx + + # TODO: Dynamic fee selection + xmr_offer.a_fee_rate = make_int(0.00032595, self.ci(coin_from).exp()) + xmr_offer.b_fee_rate = make_int(0.0012595, self.ci(coin_to).exp()) + msg_buf.fee_rate_from = xmr_offer.a_fee_rate + msg_buf.fee_rate_to = xmr_offer.b_fee_rate + offer_bytes = msg_buf.SerializeToString() payload_hex = str.format('{:02x}', MessageTypes.OFFER) + offer_bytes.hex() @@ -792,6 +814,10 @@ class BasicSwap(BaseApp): auto_accept_bids=auto_accept_bids,) offer.setState(OfferStates.OFFER_SENT) + if swap_type == SwapTypes.XMR_SWAP: + xmr_offer.offer_id = offer_id + session.add(xmr_offer) + session.add(offer) session.add(SentOffer(offer_id=offer_id)) if addr_send_from is None: @@ -804,6 +830,31 @@ class BasicSwap(BaseApp): self.log.info('Sent OFFER %s', offer_id.hex()) return offer_id + def getPathKey(self, coin_from, coin_to, offer_created_at, contract_count, key_no, for_xmr=False): + account = self.callcoinrpc(Coins.PART, 'extkey', ['account']) + evkey = self.callcoinrpc(Coins.PART, 'extkey', ['account', 'default', 'true'])['evkey'] + ci = self.ci(coin_to) + + days = offer_created_at // 86400 + secs = offer_created_at - days * 86400 + key_path_base = '44445555h/999999/{}/{}/{}/{}/{}/{}'.format(int(coin_from), int(coin_to), days, secs, contract_count, key_no) + + if not for_xmr: + extkey = self.callcoinrpc(Coins.PART, 'extkey', ['info', evkey, key_path_base])['key_info']['result'] + return decodeWif(self.callcoinrpc(Coins.PART, 'extkey', ['info', extkey])['key_info']['privkey']) + + nonce = 1 + while True: + key_path = key_path_base + '/{}'.format(nonce) + 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): + return privkey + nonce += 1 + if nonce > 1000: + raise ValueError('getKeyForXMR failed') + def getContractPubkey(self, date, contract_count): account = self.callcoinrpc(Coins.PART, 'extkey', ['account']) @@ -1005,18 +1056,31 @@ class BasicSwap(BaseApp): return (sign_for_addr, signature) - def saveBidInSession(self, bid_id, bid, session): + def saveBidInSession(self, bid_id, bid, session, xmr_swap=None): session.add(bid) if bid.initiate_tx: session.add(bid.initiate_tx) if bid.participate_tx: session.add(bid.participate_tx) + if xmr_swap is not None: + session.add(xmr_swap) - def saveBid(self, bid_id, bid): + def saveBid(self, bid_id, bid, xmr_swap=None): self.mxDB.acquire() try: session = scoped_session(self.session_factory) - self.saveBidInSession(bid_id, bid, session) + self.saveBidInSession(bid_id, bid, session, xmr_swap) + session.commit() + session.close() + session.remove() + finally: + self.mxDB.release() + + def saveToDB(self, db_record): + self.mxDB.acquire() + try: + session = scoped_session(self.session_factory) + session.add(db_record) session.commit() session.close() session.remove() @@ -1070,6 +1134,8 @@ class BasicSwap(BaseApp): proof_addr, proof_sig = self.getProofOfFunds(coin_to, msg_buf.amount) msg_buf.proof_address = proof_addr msg_buf.proof_signature = proof_sig + else: + raise ValueError('TODO') bid_bytes = msg_buf.SerializeToString() payload_hex = str.format('{:02x}', MessageTypes.BID) + bid_bytes.hex() @@ -1124,6 +1190,34 @@ class BasicSwap(BaseApp): session.remove() self.mxDB.release() + def getXmrBid(self, bid_id, sent=False): + self.mxDB.acquire() + try: + session = scoped_session(self.session_factory) + bid = session.query(Bid).filter_by(bid_id=bid_id).first() + xmr_swap = None + if bid: + xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid_id).first() + return bid, xmr_swap + finally: + session.close() + session.remove() + self.mxDB.release() + + def getXmrOffer(self, offer_id, sent=False): + self.mxDB.acquire() + try: + session = scoped_session(self.session_factory) + offer = session.query(Offer).filter_by(offer_id=offer_id).first() + xmr_offer = None + if offer: + xmr_offer = session.query(XmrOffer).filter_by(offer_id=offer_id).first() + return offer, xmr_offer + finally: + session.close() + session.remove() + self.mxDB.release() + def getBid(self, bid_id): self.mxDB.acquire() try: @@ -1244,6 +1338,254 @@ class BasicSwap(BaseApp): self.saveBid(bid_id, bid) self.swaps_in_progress[bid_id] = (bid, offer) + def postXmrBid(self, offer_id, amount, addr_send_from=None): + # Bid to send bid.amount * offer.rate of coin_to in exchange for bid.amount of coin_from + # Send MSG1L F -> L + self.log.debug('postBid %s %s', offer_id.hex(), format8(amount)) + + self.mxDB.acquire() + try: + offer, xmr_offer = self.getXmrOffer(offer_id) + + assert(offer), 'Offer not found: {}.'.format(offer_id.hex()) + assert(xmr_offer), 'XMR offer not found: {}.'.format(offer_id.hex()) + assert(offer.expire_at > int(time.time())), 'Offer has expired' + + msg_buf = XmrBidMessage() + msg_buf.offer_msg_id = offer_id + msg_buf.time_valid = 60 * 10 + msg_buf.amount = int(amount) # amount of coin_from + + coin_from = Coins(offer.coin_from) + coin_to = Coins(offer.coin_to) + ci_from = self.ci(coin_from) + ci_to = self.ci(coin_to) + + self.checkSynced(coin_from, coin_to) + + bid_created_at = int(time.time()) + if offer.swap_type != SwapTypes.XMR_SWAP: + raise ValueError('TODO') + + # Follower to leader + xmr_swap = XmrSwap() + xmr_swap.contract_count = self.getNewContractId() + xmr_swap.b_restore_height = self.ci(coin_to).getChainHeight() + kbvf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 1, for_xmr=True) + 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) + karf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 4) + + xmr_swap.pkaf = ci_from.getPubkey(kaf) + xmr_swap.pkarf = ci_from.getPubkey(karf) + + xmr_swap.kbsf_dleag = ci_to.proveDLEAG(kbsf) + + msg_buf.pkaf = xmr_swap.pkaf + msg_buf.pkarf = xmr_swap.pkarf + msg_buf.kbvf = kbvf + 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() + + if addr_send_from is None: + bid_addr = self.callrpc('getnewaddress') + else: + bid_addr = addr_send_from + self.callrpc('smsgaddlocaladdress', [bid_addr]) # Enable receiving smsg + options = {'decodehex': True, 'ttl_is_seconds': True} + msg_valid = self.SMSG_SECONDS_IN_HOUR * 1 + 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']) + + 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, + offer_id=offer_id, + amount=msg_buf.amount, + #pkhash_buyer=msg_buf.pkhash_buyer, + #proof_address=msg_buf.proof_address, + + created_at=bid_created_at, + contract_count=xmr_swap.contract_count, + amount_to=(msg_buf.amount * offer.rate) // COIN, + expire_at=bid_created_at + msg_buf.time_valid, + bid_addr=bid_addr, + was_sent=True, + ) + bid.setState(BidStates.BID_SENT) + + session = scoped_session(self.session_factory) + self.saveBidInSession(xmr_swap.bid_id, bid, session, xmr_swap) + if addr_send_from is None: + session.add(SmsgAddress(addr=bid_addr, use_type=MessageTypes.BID)) + session.commit() + session.close() + session.remove() + + self.log.info('Sent XMR_BID %s', xmr_swap.bid_id.hex()) + return xmr_swap.bid_id + finally: + self.mxDB.release() + + def acceptXmrBid(self, bid_id): + # MSG1F and MSG2F L -> F + self.log.info('Accepting xmr bid %s', bid_id.hex()) + + now = int(time.time()) + self.mxDB.acquire() + try: + 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()) + assert(bid.expire_at > now), 'Offer has expired' + + offer, xmr_offer = self.getXmrOffer(bid.offer_id) + assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex()) + assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex()) + assert(offer.expire_at > now), 'Offer has expired' + + coin_from = Coins(offer.coin_from) + coin_to = Coins(offer.coin_to) + 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() + + contract_secret = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 1) + + kbvl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 2, for_xmr=True) + kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 3, for_xmr=True) + + 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.sh = hashlib.sha256(contract_secret).digest() + xmr_swap.pkal = ci_from.getPubkey(kal) + xmr_swap.pkarl = ci_from.getPubkey(karl) + + xmr_swap.kbsl_dleag = ci_to.proveDLEAG(kbsl) + + # MSG2F + xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script = ci_from.createScriptLockTx( + bid.amount, + xmr_swap.sh, + xmr_swap.pkal, xmr_swap.pkaf, + xmr_offer.lock_time_1, + xmr_swap.pkarl, xmr_swap.pkarf, + ) + 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) + + 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, + xmr_swap.pkarl, xmr_swap.pkarf, + xmr_offer.lock_time_2, + xmr_swap.pkaf, + xmr_offer.a_fee_rate + ) + + xmr_swap.al_lock_refund_tx_sig = ci_from.signTx(karl, xmr_swap.a_lock_refund_tx, 0, xmr_swap.a_lock_tx_script, bid.amount) + + v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_tx, xmr_swap.al_lock_refund_tx_sig, xmr_swap.pkarl, 0, xmr_swap.a_lock_tx_script, bid.amount) + assert(v) + + xmr_swap.a_lock_refund_spend_tx = ci_from.createScriptLockRefundSpendTx( + xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script, + xmr_swap.pkal, + xmr_offer.a_fee_rate + ) + + msg_buf = XmrBidAcceptMessage() + msg_buf.bid_msg_id = bid_id + msg_buf.sh = xmr_swap.sh + msg_buf.pkal = xmr_swap.pkal + msg_buf.pkarl = xmr_swap.pkarl + msg_buf.kbvl = kbvl + msg_buf.kbsl_dleag = xmr_swap.kbsl_dleag[:16000] + + # MSG2F + msg_buf.a_lock_tx = xmr_swap.a_lock_tx + msg_buf.a_lock_tx_script = xmr_swap.a_lock_tx_script + msg_buf.a_lock_refund_tx = xmr_swap.a_lock_refund_tx + msg_buf.a_lock_refund_tx_script = xmr_swap.a_lock_refund_tx_script + msg_buf.a_lock_refund_spend_tx = xmr_swap.a_lock_refund_spend_tx + 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() + 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]) + msg_id = ro['msgid'] + 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']) + + 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) + + session = scoped_session(self.session_factory) + self.saveBidInSession(bid_id, bid, session, xmr_swap) + session.commit() + session.close() + session.remove() + + self.swaps_in_progress[bid_id] = (bid, offer) + self.log.info('Sent XMR_BID_ACCEPT %s', bid_id.hex()) + return bid_id + finally: + self.mxDB.release() + def abandonOffer(self, offer_id): self.log.info('Abandoning Offer %s', offer_id.hex()) self.mxDB.acquire() @@ -1282,10 +1624,10 @@ class BasicSwap(BaseApp): session.remove() self.mxDB.release() - def setBidError(self, bif_id, bid, error_str): + def setBidError(self, bid_id, bid, error_str): bid.setState(BidStates.BID_ERROR) bid.state_note = 'error msg: ' + error_str - self.saveBid(bif_id, bid) + self.saveBid(bid_id, bid) def createInitiateTxn(self, coin_type, bid_id, bid, initiate_script): if self.coin_clients[coin_type]['connection_type'] != 'rpc': @@ -1771,6 +2113,9 @@ class BasicSwap(BaseApp): return sum_unspent return None + def checkXmrBidState(self, bid_id, bid, offer): + pass + def checkBidState(self, bid_id, bid, offer): # assert(self.mxDB.locked()) # Return True to remove bid from in-progress list @@ -1778,6 +2123,9 @@ class BasicSwap(BaseApp): state = BidStates(bid.state) self.log.debug('checkBidState %s %s', bid_id.hex(), str(state)) + if offer.swap_type == SwapTypes.XMR_SWAP: + return self.checkXmrBidState(bid_id, bid, offer) + save_bid = False coin_from = Coins(offer.coin_from) coin_to = Coins(offer.coin_to) @@ -2072,21 +2420,19 @@ class BasicSwap(BaseApp): options = {'encoding': 'none', 'delete': True} del_msg = self.callrpc('smsg', [msg['msgid'], options]) - # TODO: remove offers from db + logging.debug('TODO: Expire records from db') finally: self.mxDB.release() def checkEvents(self): self.mxDB.acquire() + now = int(time.time()) try: - now = int(time.time()) - session = scoped_session(self.session_factory) q = session.query(EventQueue).filter(EventQueue.trigger_at >= now) for row in q: - if row.event_type == EventTypes.ACCEPT_BID: self.acceptBid(row.linked_id) else: @@ -2100,6 +2446,55 @@ class BasicSwap(BaseApp): finally: self.mxDB.release() + def checkXmrSwaps(self): + self.mxDB.acquire() + now = int(time.time()) + ttl_xmr_split_messages = 60 * 60 + try: + session = scoped_session(self.session_factory) + q = session.query(Bid).filter(Bid.state == BidStates.BID_RECEIVING) + for bid in q: + q = self.engine.execute('SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = x\'{}\' AND msg_type = {}'.format(bid.bid_id.hex(), XmrSplitMsgTypes.BID)).first() + num_segments = q[0] + if num_segments > 1: + try: + self.receiveXmrBid(bid, session) + except Exception as e: + self.log.info('Verify xmr bid {} failed: {}'.format(bid.bid_id.hex(), str(e))) + bid.setState(BidStates.BID_ERROR, 'Failed validation: ' + str(e)) + session.add(bid) + continue + if bid.created_at + ttl_xmr_split_messages < now: + self.log.debug('Expiring partially received bid: {}'.format(bid.bid_id.hex())) + bid.setState(BidStates.BID_ERROR, 'Timed out') + session.add(bid) + + q = session.query(Bid).filter(Bid.state == BidStates.BID_RECEIVING_ACC) + for bid in q: + q = self.engine.execute('SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = x\'{}\' AND msg_type = {}'.format(bid.bid_id.hex(), XmrSplitMsgTypes.BID_ACCEPT)).first() + num_segments = q[0] + if num_segments > 1: + try: + self.receiveXmrBidAccept(bid, session) + except Exception as e: + self.log.info('Verify xmr bid accept {} failed: {}'.format(bid.bid_id.hex(), str(e))) + bid.setState(BidStates.BID_ERROR, 'Failed accept validation: ' + str(e)) + session.add(bid) + continue + if bid.created_at + ttl_xmr_split_messages < now: + self.log.debug('Expiring partially received bid accept: {}'.format(bid.bid_id.hex())) + bid.setState(BidStates.BID_ERROR, 'Timed out') + session.add(bid) + + # Expire old records + q = session.query(XmrSplitData).filter(XmrSplitData.created_at + ttl_xmr_split_messages < now) + q.delete(synchronize_session=False) + session.commit() + session.close() + session.remove() + finally: + self.mxDB.release() + def processOffer(self, msg): assert(msg['to'] == self.network_addr), 'Offer received on wrong address' @@ -2159,6 +2554,19 @@ class BasicSwap(BaseApp): was_sent=False) offer.setState(OfferStates.OFFER_RECEIVED) session.add(offer) + + if offer.swap_type == SwapTypes.XMR_SWAP: + xmr_offer = XmrOffer() + + xmr_offer.offer_id = offer_id + xmr_offer.lock_time_1 = offer_data.lock_value + xmr_offer.lock_time_2 = offer_data.lock_value + + xmr_offer.a_fee_rate = offer_data.fee_rate_from + xmr_offer.b_fee_rate = offer_data.fee_rate_to + + session.add(xmr_offer) + self.log.debug('Received new offer %s', offer_id.hex()) else: existing_offer.setState(OfferStates.OFFER_RECEIVED) @@ -2326,6 +2734,179 @@ class BasicSwap(BaseApp): self.saveBid(bid_id, bid) self.swaps_in_progress[bid_id] = (bid, offer) + def receiveXmrBid(self, bid, session): + self.log.debug('Receiving xmr bid %s', bid.bid_id.hex()) + now = int(time.time()) + + offer, xmr_offer = self.getXmrOffer(bid.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()) + coin_from = Coins(offer.coin_from) + coin_to = Coins(offer.coin_to) + + 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(coin_from) + ci_to = self.ci(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 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.') + + if not ci_from.verifyPubkey(xmr_swap.pkaf): + raise ValueError('Invalid pubkey.') + if not ci_from.verifyPubkey(xmr_swap.pkarf): + raise ValueError('Invalid pubkey.') + + bid.setState(BidStates.BID_RECEIVED) + self.saveBidInSession(bid.bid_id, bid, session, xmr_swap) + + def receiveXmrBidAccept(self, bid, session): + self.log.debug('Receiving xmr bid accept %s', bid.bid_id.hex()) + now = int(time.time()) + + 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()) + coin_from = Coins(offer.coin_from) + coin_to = Coins(offer.coin_to) + + 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(coin_from) + ci_to = self.ci(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 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.') + + if not ci_from.verifyPubkey(xmr_swap.pkal): + raise ValueError('Invalid pubkey.') + if not ci_from.verifyPubkey(xmr_swap.pkarl): + raise ValueError('Invalid pubkey.') + + bid.setState(BidStates.BID_ACCEPTED) + self.saveBidInSession(bid.bid_id, bid, session, xmr_swap) + + def processXmrBid(self, msg): + self.log.debug('Processing xmr bid msg %s', msg['msgid']) + now = int(time.time()) + bid_bytes = bytes.fromhex(msg['hex'][2:-2]) + bid_data = XmrBidMessage() + bid_data.ParseFromString(bid_bytes) + + # Validate data + assert(len(bid_data.offer_msg_id) == 28), 'Bad offer_id length' + assert(bid_data.time_valid >= MIN_BID_VALID_TIME and bid_data.time_valid <= MAX_BID_VALID_TIME), 'Invalid time_valid' + + offer_id = bid_data.offer_msg_id + 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()) + coin_from = Coins(offer.coin_from) + coin_to = Coins(offer.coin_to) + + logging.debug('TODO: xmr bid validation') + + bid_id = bytes.fromhex(msg['msgid']) + + bid, xmr_swap = self.getXmrBid(bid_id) + if bid is None: + bid = Bid( + bid_id=bid_id, + offer_id=offer_id, + amount=bid_data.amount, + #pkhash_buyer=bid_data.pkhash_buyer, + + created_at=msg['sent'], + amount_to=(bid_data.amount * offer.rate) // COIN, + expire_at=msg['sent'] + bid_data.time_valid, + bid_addr=msg['from'], + was_received=True, + ) + + xmr_swap = XmrSwap( + bid_id=bid_id, + pkaf=bid_data.pkaf, + pkarf=bid_data.pkarf, + vkbvf=bid_data.kbvf, + kbsf_dleag=bid_data.kbsf_dleag, + b_restore_height=self.ci(coin_to).getChainHeight(), + ) + else: + bid.created_at = msg['sent'] + bid.expire_at = msg['sent'] + bid_data.time_valid + bid.was_received = True + + bid.setState(BidStates.BID_RECEIVING) + + 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) + + def processXmrBidAccept(self, msg): + # F receiving MSG1F + self.log.debug('Processing xmr bid accept msg %s', msg['msgid']) + now = int(time.time()) + msg_bytes = bytes.fromhex(msg['hex'][2:-2]) + msg_data = XmrBidAcceptMessage() + msg_data.ParseFromString(msg_bytes) + + assert(len(msg_data.bid_msg_id) == 28), 'Bad bid_msg_id length' + + self.log.debug('for bid %s', msg_data.bid_msg_id.hex()) + bid, xmr_swap = self.getXmrBid(msg_data.bid_msg_id) + + 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()) + coin_from = Coins(offer.coin_from) + coin_to = Coins(offer.coin_to) + + assert(len(msg_data.sh) == 32), 'Bad secret hash length' + + xmr_swap.sh = msg_data.sh + xmr_swap.pkal = msg_data.pkal + xmr_swap.pkarl = msg_data.pkarl + xmr_swap.vkbvl = msg_data.kbvl + xmr_swap.kbsl_dleag = msg_data.kbsl_dleag + + bid.setState(BidStates.BID_RECEIVING_ACC) + self.saveBid(msg_data.bid_msg_id, bid, xmr_swap=xmr_swap) + + def processXmrSplitMessage(self, msg): + self.log.debug('Processing xmr split msg %s', msg['msgid']) + now = int(time.time()) + msg_bytes = bytes.fromhex(msg['hex'][2:-2]) + msg_data = XmrSplitMessage() + msg_data.ParseFromString(msg_bytes) + + # Validate data + print('[rm] msg_data.msg_id', msg_data.msg_id.hex()) + assert(len(msg_data.msg_id) == 28), 'Bad msg_id length' + + if msg_data.msg_type == XmrSplitMsgTypes.BID or msg_data.msg_type == XmrSplitMsgTypes.BID_ACCEPT: + dbr = XmrSplitData() + dbr.bid_id = msg_data.msg_id + dbr.msg_type = msg_data.msg_type + dbr.msg_sequence = msg_data.sequence + dbr.dleag = msg_data.dleag + dbr.created_at = now + self.saveToDB(dbr) + def processMsg(self, msg): self.mxDB.acquire() try: @@ -2338,6 +2919,12 @@ class BasicSwap(BaseApp): self.processBid(msg) elif msg_type == MessageTypes.BID_ACCEPT: self.processBidAccept(msg) + elif msg_type == MessageTypes.XMR_BID: + self.processXmrBid(msg) + elif msg_type == MessageTypes.XMR_BID_ACCEPT: + self.processXmrBidAccept(msg) + elif msg_type == MessageTypes.XMR_BID_SPLIT: + self.processXmrSplitMessage(msg) except Exception as ex: self.log.error('processMsg %s', str(ex)) @@ -2373,7 +2960,7 @@ class BasicSwap(BaseApp): try: # TODO: Wait for blocks / txns, would need to check multiple coins now = int(time.time()) - if now - self.last_checked_progress >= self.check_progress_seconds: + if now - self._last_checked_progress >= self.check_progress_seconds: to_remove = [] for bid_id, v in self.swaps_in_progress.items(): try: @@ -2387,21 +2974,25 @@ class BasicSwap(BaseApp): for bid_id in to_remove: self.log.debug('Removing bid from in-progress: %s', bid_id.hex()) del self.swaps_in_progress[bid_id] - self.last_checked_progress = now + self._last_checked_progress = now - if now - self.last_checked_watched >= self.check_watched_seconds: + if now - self._last_checked_watched >= self.check_watched_seconds: for k, c in self.coin_clients.items(): if len(c['watched_outputs']) > 0: self.checkForSpends(k, c) - self.last_checked_watched = now + self._last_checked_watched = now - if now - self.last_checked_expired >= self.check_expired_seconds: + if now - self._last_checked_expired >= self.check_expired_seconds: self.expireMessages() - self.last_checked_expired = now + self._last_checked_expired = now - if now - self.last_checked_events >= self.check_events_seconds: + if now - self._last_checked_events >= self.check_events_seconds: self.checkEvents() - self.last_checked_events = now + self._last_checked_events = now + + if now - self._last_checked_xmr_swaps >= self.check_xmr_swaps_seconds: + self.checkXmrSwaps() + self._last_checked_xmr_swaps = now except Exception as ex: self.log.error('update %s', str(ex)) @@ -2542,6 +3133,9 @@ class BasicSwap(BaseApp): else: q = session.query(Offer).filter(Offer.expire_at > now) + filter_offer_id = filters.get('offer_id', None) + if filter_offer_id is not None: + q = q.filter(Offer.offer_id == filter_offer_id) filter_coin_from = filters.get('coin_from', None) if filter_coin_from and filter_coin_from > -1: q = q.filter(Offer.coin_from == int(filter_coin_from)) diff --git a/basicswap/contrib/ed25519_fast.py b/basicswap/contrib/ed25519_fast.py new file mode 100644 index 0000000..6e94238 --- /dev/null +++ b/basicswap/contrib/ed25519_fast.py @@ -0,0 +1,356 @@ +# ed25519.py - Optimized version of the reference implementation of Ed25519 +# +# Written in 2011? by Daniel J. Bernstein +# 2013 by Donald Stufft +# 2013 by Alex Gaynor +# 2013 by Greg Price +# +# To the extent possible under law, the author(s) have dedicated all copyright +# and related and neighboring rights to this software to the public domain +# worldwide. This software is distributed without any warranty. +# +# You should have received a copy of the CC0 Public Domain Dedication along +# with this software. If not, see +# . + +""" +NB: This code is not safe for use with secret keys or secret data. +The only safe use of this code is for verifying signatures on public messages. + +Functions for computing the public key of a secret key and for signing +a message are included, namely publickey_unsafe and signature_unsafe, +for testing purposes only. + +The root of the problem is that Python's long-integer arithmetic is +not designed for use in cryptography. Specifically, it may take more +or less time to execute an operation depending on the values of the +inputs, and its memory access patterns may also depend on the inputs. +This opens it to timing and cache side-channel attacks which can +disclose data to an attacker. We rely on Python's long-integer +arithmetic, so we cannot handle secrets without risking their disclosure. +""" + +import hashlib +import operator +import sys + + +__version__ = "1.0.dev0" + + +# Useful for very coarse version differentiation. +PY3 = sys.version_info[0] == 3 + +if PY3: + indexbytes = operator.getitem + intlist2bytes = bytes + int2byte = operator.methodcaller("to_bytes", 1, "big") +else: + int2byte = chr + range = xrange + + def indexbytes(buf, i): + return ord(buf[i]) + + def intlist2bytes(l): + return b"".join(chr(c) for c in l) + + +b = 256 +q = 2 ** 255 - 19 +l = 2 ** 252 + 27742317777372353535851937790883648493 + + +def H(m): + return hashlib.sha512(m).digest() + + +def pow2(x, p): + """== pow(x, 2**p, q)""" + while p > 0: + x = x * x % q + p -= 1 + return x + + +def inv(z): + """$= z^{-1} \mod q$, for z != 0""" + # Adapted from curve25519_athlon.c in djb's Curve25519. + z2 = z * z % q # 2 + z9 = pow2(z2, 2) * z % q # 9 + z11 = z9 * z2 % q # 11 + z2_5_0 = (z11 * z11) % q * z9 % q # 31 == 2^5 - 2^0 + z2_10_0 = pow2(z2_5_0, 5) * z2_5_0 % q # 2^10 - 2^0 + z2_20_0 = pow2(z2_10_0, 10) * z2_10_0 % q # ... + z2_40_0 = pow2(z2_20_0, 20) * z2_20_0 % q + z2_50_0 = pow2(z2_40_0, 10) * z2_10_0 % q + z2_100_0 = pow2(z2_50_0, 50) * z2_50_0 % q + z2_200_0 = pow2(z2_100_0, 100) * z2_100_0 % q + z2_250_0 = pow2(z2_200_0, 50) * z2_50_0 % q # 2^250 - 2^0 + return pow2(z2_250_0, 5) * z11 % q # 2^255 - 2^5 + 11 = q - 2 + + +d = -121665 * inv(121666) % q +I = pow(2, (q - 1) // 4, q) + + +def xrecover(y, sign=0): + xx = (y * y - 1) * inv(d * y * y + 1) + x = pow(xx, (q + 3) // 8, q) + + if (x * x - xx) % q != 0: + x = (x * I) % q + + if x % 2 != sign: + x = q-x + + return x + + +By = 4 * inv(5) +Bx = xrecover(By) +B = (Bx % q, By % q, 1, (Bx * By) % q) +ident = (0, 1, 1, 0) + + +def edwards_add(P, Q): + # This is formula sequence 'addition-add-2008-hwcd-3' from + # http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html + (x1, y1, z1, t1) = P + (x2, y2, z2, t2) = Q + + a = (y1-x1)*(y2-x2) % q + b = (y1+x1)*(y2+x2) % q + c = t1*2*d*t2 % q + dd = z1*2*z2 % q + e = b - a + f = dd - c + g = dd + c + h = b + a + x3 = e*f + y3 = g*h + t3 = e*h + z3 = f*g + + return (x3 % q, y3 % q, z3 % q, t3 % q) + + +def edwards_sub(P, Q): + # This is formula sequence 'addition-add-2008-hwcd-3' from + # http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html + (x1, y1, z1, t1) = P + (x2, y2, z2, t2) = Q + + # https://eprint.iacr.org/2008/522.pdf + # The negative of (X:Y:Z)is (−X:Y:Z) + #x2 = q-x2 + """ + doesn't work + x2 = q-x2 + t2 = (x2*y2) % q + """ + + zi = inv(z2) + x2 = q-((x2 * zi) % q) + y2 = (y2 * zi) % q + z2 = 1 + t2 = (x2*y2) % q + + + a = (y1-x1)*(y2-x2) % q + b = (y1+x1)*(y2+x2) % q + c = t1*2*d*t2 % q + dd = z1*2*z2 % q + e = b - a + f = dd - c + g = dd + c + h = b + a + x3 = e*f + y3 = g*h + t3 = e*h + z3 = f*g + + return (x3 % q, y3 % q, z3 % q, t3 % q) + + +def edwards_double(P): + # This is formula sequence 'dbl-2008-hwcd' from + # http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html + (x1, y1, z1, t1) = P + + a = x1*x1 % q + b = y1*y1 % q + c = 2*z1*z1 % q + # dd = -a + e = ((x1+y1)*(x1+y1) - a - b) % q + g = -a + b # dd + b + f = g - c + h = -a - b # dd - b + x3 = e*f + y3 = g*h + t3 = e*h + z3 = f*g + + return (x3 % q, y3 % q, z3 % q, t3 % q) + + +def scalarmult(P, e): + if e == 0: + return ident + Q = scalarmult(P, e // 2) + Q = edwards_double(Q) + if e & 1: + Q = edwards_add(Q, P) + return Q + + +# Bpow[i] == scalarmult(B, 2**i) +Bpow = [] + + +def make_Bpow(): + P = B + for i in range(253): + Bpow.append(P) + P = edwards_double(P) +make_Bpow() + + +def scalarmult_B(e): + """ + Implements scalarmult(B, e) more efficiently. + """ + # scalarmult(B, l) is the identity + e = e % l + P = ident + for i in range(253): + if e & 1: + P = edwards_add(P, Bpow[i]) + e = e // 2 + assert e == 0, e + return P + + +def encodeint(y): + bits = [(y >> i) & 1 for i in range(b)] + return b''.join([ + int2byte(sum([bits[i * 8 + j] << j for j in range(8)])) + for i in range(b//8) + ]) + + +def encodepoint(P): + (x, y, z, t) = P + zi = inv(z) + x = (x * zi) % q + y = (y * zi) % q + bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1] + return b''.join([ + int2byte(sum([bits[i * 8 + j] << j for j in range(8)])) + for i in range(b // 8) + ]) + + +def bit(h, i): + return (indexbytes(h, i // 8) >> (i % 8)) & 1 + + +def publickey_unsafe(sk): + """ + Not safe to use with secret keys or secret data. + + See module docstring. This function should be used for testing only. + """ + h = H(sk) + a = 2 ** (b - 2) + sum(2 ** i * bit(h, i) for i in range(3, b - 2)) + A = scalarmult_B(a) + return encodepoint(A) + + +def Hint(m): + h = H(m) + return sum(2 ** i * bit(h, i) for i in range(2 * b)) + + +def signature_unsafe(m, sk, pk): + """ + Not safe to use with secret keys or secret data. + + See module docstring. This function should be used for testing only. + """ + h = H(sk) + a = 2 ** (b - 2) + sum(2 ** i * bit(h, i) for i in range(3, b - 2)) + r = Hint( + intlist2bytes([indexbytes(h, j) for j in range(b // 8, b // 4)]) + m + ) + R = scalarmult_B(r) + S = (r + Hint(encodepoint(R) + pk + m) * a) % l + return encodepoint(R) + encodeint(S) + + +def isoncurve(P): + (x, y, z, t) = P + return (z % q != 0 and + x*y % q == z*t % q and + (y*y - x*x - z*z - d*t*t) % q == 0) + + +def decodeint(s): + return sum(2 ** i * bit(s, i) for i in range(0, b)) + + +def decodepoint(s): + y = sum(2 ** i * bit(s, i) for i in range(0, b - 1)) + x = xrecover(y) + if x & 1 != bit(s, b-1): + x = q - x + P = (x, y, 1, (x*y) % q) + if not isoncurve(P): + raise ValueError("decoding point that is not on curve") + return P + + +class SignatureMismatch(Exception): + pass + + +def checkvalid(s, m, pk): + """ + Not safe to use when any argument is secret. + + See module docstring. This function should be used only for + verifying public signatures of public messages. + """ + if len(s) != b // 4: + raise ValueError("signature length is wrong") + + if len(pk) != b // 8: + raise ValueError("public-key length is wrong") + + R = decodepoint(s[:b // 8]) + A = decodepoint(pk) + S = decodeint(s[b // 8:b // 4]) + h = Hint(encodepoint(R) + pk + m) + + (x1, y1, z1, t1) = P = scalarmult_B(S) + (x2, y2, z2, t2) = Q = edwards_add(R, scalarmult(A, h)) + + if (not isoncurve(P) or not isoncurve(Q) or + (x1*z2 - x2*z1) % q != 0 or (y1*z2 - y2*z1) % q != 0): + raise SignatureMismatch("signature does not pass verification") + + +def is_identity(P): + return True if P[0] == 0 else False + + +def edwards_negated(P): + (x, y, z, t) = P + + zi = inv(z) + x = q - ((x * zi) % q) + y = (y * zi) % q + z = 1 + t = (x * y) % q + + return (x, y, z, t) diff --git a/basicswap/db.py b/basicswap/db.py index 02f32a8..a0a4cb5 100644 --- a/basicswap/db.py +++ b/basicswap/db.py @@ -53,6 +53,9 @@ class Offer(Base): expire_at = sa.Column(sa.BigInteger) was_sent = sa.Column(sa.Boolean) + from_feerate = sa.Column(sa.BigInteger) + to_feerate = sa.Column(sa.BigInteger) + auto_accept_bids = sa.Column(sa.Boolean) state = sa.Column(sa.Integer) @@ -125,10 +128,13 @@ class Bid(Base): self.participate_tx.state = new_state self.participate_tx.states = (self.participate_tx.states if self.participate_tx.states is not None else bytes()) + struct.pack(' 0) + + def verifyPubkey(self, pubkey_bytes): + return verify_secp256k1_point(pubkey_bytes) + def encodePubkey(self, pk): return pointToCPK(pk) @@ -192,14 +206,20 @@ class BTCInterface(CoinInterface): return secret_hash, pk1, pk2, csv_val, pk3, pk4 def genScriptLockTxScript(self, sh, Kal, Kaf, lock_blocks, Karl, Karf): + + Kal_enc = Kal if len(Kal) == 33 else self.encodePubkey(Kal) + Kaf_enc = Kaf if len(Kaf) == 33 else self.encodePubkey(Kaf) + Karl_enc = Karl if len(Karl) == 33 else self.encodePubkey(Karl) + Karf_enc = Karf if len(Karf) == 33 else self.encodePubkey(Karf) + return CScript([ CScriptOp(OP_IF), CScriptOp(OP_SIZE), 32, CScriptOp(OP_EQUALVERIFY), CScriptOp(OP_SHA256), sh, CScriptOp(OP_EQUALVERIFY), - 2, self.encodePubkey(Kal), self.encodePubkey(Kaf), 2, CScriptOp(OP_CHECKMULTISIG), + 2, Kal_enc, Kaf_enc, 2, CScriptOp(OP_CHECKMULTISIG), CScriptOp(OP_ELSE), lock_blocks, CScriptOp(OP_CHECKSEQUENCEVERIFY), CScriptOp(OP_DROP), - 2, self.encodePubkey(Karl), self.encodePubkey(Karf), 2, CScriptOp(OP_CHECKMULTISIG), + 2, Karl_enc, Karf_enc, 2, CScriptOp(OP_CHECKMULTISIG), CScriptOp(OP_ENDIF)]) def createScriptLockTx(self, value, sh, Kal, Kaf, lock_blocks, Karl, Karf): @@ -209,7 +229,7 @@ class BTCInterface(CoinInterface): tx.nVersion = self.txVersion() tx.vout.append(self.txoType(value, CScript([OP_0, hashlib.sha256(script).digest()]))) - return tx, script + return tx.serialize(), script def extractScriptLockRefundScriptValues(self, script_bytes): script_len = len(script_bytes) @@ -243,15 +263,22 @@ class BTCInterface(CoinInterface): return pk1, pk2, csv_val, pk3 def genScriptLockRefundTxScript(self, Karl, Karf, csv_val, Kaf): + + Kaf_enc = Kaf if len(Kaf) == 33 else self.encodePubkey(Kaf) + Karl_enc = Karl if len(Karl) == 33 else self.encodePubkey(Karl) + Karf_enc = Karf if len(Karf) == 33 else self.encodePubkey(Karf) + return CScript([ CScriptOp(OP_IF), - 2, self.encodePubkey(Karl), self.encodePubkey(Karf), 2, CScriptOp(OP_CHECKMULTISIG), + 2, Karl_enc, Karf_enc, 2, CScriptOp(OP_CHECKMULTISIG), CScriptOp(OP_ELSE), csv_val, CScriptOp(OP_CHECKSEQUENCEVERIFY), CScriptOp(OP_DROP), - self.encodePubkey(Kaf), CScriptOp(OP_CHECKSIG), + Kaf_enc, CScriptOp(OP_CHECKSIG), CScriptOp(OP_ENDIF)]) - def createScriptLockRefundTx(self, tx_lock, script_lock, Karl, Karf, csv_val, Kaf, tx_fee_rate): + def createScriptLockRefundTx(self, tx_lock_bytes, script_lock, Karl, Karf, csv_val, Kaf, tx_fee_rate): + tx_lock = CTransaction() + tx_lock = FromHex(tx_lock, tx_lock_bytes.hex()) output_script = CScript([OP_0, hashlib.sha256(script_lock).digest()]) locked_n = findOutput(tx_lock, output_script) @@ -281,13 +308,15 @@ class BTCInterface(CoinInterface): logging.info('createScriptLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) - return tx, refund_script, tx.vout[0].nValue + return tx.serialize(), refund_script, tx.vout[0].nValue - def createScriptLockRefundSpendTx(self, tx_lock_refund, script_lock_refund, Kal, tx_fee_rate): + def createScriptLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, Kal, tx_fee_rate): # Returns the coinA locked coin to the leader # The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey # When the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower + tx_lock_refund = self.loadTx(tx_lock_refund_bytes) + output_script = CScript([OP_0, hashlib.sha256(script_lock_refund).digest()]) locked_n = findOutput(tx_lock_refund, output_script) assert_cond(locked_n is not None, 'Output not found in tx') @@ -300,7 +329,8 @@ class BTCInterface(CoinInterface): tx.nVersion = self.txVersion() tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), nSequence=0)) - pubkeyhash = hash160(self.encodePubkey(Kal)) + #pubkeyhash = hash160(self.encodePubkey(Kal)) + pubkeyhash = hash160(Kal) tx.vout.append(self.txoType(locked_coin, CScript([OP_0, pubkeyhash]))) witness_bytes = len(script_lock_refund) @@ -315,7 +345,7 @@ class BTCInterface(CoinInterface): logging.info('createScriptLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) - return tx + return tx.serialize() def createScriptLockRefundSpendToFTx(self, tx_lock_refund, script_lock_refund, pkh_dest, tx_fee_rate): # Sends the coinA locked coin to the follower @@ -347,7 +377,7 @@ class BTCInterface(CoinInterface): logging.info('createScriptLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) - return tx + return tx.serialize() def createScriptLockSpendTx(self, tx_lock, script_lock, pkh_dest, tx_fee_rate): @@ -379,7 +409,7 @@ class BTCInterface(CoinInterface): logging.info('createScriptLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) - return tx + return tx.serialize() def verifyLockTx(self, tx, script_out, swap_value, @@ -592,11 +622,13 @@ class BTCInterface(CoinInterface): return True - def signTx(self, key_int, tx, prevout_n, prevout_script, prevout_value): + def signTx(self, key_bytes, tx_bytes, prevout_n, prevout_script, prevout_value): + # TODO: use libsecp356k1 + tx = self.loadTx(tx_bytes) sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value) eck = ECKey() - eck.set(i2b(key_int), compressed=True) + eck.set(key_bytes, compressed=True) return eck.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL @@ -611,22 +643,25 @@ class BTCInterface(CoinInterface): def decryptOtVES(self, k, esig): return otves.DecSig(k, esig) + b'\x01' # 0x1 is SIGHASH_ALL - def verifyTxSig(self, tx, sig, K, prevout_n, prevout_script, prevout_value): + def verifyTxSig(self, tx_bytes, sig, K, prevout_n, prevout_script, prevout_value): + tx = self.loadTx(tx_bytes) sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value) ecK = ECPubKey() - ecK.set_int(K.x(), K.y()) + #ecK.set_int(K.x(), K.y()) + ecK.set(K) return ecK.verify_ecdsa(sig[: -1], sig_hash) # Pop the hashtype byte def fundTx(self, tx, feerate): feerate_str = format_amount(feerate, self.exp()) - rv = self.rpc_callback('fundrawtransaction', [ToHex(tx), {'feeRate': feerate_str}]) - return FromHex(tx, rv['hex']) + rv = self.rpc_callback('fundrawtransaction', [tx.hex(), {'feeRate': feerate_str}]) + return bytes.fromhex(rv['hex']) def signTxWithWallet(self, tx): rv = self.rpc_callback('signrawtransactionwithwallet', [ToHex(tx)]) - return FromHex(tx, rv['hex']) + #return FromHex(tx, rv['hex']) + return bytes.fromhex(rv['hex']) def publishTx(self, tx): return self.rpc_callback('sendrawtransaction', [ToHex(tx)]) @@ -640,7 +675,9 @@ class BTCInterface(CoinInterface): tx.deserialize(BytesIO(tx_bytes)) return tx - def getTxHash(self, tx): + def getTxHash(self, tx_bytes): + tx = CTransaction() + tx = FromHex(tx, tx_bytes.hex()) tx.rehash() return i2b(tx.sha256) @@ -680,7 +717,7 @@ class BTCInterface(CoinInterface): tx.nVersion = self.txVersion() p2wpkh = self.getPkDest(Kbs) tx.vout.append(self.txoType(output_amount, p2wpkh)) - return tx + return tx.serialize() def publishBLockTx(self, Kbv, Kbs, output_amount, feerate): b_lock_tx = self.createBLockTx(Kbs, output_amount) diff --git a/basicswap/interface_xmr.py b/basicswap/interface_xmr.py index 2812c04..f94f33f 100644 --- a/basicswap/interface_xmr.py +++ b/basicswap/interface_xmr.py @@ -8,11 +8,24 @@ import time import logging -from .chainparams import CoinInterface -from .rpc_xmr import make_xmr_rpc_func, make_xmr_wallet_rpc_func +import basicswap.contrib.ed25519_fast as edf +import basicswap.ed25519_fast_util as edu +from coincurve.ed25519 import ed25519_get_pubkey +from coincurve.keys import PrivateKey +from coincurve.dleag import ( + verify_ed25519_point, + dleag_proof_len, + dleag_verify, + dleag_prove) + from .util import ( - format_amount -) + format_amount) +from .rpc_xmr import ( + make_xmr_rpc_func, + make_xmr_wallet_rpc_func) +from .ecc_util import ( + b2i) +from .chainparams import CoinInterface XMR_COIN = 10 ** 12 @@ -49,6 +62,9 @@ class XMRInterface(CoinInterface): rv['verificationprogress'] = 0 # TODO return rv + def getChainHeight(self): + return self.rpc_cb('get_block_count')['count'] + def getWalletInfo(self): rv = {} balance_info = self.rpc_wallet_cb('get_balance') @@ -60,6 +76,10 @@ class XMRInterface(CoinInterface): logging.debug('TODO - subaddress?') return self.rpc_wallet_cb('get_address')['address'] + def isValidKey(self, key_bytes): + ki = b2i(key_bytes) + return ki < edf.l and ki > 8 + def getNewSecretKey(self): return edu.get_secret() @@ -72,6 +92,26 @@ class XMRInterface(CoinInterface): def decodePubkey(self, pke): return edf.decodepoint(pke) + def getPubkey(self, privkey): + return ed25519_get_pubkey(privkey) + + def verifyKey(self, k): + i = b2i(k) + return(i < edf.l and i > 8) + + def verifyPubkey(self, pubkey_bytes): + return verify_ed25519_point(pubkey_bytes) + + def proveDLEAG(self, key): + privkey = PrivateKey(key) + return dleag_prove(privkey) + + def verifyDLEAG(self, dleag_bytes): + return dleag_verify(dleag_bytes) + + def lengthDLEAG(self): + return dleag_proof_len() + def decodeKey(self, k): i = b2i(k) assert(i < edf.l and i > 8) diff --git a/basicswap/messages.proto b/basicswap/messages.proto index db8e252..135436f 100644 --- a/basicswap/messages.proto +++ b/basicswap/messages.proto @@ -26,6 +26,9 @@ message OfferMessage { string proof_signature = 11; bytes pkhash_seller = 12; bytes secret_hash = 13; + + uint64 fee_rate_from = 14; + uint64 fee_rate_to = 15; } /* Step 2, buyer -> seller */ @@ -48,29 +51,44 @@ message BidAcceptMessage { } -message XmrOfferMessage { - uint32 coin_from = 1; - uint32 coin_to = 2; - uint64 amount_from = 3; - uint64 rate = 4; - uint64 min_bid_amount = 5; - uint64 time_valid = 6; - enum LockType { - NOT_SET = 0; - SEQUENCE_LOCK_BLOCKS = 1; - SEQUENCE_LOCK_TIME = 2; - ABS_LOCK_BLOCKS = 3; - ABS_LOCK_TIME = 4; - } - LockType lock_type = 7; - uint32 lock_value = 8; - uint32 swap_type = 9; +/* Step 2, buyer -> seller */ +message XmrBidMessage { + bytes offer_msg_id = 1; + uint64 time_valid = 2; /* seconds bid is valid for */ + uint64 amount = 3; /* amount of amount_from bid is for */ + bytes pkaf = 4; + bytes pkarf = 5; - /* optional */ - string proof_address = 10; - string proof_signature = 11; - bytes pkhash_seller = 12; - bytes secret_hash = 13; + bytes kbvf = 6; + bytes kbsf_dleag = 7; } + +message XmrSplitMessage { + bytes msg_id = 1; + uint32 msg_type = 2; /* 1 XmrBid, 2 XmrBidAccept */ + uint32 sequence = 3; + bytes dleag = 4; +} + + +message XmrBidAcceptMessage { + bytes bid_msg_id = 1; + + bytes sh = 2; + bytes pkal = 3; + bytes pkarl = 4; + + bytes kbvl = 5; + bytes kbsl_dleag = 6; + + /* MSG2F */ + bytes a_lock_tx = 7; + bytes a_lock_tx_script = 8; + bytes a_lock_refund_tx = 9; + bytes a_lock_refund_tx_script = 10; + bytes a_lock_refund_spend_tx = 11; + bytes al_lock_refund_tx_sig = 12; +} + diff --git a/basicswap/messages_pb2.py b/basicswap/messages_pb2.py index 686d918..b6b5029 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\"\xac\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\"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\"\xb2\x03\n\x0fXmrOfferMessage\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\x36\n\tlock_type\x18\x07 \x01(\x0e\x32#.basicswap.XmrOfferMessage.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\"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\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\"\x88\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\"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\x62\x06proto3' ) @@ -59,51 +59,11 @@ _OFFERMESSAGE_LOCKTYPE = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=345, - serialized_end=458, + serialized_start=389, + serialized_end=502, ) _sym_db.RegisterEnumDescriptor(_OFFERMESSAGE_LOCKTYPE) -_XMROFFERMESSAGE_LOCKTYPE = _descriptor.EnumDescriptor( - name='LockType', - full_name='basicswap.XmrOfferMessage.LockType', - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name='NOT_SET', index=0, number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='SEQUENCE_LOCK_BLOCKS', index=1, number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='SEQUENCE_LOCK_TIME', index=2, number=2, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='ABS_LOCK_BLOCKS', index=3, number=3, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='ABS_LOCK_TIME', index=4, number=4, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - ], - containing_type=None, - serialized_options=None, - serialized_start=345, - serialized_end=458, -) -_sym_db.RegisterEnumDescriptor(_XMROFFERMESSAGE_LOCKTYPE) - _OFFERMESSAGE = _descriptor.Descriptor( name='OfferMessage', @@ -204,6 +164,20 @@ _OFFERMESSAGE = _descriptor.Descriptor( 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='fee_rate_from', full_name='basicswap.OfferMessage.fee_rate_from', index=13, + number=14, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + 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='fee_rate_to', full_name='basicswap.OfferMessage.fee_rate_to', index=14, + number=15, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + 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=[ ], @@ -218,7 +192,7 @@ _OFFERMESSAGE = _descriptor.Descriptor( oneofs=[ ], serialized_start=30, - serialized_end=458, + serialized_end=502, ) @@ -284,8 +258,8 @@ _BIDMESSAGE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=461, - serialized_end=601, + serialized_start=505, + serialized_end=645, ) @@ -330,106 +304,64 @@ _BIDACCEPTMESSAGE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=603, - serialized_end=689, + serialized_start=647, + serialized_end=733, ) -_XMROFFERMESSAGE = _descriptor.Descriptor( - name='XmrOfferMessage', - full_name='basicswap.XmrOfferMessage', +_XMRBIDMESSAGE = _descriptor.Descriptor( + name='XmrBidMessage', + full_name='basicswap.XmrBidMessage', filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( - name='coin_from', full_name='basicswap.XmrOfferMessage.coin_from', index=0, - number=1, type=13, cpp_type=3, label=1, + name='offer_msg_id', full_name='basicswap.XmrBidMessage.offer_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='time_valid', full_name='basicswap.XmrBidMessage.time_valid', index=1, + number=2, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, 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='coin_to', full_name='basicswap.XmrOfferMessage.coin_to', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - 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='amount_from', full_name='basicswap.XmrOfferMessage.amount_from', index=2, + name='amount', full_name='basicswap.XmrBidMessage.amount', index=2, number=3, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, 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='rate', full_name='basicswap.XmrOfferMessage.rate', index=3, - number=4, type=4, cpp_type=4, label=1, - has_default_value=False, default_value=0, - 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='min_bid_amount', full_name='basicswap.XmrOfferMessage.min_bid_amount', index=4, - number=5, type=4, cpp_type=4, label=1, - has_default_value=False, default_value=0, - 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='time_valid', full_name='basicswap.XmrOfferMessage.time_valid', index=5, - number=6, type=4, cpp_type=4, label=1, - has_default_value=False, default_value=0, - 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='lock_type', full_name='basicswap.XmrOfferMessage.lock_type', index=6, - number=7, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - 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='lock_value', full_name='basicswap.XmrOfferMessage.lock_value', index=7, - number=8, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - 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='swap_type', full_name='basicswap.XmrOfferMessage.swap_type', index=8, - number=9, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - 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='proof_address', full_name='basicswap.XmrOfferMessage.proof_address', index=9, - number=10, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - 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='proof_signature', full_name='basicswap.XmrOfferMessage.proof_signature', index=10, - number=11, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - 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='pkhash_seller', full_name='basicswap.XmrOfferMessage.pkhash_seller', index=11, - number=12, type=12, cpp_type=9, label=1, + name='pkaf', full_name='basicswap.XmrBidMessage.pkaf', index=3, + number=4, 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_hash', full_name='basicswap.XmrOfferMessage.secret_hash', index=12, - number=13, type=12, cpp_type=9, label=1, + name='pkarf', full_name='basicswap.XmrBidMessage.pkarf', index=4, + number=5, 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='kbvf', full_name='basicswap.XmrBidMessage.kbvf', index=5, + number=6, 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='kbsf_dleag', full_name='basicswap.XmrBidMessage.kbsf_dleag', index=6, + number=7, 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, @@ -439,7 +371,6 @@ _XMROFFERMESSAGE = _descriptor.Descriptor( ], nested_types=[], enum_types=[ - _XMROFFERMESSAGE_LOCKTYPE, ], serialized_options=None, is_extendable=False, @@ -447,18 +378,180 @@ _XMROFFERMESSAGE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=692, - serialized_end=1126, + serialized_start=736, + serialized_end=872, +) + + +_XMRSPLITMESSAGE = _descriptor.Descriptor( + name='XmrSplitMessage', + full_name='basicswap.XmrSplitMessage', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='msg_id', full_name='basicswap.XmrSplitMessage.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='msg_type', full_name='basicswap.XmrSplitMessage.msg_type', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + 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='sequence', full_name='basicswap.XmrSplitMessage.sequence', index=2, + number=3, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + 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='dleag', full_name='basicswap.XmrSplitMessage.dleag', index=3, + number=4, 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=874, + serialized_end=958, +) + + +_XMRBIDACCEPTMESSAGE = _descriptor.Descriptor( + name='XmrBidAcceptMessage', + full_name='basicswap.XmrBidAcceptMessage', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='bid_msg_id', full_name='basicswap.XmrBidAcceptMessage.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='sh', full_name='basicswap.XmrBidAcceptMessage.sh', 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), + _descriptor.FieldDescriptor( + name='pkal', full_name='basicswap.XmrBidAcceptMessage.pkal', index=2, + number=3, 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='pkarl', full_name='basicswap.XmrBidAcceptMessage.pkarl', index=3, + number=4, 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='kbvl', full_name='basicswap.XmrBidAcceptMessage.kbvl', index=4, + number=5, 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='kbsl_dleag', full_name='basicswap.XmrBidAcceptMessage.kbsl_dleag', index=5, + number=6, 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='a_lock_tx', full_name='basicswap.XmrBidAcceptMessage.a_lock_tx', index=6, + number=7, 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='a_lock_tx_script', full_name='basicswap.XmrBidAcceptMessage.a_lock_tx_script', index=7, + number=8, 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='a_lock_refund_tx', full_name='basicswap.XmrBidAcceptMessage.a_lock_refund_tx', index=8, + number=9, 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='a_lock_refund_tx_script', full_name='basicswap.XmrBidAcceptMessage.a_lock_refund_tx_script', index=9, + number=10, 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='a_lock_refund_spend_tx', full_name='basicswap.XmrBidAcceptMessage.a_lock_refund_spend_tx', index=10, + number=11, 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='al_lock_refund_tx_sig', full_name='basicswap.XmrBidAcceptMessage.al_lock_refund_tx_sig', index=11, + number=12, 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=961, + serialized_end=1244, ) _OFFERMESSAGE.fields_by_name['lock_type'].enum_type = _OFFERMESSAGE_LOCKTYPE _OFFERMESSAGE_LOCKTYPE.containing_type = _OFFERMESSAGE -_XMROFFERMESSAGE.fields_by_name['lock_type'].enum_type = _XMROFFERMESSAGE_LOCKTYPE -_XMROFFERMESSAGE_LOCKTYPE.containing_type = _XMROFFERMESSAGE DESCRIPTOR.message_types_by_name['OfferMessage'] = _OFFERMESSAGE DESCRIPTOR.message_types_by_name['BidMessage'] = _BIDMESSAGE DESCRIPTOR.message_types_by_name['BidAcceptMessage'] = _BIDACCEPTMESSAGE -DESCRIPTOR.message_types_by_name['XmrOfferMessage'] = _XMROFFERMESSAGE +DESCRIPTOR.message_types_by_name['XmrBidMessage'] = _XMRBIDMESSAGE +DESCRIPTOR.message_types_by_name['XmrSplitMessage'] = _XMRSPLITMESSAGE +DESCRIPTOR.message_types_by_name['XmrBidAcceptMessage'] = _XMRBIDACCEPTMESSAGE _sym_db.RegisterFileDescriptor(DESCRIPTOR) OfferMessage = _reflection.GeneratedProtocolMessageType('OfferMessage', (_message.Message,), { @@ -482,12 +575,26 @@ BidAcceptMessage = _reflection.GeneratedProtocolMessageType('BidAcceptMessage', }) _sym_db.RegisterMessage(BidAcceptMessage) -XmrOfferMessage = _reflection.GeneratedProtocolMessageType('XmrOfferMessage', (_message.Message,), { - 'DESCRIPTOR' : _XMROFFERMESSAGE, +XmrBidMessage = _reflection.GeneratedProtocolMessageType('XmrBidMessage', (_message.Message,), { + 'DESCRIPTOR' : _XMRBIDMESSAGE, '__module__' : 'messages_pb2' - # @@protoc_insertion_point(class_scope:basicswap.XmrOfferMessage) + # @@protoc_insertion_point(class_scope:basicswap.XmrBidMessage) }) -_sym_db.RegisterMessage(XmrOfferMessage) +_sym_db.RegisterMessage(XmrBidMessage) + +XmrSplitMessage = _reflection.GeneratedProtocolMessageType('XmrSplitMessage', (_message.Message,), { + 'DESCRIPTOR' : _XMRSPLITMESSAGE, + '__module__' : 'messages_pb2' + # @@protoc_insertion_point(class_scope:basicswap.XmrSplitMessage) + }) +_sym_db.RegisterMessage(XmrSplitMessage) + +XmrBidAcceptMessage = _reflection.GeneratedProtocolMessageType('XmrBidAcceptMessage', (_message.Message,), { + 'DESCRIPTOR' : _XMRBIDACCEPTMESSAGE, + '__module__' : 'messages_pb2' + # @@protoc_insertion_point(class_scope:basicswap.XmrBidAcceptMessage) + }) +_sym_db.RegisterMessage(XmrBidAcceptMessage) # @@protoc_insertion_point(module_scope) diff --git a/basicswap/util.py b/basicswap/util.py index 8429e58..154397f 100644 --- a/basicswap/util.py +++ b/basicswap/util.py @@ -5,6 +5,7 @@ # file LICENSE or http://www.opensource.org/licenses/mit-license.php. import json +import decimal import hashlib from .contrib.segwit_addr import bech32_decode, convertbits, bech32_encode diff --git a/tests/basicswap/test_other.py b/tests/basicswap/test_other.py index 5d1c92b..b1e4fd5 100644 --- a/tests/basicswap/test_other.py +++ b/tests/basicswap/test_other.py @@ -1,16 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright (c) 2019 tecnovert +# Copyright (c) 2019-2020 tecnovert # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. import unittest + +import basicswap.contrib.ed25519_fast as edf +import basicswap.ed25519_fast_util as edu + +from basicswap.ecc_util import i2b +from coincurve.ed25519 import ed25519_get_pubkey + + from basicswap.util import ( SerialiseNum, DeserialiseNum, make_int, format8, + format_amount, + validate_amount, ) from basicswap.basicswap import ( Coins, @@ -134,6 +144,14 @@ class Test(unittest.TestCase): except Exception as e: assert('Too many decimal places' in str(e)) + def test_ed25519(self): + privkey = edu.get_secret() + pubkey = edu.encodepoint(edf.scalarmult_B(privkey)) + + privkey_bytes = i2b(privkey) + pubkey_test = ed25519_get_pubkey(privkey_bytes) + assert(pubkey == pubkey_test) + if __name__ == '__main__': unittest.main() diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py index feec1ba..5d36e5b 100644 --- a/tests/basicswap/test_xmr.py +++ b/tests/basicswap/test_xmr.py @@ -67,6 +67,8 @@ 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() NUM_NODES = 3 @@ -239,6 +241,7 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey): 'check_watched_seconds': 4, 'check_expired_seconds': 60, 'check_events_seconds': 1, + 'check_xmr_swaps_seconds': 1, 'min_delay_auto_accept': 1, 'max_delay_auto_accept': 5 } @@ -310,7 +313,7 @@ def run_loop(cls): btcRpc('generatetoaddress 1 {}'.format(cls.btc_addr)) if cls.xmr_addr is not None: - callrpc_xmr_na(XMR_BASE_RPC_PORT + 0, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1}) + callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1}) time.sleep(1.0) @@ -335,6 +338,7 @@ class Test(unittest.TestCase): logger.propagate = False logger.handlers = [] logger.setLevel(logging.INFO) # DEBUG shows many messages from requests.post + #logger.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s %(levelname)s : %(message)s') stream_stdout = logging.StreamHandler() stream_stdout.setFormatter(formatter) @@ -417,7 +421,7 @@ class Test(unittest.TestCase): t.start() cls.btc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT) - cls.xmr_addr = cls.callxmrnodewallet(cls, 0, 'get_address')['address'] + 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) @@ -425,10 +429,10 @@ class Test(unittest.TestCase): checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=BTC_BASE_RPC_PORT)) - if callrpc_xmr_na(XMR_BASE_RPC_PORT + 0, 'get_block_count')['count'] < num_blocks: + if callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'] < num_blocks: logging.info('Mining %d Monero blocks.', num_blocks) - callrpc_xmr_na(XMR_BASE_RPC_PORT + 0, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': num_blocks}) - rv = callrpc_xmr_na(XMR_BASE_RPC_PORT + 0, 'get_block_count') + callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': num_blocks}) + rv = callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count') logging.info('XMR blocks: %d', rv['count']) logging.info('Starting update thread.') @@ -509,21 +513,49 @@ class Test(unittest.TestCase): return raise ValueError('wait_for_offer timed out.') + def wait_for_bid(self, swap_client, bid_id, state=None, sent=False): + logging.info('wait_for_bid %s', bid_id.hex()) + for i in range(20): + time.sleep(1) + bids = swap_client.listBids(sent=sent) + for bid in bids: + if bid[1] == bid_id: + if state is not None and state != bid[4]: + continue + return + raise ValueError('wait_for_bid timed out.') + def test_01_part_xmr(self): logging.info('---------- Test PART to XMR') swap_clients = self.swap_clients - js_0 = json.loads(urlopen('http://localhost:1800/json/wallets').read()) + js_0 = json.loads(urlopen('http://localhost:1801/json/wallets').read()) assert(make_int(js_0[str(int(Coins.XMR))]['balance'], scale=12) > 0) assert(make_int(js_0[str(int(Coins.XMR))]['unconfirmed'], scale=12) > 0) offer_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 100 * COIN, 10 * XMR_COIN, 100 * COIN, SwapTypes.XMR_SWAP) - self.wait_for_offer(swap_clients[1], offer_id) - offers = swap_clients[1].listOffers() + offer = swap_clients[1].getOffer(offer_id) + + offers = swap_clients[1].listOffers(filters={'offer_id': offer_id}) assert(len(offers) == 1) - for offer in offers: - print('offer', offer) + offer = offers[0] + pprint(vars(offer)) + + bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from) + + self.wait_for_bid(swap_clients[0], bid_id, BidStates.BID_RECEIVED) + + bid, xmr_swap = swap_clients[0].getXmrBid(bid_id) + assert(xmr_swap) + + swap_clients[0].acceptXmrBid(bid_id) + + self.wait_for_bid(swap_clients[1], bid_id, BidStates.BID_ACCEPTED, sent=True) + + + + if __name__ == '__main__':