diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index ed74ebe..5333926 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -33,6 +33,7 @@ from .interface.ltc import LTCInterface from .interface.nmc import NMCInterface from .interface.xmr import XMRInterface from .interface.pivx import PIVXInterface +from .interface.dash import DASHInterface from .interface.passthrough_btc import PassthroughBTCInterface from . import __version__ @@ -527,6 +528,8 @@ class BasicSwap(BaseApp): return xmr_i elif coin == Coins.PIVX: return PIVXInterface(self.coin_clients[coin], self.chain, self) + elif coin == Coins.DASH: + return DASHInterface(self.coin_clients[coin], self.chain, self) else: raise ValueError('Unknown coin type') @@ -545,7 +548,7 @@ class BasicSwap(BaseApp): authcookiepath = os.path.join(self.getChainDatadirPath(coin), '.cookie') pidfilename = cc['name'] - if cc['name'] in ('bitcoin', 'litecoin', 'namecoin'): + if cc['name'] in ('bitcoin', 'litecoin', 'namecoin', 'dash'): pidfilename += 'd' pidfilepath = os.path.join(self.getChainDatadirPath(coin), pidfilename + '.pid') diff --git a/basicswap/chainparams.py b/basicswap/chainparams.py index 1fa4e05..b85e44e 100644 --- a/basicswap/chainparams.py +++ b/basicswap/chainparams.py @@ -29,6 +29,7 @@ class Coins(IntEnum): # ZANO = 9 # NDAU = 10 PIVX = 11 + DASH = 12 chainparams = { @@ -247,6 +248,45 @@ chainparams = { 'max_amount': 100000 * COIN, } }, + Coins.DASH: { + 'name': 'dash', + 'ticker': 'DASH', + 'message_magic': 'DarkCoin Signed Message:\n', + 'blocks_target': 60 * 2.5, + 'decimal_places': 8, + 'has_csv': True, + 'has_segwit': False, + 'mainnet': { + 'rpcport': 9998, + 'pubkey_address': 76, + 'script_address': 16, + 'key_prefix': 204, + 'hrp': '', + 'bip44': 5, + 'min_amount': 1000, + 'max_amount': 100000 * COIN, + }, + 'testnet': { + 'rpcport': 19998, + 'pubkey_address': 140, + 'script_address': 19, + 'key_prefix': 239, + 'hrp': '', + 'bip44': 1, + 'min_amount': 1000, + 'max_amount': 100000 * COIN, + }, + 'regtest': { + 'rpcport': 18332, + 'pubkey_address': 140, + 'script_address': 19, + 'key_prefix': 239, + 'hrp': '', + 'bip44': 1, + 'min_amount': 1000, + 'max_amount': 100000 * COIN, + } + } } ticker_map = {} diff --git a/basicswap/config.py b/basicswap/config.py index c16d18f..61010e5 100644 --- a/basicswap/config.py +++ b/basicswap/config.py @@ -41,3 +41,8 @@ PIVX_BINDIR = os.path.expanduser(os.getenv('PIVX_BINDIR', os.path.join(DEFAULT_T PIVXD = os.getenv('PIVXD', 'pivxd' + bin_suffix) PIVX_CLI = os.getenv('PIVX_CLI', 'pivx-cli' + bin_suffix) PIVX_TX = os.getenv('PIVX_TX', 'pivx-tx' + bin_suffix) + +DASH_BINDIR = os.path.expanduser(os.getenv('DASH_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'dash'))) +DASHD = os.getenv('DASHD', 'dashd' + bin_suffix) +DASH_CLI = os.getenv('DASH_CLI', 'dash-cli' + bin_suffix) +DASH_TX = os.getenv('DASH_TX', 'dash-tx' + bin_suffix) diff --git a/basicswap/interface/dash.py b/basicswap/interface/dash.py new file mode 100644 index 0000000..117d8e4 --- /dev/null +++ b/basicswap/interface/dash.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (c) 2022 tecnovert +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +from .btc import BTCInterface +from basicswap.chainparams import Coins + + +class DASHInterface(BTCInterface): + @staticmethod + def coin_type(): + return Coins.DASH + + def initialiseWallet(self, key): + raise ValueError('Load seed with with -hdseed daemon argument') diff --git a/bin/basicswap_prepare.py b/bin/basicswap_prepare.py index a6f910c..098674f 100755 --- a/bin/basicswap_prepare.py +++ b/bin/basicswap_prepare.py @@ -49,7 +49,10 @@ XMR_SITE_COMMIT = 'f093c0da2219d94e6bef5f3948ac61b4ecdcb95b' # Lock hashes.txt PIVX_VERSION = os.getenv('PIVX_VERSION', '5.4.99') PIVX_VERSION_TAG = os.getenv('PIVX_VERSION_TAG', '_scantxoutset') -# version, version tag eg. "rc1", signers +DASH_VERSION = os.getenv('DASH_VERSION', '18.1.0') +DASH_VERSION_TAG = os.getenv('DASH_VERSION_TAG', '') + + known_coins = { 'particl': (PARTICL_VERSION, PARTICL_VERSION_TAG, ('tecnovert',)), 'litecoin': (LITECOIN_VERSION, LITECOIN_VERSION_TAG, ('davidburkett38',)), @@ -57,6 +60,7 @@ known_coins = { 'namecoin': ('0.18.0', '', ('JeremyRand',)), 'monero': (MONERO_VERSION, MONERO_VERSION_TAG, ('binaryfate',)), 'pivx': (PIVX_VERSION, PIVX_VERSION_TAG, ('tecnovert',)), + 'dash': (DASH_VERSION, DASH_VERSION_TAG, ('pasta',)), } expected_key_ids = { @@ -67,6 +71,7 @@ expected_key_ids = { 'binaryfate': ('F0AF4D462A0BDF92',), 'davidburkett38': ('3620E9D387E55666',), 'fuzzbawls': ('3BDCDA2D87A881D9',), + 'pasta': ('52527BEDABE87984',), } if platform.system() == 'Darwin': @@ -125,6 +130,12 @@ PIVX_ONION_PORT = int(os.getenv('PIVX_ONION_PORT', 51472)) # nDefaultPort PIVX_RPC_USER = os.getenv('PIVX_RPC_USER', '') PIVX_RPC_PWD = os.getenv('PIVX_RPC_PWD', '') +DASH_RPC_HOST = os.getenv('DASH_RPC_HOST', '127.0.0.1') +DASH_RPC_PORT = int(os.getenv('DASH_RPC_PORT', 9998)) +DASH_ONION_PORT = int(os.getenv('DASH_ONION_PORT', 9999)) # nDefaultPort +DASH_RPC_USER = os.getenv('DASH_RPC_USER', '') +DASH_RPC_PWD = os.getenv('DASH_RPC_PWD', '') + TOR_PROXY_HOST = os.getenv('TOR_PROXY_HOST', '127.0.0.1') TOR_PROXY_PORT = int(os.getenv('TOR_PROXY_PORT', 9050)) TOR_CONTROL_PORT = int(os.getenv('TOR_CONTROL_PORT', 9051)) @@ -296,6 +307,7 @@ def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts= bins = [coin + 'd', coin + '-cli', coin + '-tx'] versions = version.split('.') + dir_name = 'dashcore' if coin == 'dash' else coin if int(versions[0]) >= 22 or int(versions[1]) >= 19: bins.append(coin + '-wallet') if 'win32' in BIN_ARCH or 'win64' in BIN_ARCH: @@ -305,7 +317,7 @@ def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts= out_path = os.path.join(bin_dir, b) if (not os.path.exists(out_path)) or extract_core_overwrite: with open(out_path, 'wb') as fout: - fout.write(fz.read('{}-{}/bin/{}'.format(coin, version, b))) + fout.write(fz.read('{}-{}/bin/{}'.format(dir_name, version, b))) try: os.chmod(out_path, stat.S_IRWXU | stat.S_IXGRP | stat.S_IXOTH) except Exception as e: @@ -317,9 +329,9 @@ def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts= if not os.path.exists(out_path) or extract_core_overwrite: if coin == 'pivx': - filename = '{}-{}/bin/{}'.format(coin, version, b) + filename = '{}-{}/bin/{}'.format(dir_name, version, b) else: - filename = '{}-{}/bin/{}'.format(coin, version + version_tag, b) + filename = '{}-{}/bin/{}'.format(dir_name, version + version_tag, b) with open(out_path, 'wb') as fout, ft.extractfile(filename) as fi: fout.write(fi.read()) @@ -394,6 +406,11 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}): release_url = 'https://github.com/tecnovert/particl-core/releases/download/v{}/{}'.format(version + version_tag, release_filename) assert_filename = 'pivx-{}-6.0-build.assert'.format(os_name) assert_url = 'https://raw.githubusercontent.com/tecnovert/gitian.sigs/pivx/5.4.99_scantxoutset-{}/tecnovert/{}'.format(os_dir_name, assert_filename) + elif coin == 'dash': + release_filename = '{}-{}-{}{}.{}'.format('dashcore', version + version_tag, BIN_ARCH, filename_extra, FILE_EXT) + release_url = 'https://github.com/dashpay/dash/releases/download/v{}/{}'.format(version + version_tag, release_filename) + assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, major_version) + assert_url = 'https://raw.githubusercontent.com/dashpay/gitian.sigs/master/%s-%s/%s/%s' % (version + version_tag, os_dir_name, signing_key_name, assert_filename) else: raise ValueError('Unknown coin') @@ -468,10 +485,12 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}): filename = '{}_{}.pgp'.format('particl', signing_key_name) else: filename = '{}_{}.pgp'.format(coin, signing_key_name) - pubkeyurls = ( + pubkeyurls = [ 'https://raw.githubusercontent.com/tecnovert/basicswap/master/pgp/keys/' + filename, 'https://gitlab.com/particl/basicswap/-/raw/master/pgp/keys/' + filename, - ) + ] + if coin == 'dash': + pubkeyurls.append('https://raw.githubusercontent.com/dashpay/dash/master/contrib/gitian-keys/pasta.pgp') for url in pubkeyurls: try: logger.info('Importing public key from url: ' + url) @@ -631,6 +650,11 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}): fp.write(f'paramsdir={params_dir}\n') if PIVX_RPC_USER != '': fp.write('rpcauth={}:{}${}\n'.format(PIVX_RPC_USER, salt, password_to_hmac(salt, PIVX_RPC_PWD))) + elif coin == 'dash': + fp.write('prune=4000\n') + fp.write('fallbackfee=0.0002\n') + if DASH_RPC_USER != '': + fp.write('rpcauth={}:{}${}\n'.format(DASH_RPC_USER, salt, password_to_hmac(salt, DASH_RPC_PWD))) else: logger.warning('Unknown coin %s', coin) @@ -846,11 +870,9 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings, with open(os.path.join(data_dir, 'basicswap.log'), 'a') as fp: swap_client = BasicSwap(fp, data_dir, settings, chain) - start_daemons = {c for c in with_coins} - if 'particl' not in with_coins: - # Particl must be running to initialise a wallet in addcoin mode - start_daemons.add('particl') - + # Always start Particl, it must be running to initialise a wallet in addcoin mode + # Particl must be loaded first as subsequent coins are initialised from the Particl mnemonic + start_daemons = ['particl', ] + [c for c in with_coins if c != 'particl'] for coin_name in start_daemons: coin_settings = settings['chainclients'][coin_name] c = swap_client.getCoinIdFromName(coin_name) @@ -862,6 +884,10 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings, if coin_settings['manage_daemon']: filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '') coin_args = ['-nofindpeers', '-nostaking'] if c == Coins.PART else [] + + if c == Coins.DASH: + coin_args += ['-hdseed={}'.format(swap_client.getWalletKey(Coins.DASH, 1).hex())] + daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args + coin_args)) swap_client.setDaemonPID(c, daemons[-1].pid) swap_client.setCoinRunParams(c) @@ -875,15 +901,15 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings, logger.info('Creating wallet.dat for {}.'.format(coin_name.capitalize())) swap_client.callcoinrpc(c, 'createwallet', ['wallet.dat']) - if 'particl' in with_coins: - logger.info('Loading Particl mnemonic') - if particl_wallet_mnemonic is None: - particl_wallet_mnemonic = swap_client.callcoinrpc(Coins.PART, 'mnemonic', ['new'])['mnemonic'] - swap_client.callcoinrpc(Coins.PART, 'extkeyimportmaster', [particl_wallet_mnemonic]) + if 'particl' in with_coins and c == Coins.PART: + logger.info('Loading Particl mnemonic') + if particl_wallet_mnemonic is None: + particl_wallet_mnemonic = swap_client.callcoinrpc(Coins.PART, 'mnemonic', ['new'])['mnemonic'] + swap_client.callcoinrpc(Coins.PART, 'extkeyimportmaster', [particl_wallet_mnemonic]) for coin_name in with_coins: c = swap_client.getCoinIdFromName(coin_name) - if c == Coins.PART: + if c in (Coins.PART, Coins.DASH): continue swap_client.waitForDaemonRPC(c) swap_client.initialiseWallet(c) @@ -1146,6 +1172,21 @@ def main(): 'conf_target': 2, 'core_version_group': 20, 'chain_lookups': 'local', + }, + 'dash': { + 'connection_type': 'rpc' if 'dash' in with_coins else 'none', + 'manage_daemon': True if ('dash' in with_coins and PIVX_RPC_HOST == '127.0.0.1') else False, + 'rpchost': DASH_RPC_HOST, + 'rpcport': DASH_RPC_PORT + port_offset, + 'onionport': DASH_ONION_PORT + port_offset, + 'datadir': os.getenv('DASH_DATA_DIR', os.path.join(data_dir, 'dash')), + 'bindir': os.path.join(bin_dir, 'dash'), + 'use_segwit': False, + 'use_csv': True, + 'blocks_confirmed': 1, + 'conf_target': 2, + 'core_version_group': 18, + 'chain_lookups': 'local', } } @@ -1161,6 +1202,9 @@ def main(): if PIVX_RPC_USER != '': chainclients['pivx']['rpcuser'] = PIVX_RPC_USER chainclients['pivx']['rpcpassword'] = PIVX_RPC_PWD + if DASH_RPC_USER != '': + chainclients['dash']['rpcuser'] = DASH_RPC_USER + chainclients['dash']['rpcpassword'] = DASH_RPC_PWD chainclients['monero']['walletsdir'] = os.getenv('XMR_WALLETS_DIR', chainclients['monero']['datadir']) diff --git a/pgp/keys/dash_pasta.pgp b/pgp/keys/dash_pasta.pgp new file mode 100644 index 0000000..f53bcec --- /dev/null +++ b/pgp/keys/dash_pasta.pgp @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +oPei +=42dS +-----END PGP PUBLIC KEY BLOCK-----