diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 00fa6f8..039afdb 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -870,7 +870,7 @@ class BasicSwap(BaseApp): ci.initialiseWallet(key_view, key_spend) root_address = ci.getAddressFromKeys(key_view, key_spend) - key_str = 'main_wallet_addr_' + chainparams[coin_type]['name'] + key_str = 'main_wallet_addr_' + ci.coin_name() self.setStringKV(key_str, root_address) return @@ -878,7 +878,7 @@ class BasicSwap(BaseApp): root_hash = ci.getAddressHashFromKey(root_key)[::-1] ci.initialiseWallet(root_key) - key_str = 'main_wallet_seedid_' + chainparams[coin_type]['name'] + key_str = 'main_wallet_seedid_' + ci.coin_name() self.setStringKV(key_str, root_hash.hex()) def setIntKVInSession(self, str_key, int_val, session): @@ -1101,6 +1101,12 @@ class BasicSwap(BaseApp): else: raise ValueError('Unknown locktype') + def validateOfferValidTime(self, coin_from, coin_to, valid_for_seconds): + if valid_for_seconds < 60 * 60: # SMSG_MIN_TTL + raise ValueError('Offer TTL too low') + if valid_for_seconds > 48 * 60 * 60: + raise ValueError('Offer TTL too high') + def postOffer(self, coin_from, coin_to, amount, rate, min_bid_amount, swap_type, lock_type=SEQUENCE_LOCK_TIME, lock_value=48 * 60 * 60, auto_accept_bids=False, addr_send_from=None, extra_options={}): # Offer to send offer.amount_from of coin_from in exchange for offer.amount_from * offer.rate of coin_to @@ -1117,9 +1123,12 @@ class BasicSwap(BaseApp): except Exception: raise ValueError('Unknown coin to type') + valid_for_seconds = extra_options.get('valid_for_seconds', 60 * 60) + self.validateSwapType(coin_from_t, coin_to_t, swap_type) self.validateOfferAmounts(coin_from_t, coin_to_t, amount, rate, min_bid_amount) self.validateOfferLockValue(coin_from_t, coin_to_t, lock_type, lock_value) + self.validateOfferValidTime(coin_from_t, coin_to_t, valid_for_seconds) self.mxDB.acquire() session = None @@ -1136,7 +1145,7 @@ class BasicSwap(BaseApp): msg_buf.rate = int(rate) msg_buf.min_bid_amount = int(min_bid_amount) - msg_buf.time_valid = 60 * 60 + msg_buf.time_valid = valid_for_seconds msg_buf.lock_type = lock_type msg_buf.lock_value = lock_value msg_buf.swap_type = swap_type @@ -1424,7 +1433,9 @@ class BasicSwap(BaseApp): self.log.info('withdrawCoin %s %s to %s %s', value, self.getTicker(coin_type), addr_to, ' subfee' if subfee else '') ci = self.ci(coin_type) - return ci.withdrawCoin(value, addr_to, subfee) + txid = ci.withdrawCoin(value, addr_to, subfee) + self.log.debug('In txn: {}'.format(txid)) + return txid def withdrawParticl(self, type_from, type_to, value, addr_to, subfee): self.log.info('withdrawParticl %s %s to %s %s %s', value, type_from, type_to, addr_to, ' subfee' if subfee else '') @@ -1435,7 +1446,9 @@ class BasicSwap(BaseApp): type_to = 'part' ci = self.ci(Coins.PART) - return ci.sendTypeTo(type_from, type_to, value, addr_to, subfee) + txid = ci.sendTypeTo(type_from, type_to, value, addr_to, subfee) + self.log.debug('In txn: {}'.format(txid)) + return txid def cacheNewAddressForCoin(self, coin_type): self.log.debug('cacheNewAddressForCoin %s', coin_type) @@ -1449,7 +1462,7 @@ class BasicSwap(BaseApp): if c == Coins.PART: return True # TODO if c == Coins.XMR: - expect_address = self.getStringKV('main_wallet_addr_' + chainparams[c]['name']) + expect_address = self.getStringKV('main_wallet_addr_' + ci.coin_name()) if expect_address is None: self.log.warning('Can\'t find expected main wallet address for coin {}'.format(ci.coin_name())) return False @@ -1459,7 +1472,7 @@ class BasicSwap(BaseApp): self.log.warning('Wallet for coin {} not derived from swap seed.'.format(ci.coin_name())) return False - expect_seedid = self.getStringKV('main_wallet_seedid_' + chainparams[c]['name']) + expect_seedid = self.getStringKV('main_wallet_seedid_' + ci.coin_name()) if expect_seedid is None: self.log.warning('Can\'t find expected wallet seed id for coin {}'.format(ci.coin_name())) return False @@ -1976,7 +1989,7 @@ class BasicSwap(BaseApp): bid.initiate_txn_refund = bytes.fromhex(refund_txn) txid = self.submitTxn(coin_from, txn) - self.log.debug('Submitted initiate txn %s to %s chain for bid %s', txid, chainparams[coin_from]['name'], bid_id.hex()) + self.log.debug('Submitted initiate txn %s to %s chain for bid %s', txid, ci_from.coin_name(), bid_id.hex()) bid.initiate_tx = SwapTx( bid_id=bid_id, tx_type=TxTypes.ITX, @@ -2061,7 +2074,6 @@ class BasicSwap(BaseApp): xmr_swap.b_restore_height = wallet_restore_height self.log.warning('XMR swap restore height clamped to {}'.format(wallet_restore_height)) - 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) @@ -3617,6 +3629,7 @@ class BasicSwap(BaseApp): self.validateSwapType(coin_from, coin_to, offer_data.swap_type) self.validateOfferAmounts(coin_from, coin_to, offer_data.amount_from, offer_data.rate, offer_data.min_bid_amount) self.validateOfferLockValue(coin_from, coin_to, offer_data.lock_type, offer_data.lock_value) + self.validateOfferValidTime(coin_from, coin_to, offer_data.time_valid) assert(offer_data.time_valid >= MIN_OFFER_VALID_TIME and offer_data.time_valid <= MAX_OFFER_VALID_TIME), 'Invalid time_valid' assert(msg['sent'] + offer_data.time_valid >= now), 'Offer expired' @@ -3629,9 +3642,8 @@ class BasicSwap(BaseApp): elif offer_data.swap_type == SwapTypes.BUYER_FIRST: raise ValueError('TODO') elif offer_data.swap_type == SwapTypes.XMR_SWAP: - assert(coin_from != Coins.XMR) - assert(coin_from != Coins.PART_ANON) - assert(coin_to == Coins.XMR or coin_to == Coins.PART_ANON) + assert(coin_from not in (Coins.XMR, Coins.PART_ANON)) + assert(coin_to in (Coins.XMR, Coins.PART_ANON)) self.log.debug('TODO - More restrictions') else: raise ValueError('Unknown swap type {}.'.format(offer_data.swap_type)) @@ -4225,7 +4237,7 @@ class BasicSwap(BaseApp): a_lock_tx_id = ci_from.getTxHash(xmr_swap.a_lock_tx) a_lock_tx_vout = ci_from.getTxOutputPos(xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script) - self.log.debug('Waiting for lock txn %s to %s chain for bid %s', a_lock_tx_id.hex(), chainparams[coin_from]['name'], bid_id.hex()) + self.log.debug('Waiting for lock txn %s to %s chain for bid %s', a_lock_tx_id.hex(), ci_from.coin_name(), bid_id.hex()) bid.xmr_a_lock_tx = SwapTx( bid_id=bid_id, tx_type=TxTypes.XMR_SWAP_A_LOCK, @@ -4292,7 +4304,7 @@ class BasicSwap(BaseApp): vout_pos = ci_from.getTxOutputPos(xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script) - self.log.debug('Submitted lock txn %s to %s chain for bid %s', txid_hex, chainparams[coin_from]['name'], bid_id.hex()) + self.log.debug('Submitted lock txn %s to %s chain for bid %s', txid_hex, ci_from.coin_name(), bid_id.hex()) bid.xmr_a_lock_tx = SwapTx( bid_id=bid_id, @@ -4356,7 +4368,7 @@ class BasicSwap(BaseApp): self.logBidEvent(bid, EventLogTypes.FAILED_TX_B_LOCK_PUBLISH, str_error, session) return - self.log.debug('Submitted lock txn %s to %s chain for bid %s', b_lock_tx_id.hex(), chainparams[coin_to]['name'], bid_id.hex()) + self.log.debug('Submitted lock txn %s to %s chain for bid %s', b_lock_tx_id.hex(), ci_to.coin_name(), bid_id.hex()) bid.xmr_b_lock_tx = SwapTx( bid_id=bid_id, tx_type=TxTypes.XMR_SWAP_B_LOCK, @@ -4435,7 +4447,7 @@ class BasicSwap(BaseApp): xmr_swap.a_lock_spend_tx = ci_from.setTxSignature(xmr_swap.a_lock_spend_tx, witness_stack) txid = bytes.fromhex(ci_from.publishTx(xmr_swap.a_lock_spend_tx)) - self.log.debug('Submitted lock spend txn %s to %s chain for bid %s', txid.hex(), chainparams[coin_from]['name'], bid_id.hex()) + self.log.debug('Submitted lock spend txn %s to %s chain for bid %s', txid.hex(), ci_from.coin_name(), bid_id.hex()) bid.xmr_a_lock_spend_tx = SwapTx( bid_id=bid_id, tx_type=TxTypes.XMR_SWAP_A_LOCK_SPEND, @@ -4980,7 +4992,7 @@ class BasicSwap(BaseApp): rv = { 'version': self.coin_clients[coin]['core_version'], 'deposit_address': self.getCachedAddressForCoin(coin), - 'name': chainparams[coin]['name'].capitalize(), + 'name': ci.coin_name().capitalize(), 'blocks': blockchaininfo['blocks'], 'balance': format_amount(make_int(walletinfo['balance'], scale), scale), 'unconfirmed': format_amount(make_int(walletinfo.get('unconfirmed_balance'), scale), scale), diff --git a/basicswap/chainparams.py b/basicswap/chainparams.py index e2365c3..68f766a 100644 --- a/basicswap/chainparams.py +++ b/basicswap/chainparams.py @@ -37,6 +37,7 @@ chainparams = { 'pubkey_address': 0x38, 'script_address': 0x3c, 'key_prefix': 0x6c, + 'stealth_key_prefix': 0x14, 'hrp': 'pw', 'bip44': 44, 'min_amount': 1000, @@ -47,6 +48,7 @@ chainparams = { 'pubkey_address': 0x76, 'script_address': 0x7a, 'key_prefix': 0x2e, + 'stealth_key_prefix': 0x15, 'hrp': 'tpw', 'bip44': 1, 'min_amount': 1000, @@ -57,6 +59,7 @@ chainparams = { 'pubkey_address': 0x76, 'script_address': 0x7a, 'key_prefix': 0x2e, + 'stealth_key_prefix': 0x15, 'hrp': 'rtpw', 'bip44': 1, 'min_amount': 1000, diff --git a/basicswap/ecc_util.py b/basicswap/ecc_util.py index e8b9d99..fad3277 100644 --- a/basicswap/ecc_util.py +++ b/basicswap/ecc_util.py @@ -33,42 +33,42 @@ G = Point(curve_secp256k1, ep.Gx, ep.Gy, ep.o) SECP256K1_ORDER_HALF = ep.o // 2 -def ToDER(P): +def ToDER(P) -> bytes: return bytes((4, )) + int(P.x()).to_bytes(32, byteorder='big') + int(P.y()).to_bytes(32, byteorder='big') -def bytes32ToInt(b): +def bytes32ToInt(b) -> int: return int.from_bytes(b, byteorder='big') -def intToBytes32(i): +def intToBytes32(i: int) -> bytes: return i.to_bytes(32, byteorder='big') -def intToBytes32_le(i): +def intToBytes32_le(i: int) -> bytes: return i.to_bytes(32, byteorder='little') -def bytesToHexStr(b): +def bytesToHexStr(b: bytes) -> str: return codecs.encode(b, 'hex').decode('utf-8') -def hexStrToBytes(h): +def hexStrToBytes(h: str) -> bytes: if h.startswith('0x'): h = h[2:] return bytes.fromhex(h) -def getSecretBytes(): +def getSecretBytes() -> bytes: i = 1 + secrets.randbelow(ep.o - 1) return intToBytes32(i) -def getSecretInt(): +def getSecretInt() -> int: return 1 + secrets.randbelow(ep.o - 1) -def getInsecureBytes(): +def getInsecureBytes() -> bytes: while True: s = os.urandom(32) @@ -77,7 +77,7 @@ def getInsecureBytes(): return s -def getInsecureInt(): +def getInsecureInt() -> int: while True: s = os.urandom(32) @@ -86,7 +86,7 @@ def getInsecureInt(): return s_test -def powMod(x, y, z): +def powMod(x, y, z) -> int: # Calculate (x ** y) % z efficiently. number = 1 while y: diff --git a/basicswap/http_server.py b/basicswap/http_server.py index 0f9f0c1..f4059be 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -66,11 +66,14 @@ def getCoinName(c): def listAvailableCoins(swap_client): coins = [] for k, v in swap_client.coin_clients.items(): + if k not in chainparams: + continue if v['connection_type'] == 'rpc': coins.append((int(k), getCoinName(k))) - if k == Coins.PART: - coins.append((int(Coins.PART_ANON), getCoinName(k))) + pass + # TODO: Uncomment + # coins.append((int(Coins.PART_ANON), getCoinName(k))) return coins @@ -402,14 +405,17 @@ class HttpHandler(BaseHTTPRequestHandler): page_data['coin_to'] = getCoinType(get_data_entry(form_data, 'coin_to')) coin_to = Coins(page_data['coin_to']) ci_to = swap_client.ci(coin_to) + if coin_to != Coins.XMR: + page_data['fee_to_conf'] = ci_to._conf_target # Set default value parsed_data['coin_to'] = coin_to - if coin_to == Coins.XMR: - page_data['swap_style'] = 'xmr' - else: - page_data['swap_style'] = 'atomic' except Exception: errors.append('Unknown Coin To') + if parsed_data['coin_to'] in (Coins.XMR, Coins.PART_ANON): + page_data['swap_style'] = 'xmr' + else: + page_data['swap_style'] = 'atomic' + try: page_data['amt_from'] = get_data_entry(form_data, 'amt_from') parsed_data['amt_from'] = inputAmount(page_data['amt_from'], ci_from) @@ -460,6 +466,12 @@ class HttpHandler(BaseHTTPRequestHandler): elif have_data_entry(form_data, 'lockseconds'): parsed_data['lock_seconds'] = int(get_data_entry(form_data, 'lockseconds')) + if have_data_entry(form_data, 'validhrs'): + page_data['validhrs'] = int(get_data_entry(form_data, 'validhrs')) + parsed_data['valid_for_seconds'] = page_data['validhrs'] * 60 * 60 + elif have_data_entry(form_data, 'valid_for_seconds'): + parsed_data['valid_for_seconds'] = int(get_data_entry(form_data, 'valid_for_seconds')) + page_data['autoaccept'] = True if have_data_entry(form_data, 'autoaccept') else False parsed_data['autoaccept'] = page_data['autoaccept'] @@ -499,7 +511,7 @@ class HttpHandler(BaseHTTPRequestHandler): swap_client = self.server.swap_client swap_type = SwapTypes.SELLER_FIRST - if parsed_data['coin_to'] == Coins.XMR: + if parsed_data['coin_to'] in (Coins.XMR, Coins.PART_ANON): swap_type = SwapTypes.XMR_SWAP if swap_client.coin_clients[parsed_data['coin_from']]['use_csv'] and swap_client.coin_clients[parsed_data['coin_to']]['use_csv']: @@ -522,6 +534,8 @@ class HttpHandler(BaseHTTPRequestHandler): extra_options['to_fee_multiplier_percent'] = parsed_data['fee_to_extra'] if 'to_fee_override' in parsed_data: extra_options['to_fee_override'] = parsed_data['to_fee_override'] + if 'valid_for_seconds' in parsed_data: + extra_options['valid_for_seconds'] = parsed_data['valid_for_seconds'] offer_id = swap_client.postOffer( parsed_data['coin_from'], @@ -552,6 +566,7 @@ class HttpHandler(BaseHTTPRequestHandler): # Set defaults 'fee_from_conf': 2, 'fee_to_conf': 2, + 'validhrs': 1, 'lockhrs': 32, 'autoaccept': True } diff --git a/basicswap/interface_btc.py b/basicswap/interface_btc.py index 76b1722..b068aa8 100644 --- a/basicswap/interface_btc.py +++ b/basicswap/interface_btc.py @@ -34,7 +34,7 @@ from coincurve.ecdsaotves import ( ecdsaotves_rec_enc_key) from .ecc_util import ( - G, ep, + ep, pointToCPK, CPKToPoint, getSecretInt, b2h, i2b, b2i, i2h) @@ -92,23 +92,23 @@ class BTCInterface(CoinInterface): return COIN @staticmethod - def exp(): + def exp() -> int: return 8 @staticmethod - def nbk(): + def nbk() -> int: return 32 @staticmethod - def nbK(): # No. of bytes requires to encode a public key + def nbK() -> int: # No. of bytes requires to encode a public key return 33 @staticmethod - def witnessScaleFactor(): + def witnessScaleFactor() -> int: return 4 @staticmethod - def txVersion(): + def txVersion() -> int: return 2 @staticmethod @@ -119,11 +119,11 @@ class BTCInterface(CoinInterface): return rv @staticmethod - def compareFeeRates(a, b): + def compareFeeRates(a, b) -> bool: return abs(a - b) < 20 @staticmethod - def xmr_swap_alock_spend_tx_vsize(): + def xmr_swap_alock_spend_tx_vsize() -> int: return 147 @staticmethod @@ -921,7 +921,7 @@ class BTCInterface(CoinInterface): rv = pubkey.verify_compact(sig, message_hash, hasher=None) assert(rv is True) - def verifyMessage(self, address, message, signature, message_magic=None): + def verifyMessage(self, address, message, signature, message_magic=None) -> bool: if message_magic is None: message_magic = chainparams[self.coin_type()]['message_magic'] diff --git a/basicswap/interface_part.py b/basicswap/interface_part.py index 6a68661..100ac90 100644 --- a/basicswap/interface_part.py +++ b/basicswap/interface_part.py @@ -15,8 +15,9 @@ from .contrib.test_framework.script import ( OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG ) +from .util import encodeStealthAddress +from .chainparams import Coins, chainparams from .interface_btc import BTCInterface -from .chainparams import Coins class BalanceTypes(IntEnum): @@ -35,22 +36,22 @@ class PARTInterface(BTCInterface): return BalanceTypes.PLAIN @staticmethod - def witnessScaleFactor(): + def witnessScaleFactor() -> int: return 2 @staticmethod - def txVersion(): + def txVersion() -> int: return 0xa0 @staticmethod - def xmr_swap_alock_spend_tx_vsize(): + def xmr_swap_alock_spend_tx_vsize() -> int: return 213 @staticmethod def txoType(): return CTxOutPart - def setDefaults(self): + def setDefaults(self) -> None: super().setDefaults() self._anon_tx_ring_size = 8 # TODO: Make option @@ -86,6 +87,11 @@ class PARTInterface(BTCInterface): def getScriptForPubkeyHash(self, pkh): return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG]) + def formatStealthAddress(self, scan_pubkey, spend_pubkey): + prefix_byte = chainparams[self.coin_type()][self._network]['stealth_key_prefix'] + + return encodeStealthAddress(prefix_byte, scan_pubkey, spend_pubkey) + class PARTInterfaceBlind(PARTInterface): @staticmethod @@ -99,7 +105,17 @@ class PARTInterfaceAnon(PARTInterface): return BalanceTypes.ANON def publishBLockTx(self, Kbv, Kbs, output_amount, feerate): - raise ValueError('TODO - new core release') + sx_addr = self.formatStealthAddress(Kbv, Kbs) + self._log.debug('sx_addr: {}'.format(sx_addr)) + + # TODO: Fund from other balances + params = ['anon', 'anon', + [{'address': sx_addr, 'amount': self.format_amount(output_amount)}, ], + '', '', self._anon_tx_ring_size, 1, False, + {'conf_target': self._conf_target, 'blind_watchonly_visible': True}] + + txid = self.rpc_callback('sendtypeto', params) + return bytes.fromhex(txid) def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height): raise ValueError('TODO - new core release') diff --git a/basicswap/interface_xmr.py b/basicswap/interface_xmr.py index 56b0f42..c47d831 100644 --- a/basicswap/interface_xmr.py +++ b/basicswap/interface_xmr.py @@ -47,15 +47,15 @@ class XMRInterface(CoinInterface): return XMR_COIN @staticmethod - def exp(): + def exp() -> int: return 12 @staticmethod - def nbk(): + def nbk() -> int: return 32 @staticmethod - def nbK(): # No. of bytes requires to encode a public key + def nbK() -> int: # No. of bytes requires to encode a public key return 32 def __init__(self, coin_settings, network, swap_client=None): diff --git a/basicswap/templates/offer_confirm.html b/basicswap/templates/offer_confirm.html index c0801cb..068141c 100644 --- a/basicswap/templates/offer_confirm.html +++ b/basicswap/templates/offer_confirm.html @@ -54,7 +54,8 @@ {% endif %} -