basicswap-prepare can enable and disable tor config.
This commit is contained in:
		
							parent
							
								
									cf797afae9
								
							
						
					
					
						commit
						d1e015962c
					
				@ -38,23 +38,27 @@ from . import __version__
 | 
			
		||||
from .rpc_xmr import make_xmr_rpc2_func
 | 
			
		||||
from .util import (
 | 
			
		||||
    TemporaryError,
 | 
			
		||||
    pubkeyToAddress,
 | 
			
		||||
    format_amount,
 | 
			
		||||
    format_timestamp,
 | 
			
		||||
    encodeAddress,
 | 
			
		||||
    decodeAddress,
 | 
			
		||||
    DeserialiseNum,
 | 
			
		||||
    decodeWif,
 | 
			
		||||
    toWIF,
 | 
			
		||||
    getKeyID,
 | 
			
		||||
    make_int,
 | 
			
		||||
    getP2SHScriptForHash,
 | 
			
		||||
    getP2WSH,
 | 
			
		||||
    ensure,
 | 
			
		||||
)
 | 
			
		||||
from .util.script import (
 | 
			
		||||
    getP2WSH,
 | 
			
		||||
    getP2SHScriptForHash,
 | 
			
		||||
)
 | 
			
		||||
from .util.address import (
 | 
			
		||||
    toWIF,
 | 
			
		||||
    getKeyID,
 | 
			
		||||
    decodeWif,
 | 
			
		||||
    decodeAddress,
 | 
			
		||||
    encodeAddress,
 | 
			
		||||
    pubkeyToAddress,
 | 
			
		||||
)
 | 
			
		||||
from .chainparams import (
 | 
			
		||||
    chainparams,
 | 
			
		||||
    Coins,
 | 
			
		||||
    chainparams,
 | 
			
		||||
)
 | 
			
		||||
from .script import (
 | 
			
		||||
    OpCodes,
 | 
			
		||||
 | 
			
		||||
@ -8,9 +8,9 @@
 | 
			
		||||
import struct
 | 
			
		||||
import hashlib
 | 
			
		||||
from enum import IntEnum, auto
 | 
			
		||||
from .util import (
 | 
			
		||||
    encodeAddress,
 | 
			
		||||
from .util.address import (
 | 
			
		||||
    decodeAddress,
 | 
			
		||||
    encodeAddress,
 | 
			
		||||
)
 | 
			
		||||
from .chainparams import (
 | 
			
		||||
    chainparams,
 | 
			
		||||
 | 
			
		||||
@ -16,17 +16,26 @@ from basicswap.contrib.test_framework import segwit_addr
 | 
			
		||||
 | 
			
		||||
from .util import (
 | 
			
		||||
    dumpj,
 | 
			
		||||
    toWIF,
 | 
			
		||||
    ensure,
 | 
			
		||||
    make_int,
 | 
			
		||||
    b2h, i2b, b2i, i2h)
 | 
			
		||||
from .util.ecc import (
 | 
			
		||||
    ep,
 | 
			
		||||
    pointToCPK, CPKToPoint,
 | 
			
		||||
    getSecretInt)
 | 
			
		||||
from .util.script import (
 | 
			
		||||
    decodeScriptNum,
 | 
			
		||||
    getCompactSizeLen,
 | 
			
		||||
    SerialiseNumCompact,
 | 
			
		||||
    getWitnessElementLen,
 | 
			
		||||
)
 | 
			
		||||
from .util.address import (
 | 
			
		||||
    toWIF,
 | 
			
		||||
    b58encode,
 | 
			
		||||
    decodeWif,
 | 
			
		||||
    decodeAddress,
 | 
			
		||||
    decodeScriptNum,
 | 
			
		||||
    pubkeyToAddress,
 | 
			
		||||
    getCompactSizeLen,
 | 
			
		||||
    SerialiseNumCompact,
 | 
			
		||||
    getWitnessElementLen)
 | 
			
		||||
)
 | 
			
		||||
from coincurve.keys import (
 | 
			
		||||
    PrivateKey,
 | 
			
		||||
    PublicKey)
 | 
			
		||||
@ -38,12 +47,6 @@ from coincurve.ecdsaotves import (
 | 
			
		||||
    ecdsaotves_dec_sig,
 | 
			
		||||
    ecdsaotves_rec_enc_key)
 | 
			
		||||
 | 
			
		||||
from .ecc_util import (
 | 
			
		||||
    ep,
 | 
			
		||||
    pointToCPK, CPKToPoint,
 | 
			
		||||
    getSecretInt,
 | 
			
		||||
    b2h, i2b, b2i, i2h)
 | 
			
		||||
 | 
			
		||||
from .contrib.test_framework.messages import (
 | 
			
		||||
    COIN,
 | 
			
		||||
    COutPoint,
 | 
			
		||||
 | 
			
		||||
@ -16,17 +16,20 @@ from .contrib.test_framework.script import (
 | 
			
		||||
    OP_0,
 | 
			
		||||
    OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG
 | 
			
		||||
)
 | 
			
		||||
from .ecc_util import i2b
 | 
			
		||||
 | 
			
		||||
from .util import (
 | 
			
		||||
    toWIF,
 | 
			
		||||
    i2b,
 | 
			
		||||
    ensure,
 | 
			
		||||
    make_int,
 | 
			
		||||
    getP2WSH,
 | 
			
		||||
    TemporaryError,
 | 
			
		||||
)
 | 
			
		||||
from .util.script import (
 | 
			
		||||
    getP2WSH,
 | 
			
		||||
    getCompactSizeLen,
 | 
			
		||||
    encodeStealthAddress,
 | 
			
		||||
    getWitnessElementLen)
 | 
			
		||||
    getWitnessElementLen,
 | 
			
		||||
)
 | 
			
		||||
from .util.address import (
 | 
			
		||||
    toWIF,
 | 
			
		||||
    encodeStealthAddress)
 | 
			
		||||
from .chainparams import Coins, chainparams
 | 
			
		||||
from .interface_btc import BTCInterface
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -31,7 +31,7 @@ from .rpc_xmr import (
 | 
			
		||||
    make_xmr_rpc_func,
 | 
			
		||||
    make_xmr_rpc2_func,
 | 
			
		||||
    make_xmr_wallet_rpc_func)
 | 
			
		||||
from .ecc_util import (
 | 
			
		||||
from .util import (
 | 
			
		||||
    b2i, b2h)
 | 
			
		||||
from .chainparams import CoinInterface, Coins
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,350 +0,0 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
# Copyright (c) 2018-2021 tecnovert
 | 
			
		||||
# Distributed under the MIT software license, see the accompanying
 | 
			
		||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
import time
 | 
			
		||||
import struct
 | 
			
		||||
import decimal
 | 
			
		||||
import hashlib
 | 
			
		||||
 | 
			
		||||
from .script import OpCodes
 | 
			
		||||
from .contrib.segwit_addr import bech32_decode, convertbits, bech32_encode
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
COIN = 100000000
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
decimal_ctx = decimal.Context()
 | 
			
		||||
decimal_ctx.prec = 20
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TemporaryError(ValueError):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def ensure(v, err_string):
 | 
			
		||||
    if not v:
 | 
			
		||||
        raise ValueError(err_string)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def toBool(s) -> bool:
 | 
			
		||||
    return s.lower() in ["1", "true"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def jsonDecimal(obj):
 | 
			
		||||
    if isinstance(obj, decimal.Decimal):
 | 
			
		||||
        return str(obj)
 | 
			
		||||
    raise TypeError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def dumpj(jin, indent=4):
 | 
			
		||||
    return json.dumps(jin, indent=indent, default=jsonDecimal)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def dumpje(jin):
 | 
			
		||||
    return json.dumps(jin, default=jsonDecimal).replace('"', '\\"')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def b58decode(v, length=None):
 | 
			
		||||
    long_value = 0
 | 
			
		||||
    for (i, c) in enumerate(v[::-1]):
 | 
			
		||||
        ofs = __b58chars.find(c)
 | 
			
		||||
        if ofs < 0:
 | 
			
		||||
            return None
 | 
			
		||||
        long_value += ofs * (58**i)
 | 
			
		||||
    result = bytes()
 | 
			
		||||
    while long_value >= 256:
 | 
			
		||||
        div, mod = divmod(long_value, 256)
 | 
			
		||||
        result = bytes((mod,)) + result
 | 
			
		||||
        long_value = div
 | 
			
		||||
    result = bytes((long_value,)) + result
 | 
			
		||||
    nPad = 0
 | 
			
		||||
    for c in v:
 | 
			
		||||
        if c == __b58chars[0]:
 | 
			
		||||
            nPad += 1
 | 
			
		||||
        else:
 | 
			
		||||
            break
 | 
			
		||||
    pad = bytes((0,)) * nPad
 | 
			
		||||
    result = pad + result
 | 
			
		||||
    if length is not None and len(result) != length:
 | 
			
		||||
        return None
 | 
			
		||||
    return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def b58encode(v):
 | 
			
		||||
    long_value = 0
 | 
			
		||||
    for (i, c) in enumerate(v[::-1]):
 | 
			
		||||
        long_value += (256**i) * c
 | 
			
		||||
 | 
			
		||||
    result = ''
 | 
			
		||||
    while long_value >= 58:
 | 
			
		||||
        div, mod = divmod(long_value, 58)
 | 
			
		||||
        result = __b58chars[mod] + result
 | 
			
		||||
        long_value = div
 | 
			
		||||
    result = __b58chars[long_value] + result
 | 
			
		||||
 | 
			
		||||
    # leading 0-bytes in the input become leading-1s
 | 
			
		||||
    nPad = 0
 | 
			
		||||
    for c in v:
 | 
			
		||||
        if c == 0:
 | 
			
		||||
            nPad += 1
 | 
			
		||||
        else:
 | 
			
		||||
            break
 | 
			
		||||
    return (__b58chars[0] * nPad) + result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def decodeWif(encoded_key):
 | 
			
		||||
    key = b58decode(encoded_key)[1:-4]
 | 
			
		||||
    if len(key) == 33:
 | 
			
		||||
        return key[:-1]
 | 
			
		||||
    return key
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def toWIF(prefix_byte, b, compressed=True):
 | 
			
		||||
    b = bytes((prefix_byte,)) + b
 | 
			
		||||
    if compressed:
 | 
			
		||||
        b += bytes((0x01,))
 | 
			
		||||
    b += hashlib.sha256(hashlib.sha256(b).digest()).digest()[:4]
 | 
			
		||||
    return b58encode(b)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def bech32Decode(hrp, addr):
 | 
			
		||||
    hrpgot, data = bech32_decode(addr)
 | 
			
		||||
    if hrpgot != hrp:
 | 
			
		||||
        return None
 | 
			
		||||
    decoded = convertbits(data, 5, 8, False)
 | 
			
		||||
    if decoded is None or len(decoded) < 2 or len(decoded) > 40:
 | 
			
		||||
        return None
 | 
			
		||||
    return bytes(decoded)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def bech32Encode(hrp, data):
 | 
			
		||||
    ret = bech32_encode(hrp, convertbits(data, 8, 5))
 | 
			
		||||
    if bech32Decode(hrp, ret) is None:
 | 
			
		||||
        return None
 | 
			
		||||
    return ret
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def decodeAddress(address_str):
 | 
			
		||||
    b58_addr = b58decode(address_str)
 | 
			
		||||
    if b58_addr is not None:
 | 
			
		||||
        address = b58_addr[:-4]
 | 
			
		||||
        checksum = b58_addr[-4:]
 | 
			
		||||
        assert(hashlib.sha256(hashlib.sha256(address).digest()).digest()[:4] == checksum), 'Checksum mismatch'
 | 
			
		||||
        return b58_addr[:-4]
 | 
			
		||||
    return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def encodeAddress(address):
 | 
			
		||||
    checksum = hashlib.sha256(hashlib.sha256(address).digest()).digest()
 | 
			
		||||
    return b58encode(address + checksum[0:4])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getKeyID(bytes):
 | 
			
		||||
    data = hashlib.sha256(bytes).digest()
 | 
			
		||||
    return hashlib.new("ripemd160", data).digest()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def pubkeyToAddress(prefix, pubkey):
 | 
			
		||||
    return encodeAddress(bytes((prefix,)) + getKeyID(pubkey))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def SerialiseNum(n):
 | 
			
		||||
    if n == 0:
 | 
			
		||||
        return bytes((0x00,))
 | 
			
		||||
    if n > 0 and n <= 16:
 | 
			
		||||
        return bytes((0x50 + n,))
 | 
			
		||||
    rv = bytearray()
 | 
			
		||||
    neg = n < 0
 | 
			
		||||
    absvalue = -n if neg else n
 | 
			
		||||
    while(absvalue):
 | 
			
		||||
        rv.append(absvalue & 0xff)
 | 
			
		||||
        absvalue >>= 8
 | 
			
		||||
    if rv[-1] & 0x80:
 | 
			
		||||
        rv.append(0x80 if neg else 0)
 | 
			
		||||
    elif neg:
 | 
			
		||||
        rv[-1] |= 0x80
 | 
			
		||||
    return bytes((len(rv),)) + rv
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def DeserialiseNum(b, o=0) -> int:
 | 
			
		||||
    if b[o] == 0:
 | 
			
		||||
        return 0
 | 
			
		||||
    if b[o] > 0x50 and b[o] <= 0x50 + 16:
 | 
			
		||||
        return b[o] - 0x50
 | 
			
		||||
    v = 0
 | 
			
		||||
    nb = b[o]
 | 
			
		||||
    o += 1
 | 
			
		||||
    for i in range(0, nb):
 | 
			
		||||
        v |= b[o + i] << (8 * i)
 | 
			
		||||
    # If the input vector's most significant byte is 0x80, remove it from the result's msb and return a negative.
 | 
			
		||||
    if b[o + nb - 1] & 0x80:
 | 
			
		||||
        return -(v & ~(0x80 << (8 * (nb - 1))))
 | 
			
		||||
    return v
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def decodeScriptNum(script_bytes, o):
 | 
			
		||||
    v = 0
 | 
			
		||||
    num_len = script_bytes[o]
 | 
			
		||||
    if num_len >= OpCodes.OP_1 and num_len <= OpCodes.OP_16:
 | 
			
		||||
        return((num_len - OpCodes.OP_1) + 1, 1)
 | 
			
		||||
 | 
			
		||||
    if num_len > 4:
 | 
			
		||||
        raise ValueError('Bad scriptnum length')  # Max 4 bytes
 | 
			
		||||
    if num_len + o >= len(script_bytes):
 | 
			
		||||
        raise ValueError('Bad script length')
 | 
			
		||||
    o += 1
 | 
			
		||||
    for i in range(num_len):
 | 
			
		||||
        b = script_bytes[o + i]
 | 
			
		||||
        # Negative flag set in last byte, if num is positive and > 0x80 an extra 0x00 byte will be appended
 | 
			
		||||
        if i == num_len - 1 and b & 0x80:
 | 
			
		||||
            b &= (~(0x80) & 0xFF)
 | 
			
		||||
            v += int(b) << 8 * i
 | 
			
		||||
            v *= -1
 | 
			
		||||
        else:
 | 
			
		||||
            v += int(b) << 8 * i
 | 
			
		||||
    return(v, 1 + num_len)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getCompactSizeLen(v):
 | 
			
		||||
    # Compact Size
 | 
			
		||||
    if v < 253:
 | 
			
		||||
        return 1
 | 
			
		||||
    if v <= 0xffff:  # USHRT_MAX
 | 
			
		||||
        return 3
 | 
			
		||||
    if v <= 0xffffffff:  # UINT_MAX
 | 
			
		||||
        return 5
 | 
			
		||||
    if v <= 0xffffffffffffffff:  # UINT_MAX
 | 
			
		||||
        return 9
 | 
			
		||||
    raise ValueError('Value too large')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getWitnessElementLen(v):
 | 
			
		||||
    return getCompactSizeLen(v) + v
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def SerialiseNumCompact(v):
 | 
			
		||||
    if v < 253:
 | 
			
		||||
        return bytes((v,))
 | 
			
		||||
    if v <= 0xffff:  # USHRT_MAX
 | 
			
		||||
        return struct.pack("<BH", 253, v)
 | 
			
		||||
    if v <= 0xffffffff:  # UINT_MAX
 | 
			
		||||
        return struct.pack("<BI", 254, v)
 | 
			
		||||
    if v <= 0xffffffffffffffff:  # UINT_MAX
 | 
			
		||||
        return struct.pack("<BQ", 255, v)
 | 
			
		||||
    raise ValueError('Value too large')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def float_to_str(f):
 | 
			
		||||
    # stackoverflow.com/questions/38847690
 | 
			
		||||
    d1 = decimal_ctx.create_decimal(repr(f))
 | 
			
		||||
    return format(d1, 'f')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def make_int(v, scale=8, r=0):  # r = 0, no rounding, fail, r > 0 round up, r < 0 floor
 | 
			
		||||
    if type(v) == float:
 | 
			
		||||
        v = float_to_str(v)
 | 
			
		||||
    elif type(v) == int:
 | 
			
		||||
        return v * 10 ** scale
 | 
			
		||||
 | 
			
		||||
    sign = 1
 | 
			
		||||
    if v[0] == '-':
 | 
			
		||||
        v = v[1:]
 | 
			
		||||
        sign = -1
 | 
			
		||||
    ep = 10 ** scale
 | 
			
		||||
    have_dp = False
 | 
			
		||||
    rv = 0
 | 
			
		||||
    for c in v:
 | 
			
		||||
        if c == '.':
 | 
			
		||||
            rv *= ep
 | 
			
		||||
            have_dp = True
 | 
			
		||||
            continue
 | 
			
		||||
        if not c.isdigit():
 | 
			
		||||
            raise ValueError('Invalid char: ' + c)
 | 
			
		||||
        if have_dp:
 | 
			
		||||
            ep //= 10
 | 
			
		||||
            if ep <= 0:
 | 
			
		||||
                if r == 0:
 | 
			
		||||
                    raise ValueError('Mantissa too long')
 | 
			
		||||
                if r > 0:
 | 
			
		||||
                    # Round up
 | 
			
		||||
                    if int(c) > 4:
 | 
			
		||||
                        rv += 1
 | 
			
		||||
                break
 | 
			
		||||
            rv += ep * int(c)
 | 
			
		||||
        else:
 | 
			
		||||
            rv = rv * 10 + int(c)
 | 
			
		||||
    if not have_dp:
 | 
			
		||||
        rv *= ep
 | 
			
		||||
    return rv * sign
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_amount(amount, scale=8) -> bool:
 | 
			
		||||
    str_amount = float_to_str(amount) if type(amount) == float else str(amount)
 | 
			
		||||
    has_decimal = False
 | 
			
		||||
    for c in str_amount:
 | 
			
		||||
        if c == '.' and not has_decimal:
 | 
			
		||||
            has_decimal = True
 | 
			
		||||
            continue
 | 
			
		||||
        if not c.isdigit():
 | 
			
		||||
            raise ValueError('Invalid amount')
 | 
			
		||||
 | 
			
		||||
    ar = str_amount.split('.')
 | 
			
		||||
    if len(ar) > 1 and len(ar[1]) > scale:
 | 
			
		||||
        raise ValueError('Too many decimal places in amount {}'.format(str_amount))
 | 
			
		||||
    return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def format_amount(i, display_scale, scale=None):
 | 
			
		||||
    if not isinstance(i, int):
 | 
			
		||||
        raise ValueError('Amount must be an integer.')  # Raise error instead of converting as amounts should always be integers
 | 
			
		||||
    if scale is None:
 | 
			
		||||
        scale = display_scale
 | 
			
		||||
    ep = 10 ** scale
 | 
			
		||||
    n = abs(i)
 | 
			
		||||
    quotient = n // ep
 | 
			
		||||
    remainder = n % ep
 | 
			
		||||
    if display_scale != scale:
 | 
			
		||||
        remainder %= (10 ** display_scale)
 | 
			
		||||
    rv = '{}.{:0>{scale}}'.format(quotient, remainder, scale=display_scale)
 | 
			
		||||
    if i < 0:
 | 
			
		||||
        rv = '-' + rv
 | 
			
		||||
    return rv
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def format_timestamp(value, with_seconds=False):
 | 
			
		||||
    str_format = '%Y-%m-%d %H:%M'
 | 
			
		||||
    if with_seconds:
 | 
			
		||||
        str_format += ':%S'
 | 
			
		||||
    str_format += ' %Z'
 | 
			
		||||
    return time.strftime(str_format, time.localtime(value))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getP2SHScriptForHash(p2sh):
 | 
			
		||||
    return bytes((OpCodes.OP_HASH160, 0x14)) \
 | 
			
		||||
        + p2sh \
 | 
			
		||||
        + bytes((OpCodes.OP_EQUAL,))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getP2WSH(script):
 | 
			
		||||
    return bytes((OpCodes.OP_0, 0x20)) + hashlib.sha256(script).digest()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def encodeStealthAddress(prefix_byte, scan_pubkey, spend_pubkey):
 | 
			
		||||
    data = bytes((0x00,))
 | 
			
		||||
    data += scan_pubkey
 | 
			
		||||
    data += bytes((0x01,))
 | 
			
		||||
    data += spend_pubkey
 | 
			
		||||
    data += bytes((0x00,))  # number_signatures - unused
 | 
			
		||||
    data += bytes((0x00,))  # num prefix bits
 | 
			
		||||
 | 
			
		||||
    b = bytes((prefix_byte,)) + data
 | 
			
		||||
    b += hashlib.sha256(hashlib.sha256(b).digest()).digest()[:4]
 | 
			
		||||
    return b58encode(b)
 | 
			
		||||
							
								
								
									
										187
									
								
								basicswap/util/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								basicswap/util/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,187 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
# Copyright (c) 2018-2022 tecnovert
 | 
			
		||||
# Distributed under the MIT software license, see the accompanying
 | 
			
		||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
import time
 | 
			
		||||
import decimal
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
COIN = 100000000
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
decimal_ctx = decimal.Context()
 | 
			
		||||
decimal_ctx.prec = 20
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TemporaryError(ValueError):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def ensure(v, err_string):
 | 
			
		||||
    if not v:
 | 
			
		||||
        raise ValueError(err_string)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def toBool(s) -> bool:
 | 
			
		||||
    return s.lower() in ['1', 'true']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def jsonDecimal(obj):
 | 
			
		||||
    if isinstance(obj, decimal.Decimal):
 | 
			
		||||
        return str(obj)
 | 
			
		||||
    raise TypeError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def dumpj(jin, indent=4):
 | 
			
		||||
    return json.dumps(jin, indent=indent, default=jsonDecimal)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def dumpje(jin):
 | 
			
		||||
    return json.dumps(jin, default=jsonDecimal).replace('"', '\\"')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def SerialiseNum(n):
 | 
			
		||||
    if n == 0:
 | 
			
		||||
        return bytes((0x00,))
 | 
			
		||||
    if n > 0 and n <= 16:
 | 
			
		||||
        return bytes((0x50 + n,))
 | 
			
		||||
    rv = bytearray()
 | 
			
		||||
    neg = n < 0
 | 
			
		||||
    absvalue = -n if neg else n
 | 
			
		||||
    while(absvalue):
 | 
			
		||||
        rv.append(absvalue & 0xff)
 | 
			
		||||
        absvalue >>= 8
 | 
			
		||||
    if rv[-1] & 0x80:
 | 
			
		||||
        rv.append(0x80 if neg else 0)
 | 
			
		||||
    elif neg:
 | 
			
		||||
        rv[-1] |= 0x80
 | 
			
		||||
    return bytes((len(rv),)) + rv
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def DeserialiseNum(b, o=0) -> int:
 | 
			
		||||
    if b[o] == 0:
 | 
			
		||||
        return 0
 | 
			
		||||
    if b[o] > 0x50 and b[o] <= 0x50 + 16:
 | 
			
		||||
        return b[o] - 0x50
 | 
			
		||||
    v = 0
 | 
			
		||||
    nb = b[o]
 | 
			
		||||
    o += 1
 | 
			
		||||
    for i in range(0, nb):
 | 
			
		||||
        v |= b[o + i] << (8 * i)
 | 
			
		||||
    # If the input vector's most significant byte is 0x80, remove it from the result's msb and return a negative.
 | 
			
		||||
    if b[o + nb - 1] & 0x80:
 | 
			
		||||
        return -(v & ~(0x80 << (8 * (nb - 1))))
 | 
			
		||||
    return v
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def float_to_str(f):
 | 
			
		||||
    # stackoverflow.com/questions/38847690
 | 
			
		||||
    d1 = decimal_ctx.create_decimal(repr(f))
 | 
			
		||||
    return format(d1, 'f')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def make_int(v, scale=8, r=0):  # r = 0, no rounding, fail, r > 0 round up, r < 0 floor
 | 
			
		||||
    if type(v) == float:
 | 
			
		||||
        v = float_to_str(v)
 | 
			
		||||
    elif type(v) == int:
 | 
			
		||||
        return v * 10 ** scale
 | 
			
		||||
 | 
			
		||||
    sign = 1
 | 
			
		||||
    if v[0] == '-':
 | 
			
		||||
        v = v[1:]
 | 
			
		||||
        sign = -1
 | 
			
		||||
    ep = 10 ** scale
 | 
			
		||||
    have_dp = False
 | 
			
		||||
    rv = 0
 | 
			
		||||
    for c in v:
 | 
			
		||||
        if c == '.':
 | 
			
		||||
            rv *= ep
 | 
			
		||||
            have_dp = True
 | 
			
		||||
            continue
 | 
			
		||||
        if not c.isdigit():
 | 
			
		||||
            raise ValueError('Invalid char: ' + c)
 | 
			
		||||
        if have_dp:
 | 
			
		||||
            ep //= 10
 | 
			
		||||
            if ep <= 0:
 | 
			
		||||
                if r == 0:
 | 
			
		||||
                    raise ValueError('Mantissa too long')
 | 
			
		||||
                if r > 0:
 | 
			
		||||
                    # Round up
 | 
			
		||||
                    if int(c) > 4:
 | 
			
		||||
                        rv += 1
 | 
			
		||||
                break
 | 
			
		||||
            rv += ep * int(c)
 | 
			
		||||
        else:
 | 
			
		||||
            rv = rv * 10 + int(c)
 | 
			
		||||
    if not have_dp:
 | 
			
		||||
        rv *= ep
 | 
			
		||||
    return rv * sign
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_amount(amount, scale=8) -> bool:
 | 
			
		||||
    str_amount = float_to_str(amount) if type(amount) == float else str(amount)
 | 
			
		||||
    has_decimal = False
 | 
			
		||||
    for c in str_amount:
 | 
			
		||||
        if c == '.' and not has_decimal:
 | 
			
		||||
            has_decimal = True
 | 
			
		||||
            continue
 | 
			
		||||
        if not c.isdigit():
 | 
			
		||||
            raise ValueError('Invalid amount')
 | 
			
		||||
 | 
			
		||||
    ar = str_amount.split('.')
 | 
			
		||||
    if len(ar) > 1 and len(ar[1]) > scale:
 | 
			
		||||
        raise ValueError('Too many decimal places in amount {}'.format(str_amount))
 | 
			
		||||
    return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def format_amount(i, display_scale, scale=None):
 | 
			
		||||
    if not isinstance(i, int):
 | 
			
		||||
        raise ValueError('Amount must be an integer.')  # Raise error instead of converting as amounts should always be integers
 | 
			
		||||
    if scale is None:
 | 
			
		||||
        scale = display_scale
 | 
			
		||||
    ep = 10 ** scale
 | 
			
		||||
    n = abs(i)
 | 
			
		||||
    quotient = n // ep
 | 
			
		||||
    remainder = n % ep
 | 
			
		||||
    if display_scale != scale:
 | 
			
		||||
        remainder %= (10 ** display_scale)
 | 
			
		||||
    rv = '{}.{:0>{scale}}'.format(quotient, remainder, scale=display_scale)
 | 
			
		||||
    if i < 0:
 | 
			
		||||
        rv = '-' + rv
 | 
			
		||||
    return rv
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def format_timestamp(value, with_seconds=False):
 | 
			
		||||
    str_format = '%Y-%m-%d %H:%M'
 | 
			
		||||
    if with_seconds:
 | 
			
		||||
        str_format += ':%S'
 | 
			
		||||
    str_format += ' %Z'
 | 
			
		||||
    return time.strftime(str_format, time.localtime(value))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def b2i(b) -> int:
 | 
			
		||||
    # bytes32ToInt
 | 
			
		||||
    return int.from_bytes(b, byteorder='big')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def i2b(i: int) -> bytes:
 | 
			
		||||
    # intToBytes32
 | 
			
		||||
    return i.to_bytes(32, byteorder='big')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def b2h(b: bytes) -> str:
 | 
			
		||||
    return b.hex()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def h2b(h: str) -> bytes:
 | 
			
		||||
    if h.startswith('0x'):
 | 
			
		||||
        h = h[2:]
 | 
			
		||||
    return bytes.fromhex(h)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def i2h(x):
 | 
			
		||||
    return b2h(i2b(x))
 | 
			
		||||
							
								
								
									
										128
									
								
								basicswap/util/address.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								basicswap/util/address.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,128 @@
 | 
			
		||||
# -*- 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.
 | 
			
		||||
 | 
			
		||||
import hashlib
 | 
			
		||||
from basicswap.contrib.segwit_addr import bech32_decode, convertbits, bech32_encode
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def b58decode(v, length=None):
 | 
			
		||||
    long_value = 0
 | 
			
		||||
    for (i, c) in enumerate(v[::-1]):
 | 
			
		||||
        ofs = __b58chars.find(c)
 | 
			
		||||
        if ofs < 0:
 | 
			
		||||
            return None
 | 
			
		||||
        long_value += ofs * (58**i)
 | 
			
		||||
    result = bytes()
 | 
			
		||||
    while long_value >= 256:
 | 
			
		||||
        div, mod = divmod(long_value, 256)
 | 
			
		||||
        result = bytes((mod,)) + result
 | 
			
		||||
        long_value = div
 | 
			
		||||
    result = bytes((long_value,)) + result
 | 
			
		||||
    nPad = 0
 | 
			
		||||
    for c in v:
 | 
			
		||||
        if c == __b58chars[0]:
 | 
			
		||||
            nPad += 1
 | 
			
		||||
        else:
 | 
			
		||||
            break
 | 
			
		||||
    pad = bytes((0,)) * nPad
 | 
			
		||||
    result = pad + result
 | 
			
		||||
    if length is not None and len(result) != length:
 | 
			
		||||
        return None
 | 
			
		||||
    return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def b58encode(v):
 | 
			
		||||
    long_value = 0
 | 
			
		||||
    for (i, c) in enumerate(v[::-1]):
 | 
			
		||||
        long_value += (256**i) * c
 | 
			
		||||
 | 
			
		||||
    result = ''
 | 
			
		||||
    while long_value >= 58:
 | 
			
		||||
        div, mod = divmod(long_value, 58)
 | 
			
		||||
        result = __b58chars[mod] + result
 | 
			
		||||
        long_value = div
 | 
			
		||||
    result = __b58chars[long_value] + result
 | 
			
		||||
 | 
			
		||||
    # leading 0-bytes in the input become leading-1s
 | 
			
		||||
    nPad = 0
 | 
			
		||||
    for c in v:
 | 
			
		||||
        if c == 0:
 | 
			
		||||
            nPad += 1
 | 
			
		||||
        else:
 | 
			
		||||
            break
 | 
			
		||||
    return (__b58chars[0] * nPad) + result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def encodeStealthAddress(prefix_byte, scan_pubkey, spend_pubkey):
 | 
			
		||||
    data = bytes((0x00,))
 | 
			
		||||
    data += scan_pubkey
 | 
			
		||||
    data += bytes((0x01,))
 | 
			
		||||
    data += spend_pubkey
 | 
			
		||||
    data += bytes((0x00,))  # number_signatures - unused
 | 
			
		||||
    data += bytes((0x00,))  # num prefix bits
 | 
			
		||||
 | 
			
		||||
    b = bytes((prefix_byte,)) + data
 | 
			
		||||
    b += hashlib.sha256(hashlib.sha256(b).digest()).digest()[:4]
 | 
			
		||||
    return b58encode(b)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def decodeWif(encoded_key):
 | 
			
		||||
    key = b58decode(encoded_key)[1:-4]
 | 
			
		||||
    if len(key) == 33:
 | 
			
		||||
        return key[:-1]
 | 
			
		||||
    return key
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def toWIF(prefix_byte, b, compressed=True):
 | 
			
		||||
    b = bytes((prefix_byte,)) + b
 | 
			
		||||
    if compressed:
 | 
			
		||||
        b += bytes((0x01,))
 | 
			
		||||
    b += hashlib.sha256(hashlib.sha256(b).digest()).digest()[:4]
 | 
			
		||||
    return b58encode(b)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getKeyID(bytes):
 | 
			
		||||
    data = hashlib.sha256(bytes).digest()
 | 
			
		||||
    return hashlib.new('ripemd160', data).digest()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def bech32Decode(hrp, addr):
 | 
			
		||||
    hrpgot, data = bech32_decode(addr)
 | 
			
		||||
    if hrpgot != hrp:
 | 
			
		||||
        return None
 | 
			
		||||
    decoded = convertbits(data, 5, 8, False)
 | 
			
		||||
    if decoded is None or len(decoded) < 2 or len(decoded) > 40:
 | 
			
		||||
        return None
 | 
			
		||||
    return bytes(decoded)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def bech32Encode(hrp, data):
 | 
			
		||||
    ret = bech32_encode(hrp, convertbits(data, 8, 5))
 | 
			
		||||
    if bech32Decode(hrp, ret) is None:
 | 
			
		||||
        return None
 | 
			
		||||
    return ret
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def decodeAddress(address_str):
 | 
			
		||||
    b58_addr = b58decode(address_str)
 | 
			
		||||
    if b58_addr is not None:
 | 
			
		||||
        address = b58_addr[:-4]
 | 
			
		||||
        checksum = b58_addr[-4:]
 | 
			
		||||
        assert(hashlib.sha256(hashlib.sha256(address).digest()).digest()[:4] == checksum), 'Checksum mismatch'
 | 
			
		||||
        return b58_addr[:-4]
 | 
			
		||||
    return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def encodeAddress(address):
 | 
			
		||||
    checksum = hashlib.sha256(hashlib.sha256(address).digest()).digest()
 | 
			
		||||
    return b58encode(address + checksum[0:4])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def pubkeyToAddress(prefix, pubkey):
 | 
			
		||||
    return encodeAddress(bytes((prefix,)) + getKeyID(pubkey))
 | 
			
		||||
@ -2,11 +2,11 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import codecs
 | 
			
		||||
import hashlib
 | 
			
		||||
import secrets
 | 
			
		||||
 | 
			
		||||
from .contrib.ellipticcurve import CurveFp, Point, INFINITY, jacobi_symbol
 | 
			
		||||
from basicswap.contrib.ellipticcurve import CurveFp, Point, INFINITY, jacobi_symbol
 | 
			
		||||
from . import i2b
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ECCParameters():
 | 
			
		||||
@ -37,31 +37,9 @@ 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) -> int:
 | 
			
		||||
    return int.from_bytes(b, byteorder='big')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def intToBytes32(i: int) -> bytes:
 | 
			
		||||
    return i.to_bytes(32, byteorder='big')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def intToBytes32_le(i: int) -> bytes:
 | 
			
		||||
    return i.to_bytes(32, byteorder='little')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def bytesToHexStr(b: bytes) -> str:
 | 
			
		||||
    return codecs.encode(b, 'hex').decode('utf-8')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def hexStrToBytes(h: str) -> bytes:
 | 
			
		||||
    if h.startswith('0x'):
 | 
			
		||||
        h = h[2:]
 | 
			
		||||
    return bytes.fromhex(h)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getSecretBytes() -> bytes:
 | 
			
		||||
    i = 1 + secrets.randbelow(ep.o - 1)
 | 
			
		||||
    return intToBytes32(i)
 | 
			
		||||
    return i2b(i)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getSecretInt() -> int:
 | 
			
		||||
@ -189,16 +167,6 @@ def hash256(inb):
 | 
			
		||||
    return hashlib.sha256(inb).digest()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
i2b = intToBytes32
 | 
			
		||||
b2i = bytes32ToInt
 | 
			
		||||
b2h = bytesToHexStr
 | 
			
		||||
h2b = hexStrToBytes
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def i2h(x):
 | 
			
		||||
    return b2h(i2b(x))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def testEccUtils():
 | 
			
		||||
    print('testEccUtils()')
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										31
									
								
								basicswap/util/rfc2440.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								basicswap/util/rfc2440.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
import hashlib
 | 
			
		||||
import secrets
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def rfc2440_hash_password(password, salt=None):
 | 
			
		||||
    # Match tor --hash-password
 | 
			
		||||
    # secret_to_key_rfc2440
 | 
			
		||||
 | 
			
		||||
    EXPBIAS = 6
 | 
			
		||||
    c = 96
 | 
			
		||||
    count = (16 + (c & 15)) << ((c >> 4) + EXPBIAS)
 | 
			
		||||
 | 
			
		||||
    if salt is None:
 | 
			
		||||
        salt = secrets.token_bytes(8)
 | 
			
		||||
    assert(len(salt) == 8)
 | 
			
		||||
 | 
			
		||||
    hashbytes = salt + password.encode('utf-8')
 | 
			
		||||
    len_hashbytes = len(hashbytes)
 | 
			
		||||
    h = hashlib.sha1()
 | 
			
		||||
 | 
			
		||||
    while count > 0:
 | 
			
		||||
        if count >= len_hashbytes:
 | 
			
		||||
            h.update(hashbytes)
 | 
			
		||||
            count -= len_hashbytes
 | 
			
		||||
            continue
 | 
			
		||||
        h.update(hashbytes[:count])
 | 
			
		||||
        break
 | 
			
		||||
    rv = '16:' + salt.hex() + '60' + h.hexdigest()
 | 
			
		||||
    return rv.upper()
 | 
			
		||||
							
								
								
									
										71
									
								
								basicswap/util/script.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								basicswap/util/script.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,71 @@
 | 
			
		||||
# -*- 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.
 | 
			
		||||
 | 
			
		||||
import struct
 | 
			
		||||
import hashlib
 | 
			
		||||
from basicswap.script import OpCodes
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def decodeScriptNum(script_bytes, o):
 | 
			
		||||
    v = 0
 | 
			
		||||
    num_len = script_bytes[o]
 | 
			
		||||
    if num_len >= OpCodes.OP_1 and num_len <= OpCodes.OP_16:
 | 
			
		||||
        return((num_len - OpCodes.OP_1) + 1, 1)
 | 
			
		||||
 | 
			
		||||
    if num_len > 4:
 | 
			
		||||
        raise ValueError('Bad scriptnum length')  # Max 4 bytes
 | 
			
		||||
    if num_len + o >= len(script_bytes):
 | 
			
		||||
        raise ValueError('Bad script length')
 | 
			
		||||
    o += 1
 | 
			
		||||
    for i in range(num_len):
 | 
			
		||||
        b = script_bytes[o + i]
 | 
			
		||||
        # Negative flag set in last byte, if num is positive and > 0x80 an extra 0x00 byte will be appended
 | 
			
		||||
        if i == num_len - 1 and b & 0x80:
 | 
			
		||||
            b &= (~(0x80) & 0xFF)
 | 
			
		||||
            v += int(b) << 8 * i
 | 
			
		||||
            v *= -1
 | 
			
		||||
        else:
 | 
			
		||||
            v += int(b) << 8 * i
 | 
			
		||||
    return(v, 1 + num_len)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getP2SHScriptForHash(p2sh):
 | 
			
		||||
    return bytes((OpCodes.OP_HASH160, 0x14)) \
 | 
			
		||||
        + p2sh \
 | 
			
		||||
        + bytes((OpCodes.OP_EQUAL,))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getP2WSH(script):
 | 
			
		||||
    return bytes((OpCodes.OP_0, 0x20)) + hashlib.sha256(script).digest()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def SerialiseNumCompact(v):
 | 
			
		||||
    if v < 253:
 | 
			
		||||
        return bytes((v,))
 | 
			
		||||
    if v <= 0xffff:  # USHRT_MAX
 | 
			
		||||
        return struct.pack("<BH", 253, v)
 | 
			
		||||
    if v <= 0xffffffff:  # UINT_MAX
 | 
			
		||||
        return struct.pack("<BI", 254, v)
 | 
			
		||||
    if v <= 0xffffffffffffffff:  # UINT_MAX
 | 
			
		||||
        return struct.pack("<BQ", 255, v)
 | 
			
		||||
    raise ValueError('Value too large')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getCompactSizeLen(v):
 | 
			
		||||
    # Compact Size
 | 
			
		||||
    if v < 253:
 | 
			
		||||
        return 1
 | 
			
		||||
    if v <= 0xffff:  # USHRT_MAX
 | 
			
		||||
        return 3
 | 
			
		||||
    if v <= 0xffffffff:  # UINT_MAX
 | 
			
		||||
        return 5
 | 
			
		||||
    if v <= 0xffffffffffffffff:  # UINT_MAX
 | 
			
		||||
        return 9
 | 
			
		||||
    raise ValueError('Value too large')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getWitnessElementLen(v):
 | 
			
		||||
    return getCompactSizeLen(v) + v
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
# Copyright (c) 2019-2021 tecnovert
 | 
			
		||||
# Copyright (c) 2019-2022 tecnovert
 | 
			
		||||
# Distributed under the MIT software license, see the accompanying
 | 
			
		||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,8 @@ import json
 | 
			
		||||
import mmap
 | 
			
		||||
import stat
 | 
			
		||||
import gnupg
 | 
			
		||||
import socks
 | 
			
		||||
import shutil
 | 
			
		||||
import signal
 | 
			
		||||
import socket
 | 
			
		||||
import hashlib
 | 
			
		||||
@ -28,6 +30,8 @@ from basicswap.rpc import (
 | 
			
		||||
)
 | 
			
		||||
from basicswap.basicswap import BasicSwap
 | 
			
		||||
from basicswap.chainparams import Coins
 | 
			
		||||
from basicswap.util import toBool
 | 
			
		||||
from basicswap.util.rfc2440 import rfc2440_hash_password
 | 
			
		||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
 | 
			
		||||
from bin.basicswap_run import startDaemon, startXmrWalletDaemon
 | 
			
		||||
 | 
			
		||||
@ -79,6 +83,10 @@ LTC_RPC_PORT = int(os.getenv('LTC_RPC_PORT', 19795))
 | 
			
		||||
BTC_RPC_PORT = int(os.getenv('BTC_RPC_PORT', 19796))
 | 
			
		||||
NMC_RPC_PORT = int(os.getenv('NMC_RPC_PORT', 19798))
 | 
			
		||||
 | 
			
		||||
PART_ONION_PORT = int(os.getenv('PART_ONION_PORT', 51734))
 | 
			
		||||
LTC_ONION_PORT = int(os.getenv('LTC_ONION_PORT', 19795))  # Still on 0.18 codebase, same port
 | 
			
		||||
BTC_ONION_PORT = int(os.getenv('BTC_ONION_PORT', 8334))
 | 
			
		||||
 | 
			
		||||
PART_RPC_USER = os.getenv('PART_RPC_USER', '')
 | 
			
		||||
PART_RPC_PWD = os.getenv('PART_RPC_PWD', '')
 | 
			
		||||
BTC_RPC_USER = os.getenv('BTC_RPC_USER', '')
 | 
			
		||||
@ -86,9 +94,21 @@ BTC_RPC_PWD = os.getenv('BTC_RPC_PWD', '')
 | 
			
		||||
LTC_RPC_USER = os.getenv('LTC_RPC_USER', '')
 | 
			
		||||
LTC_RPC_PWD = os.getenv('LTC_RPC_PWD', '')
 | 
			
		||||
 | 
			
		||||
COINS_BIND_IP = os.getenv('COINS_BIND_IP', '127.0.0.1')
 | 
			
		||||
COINS_RPCBIND_IP = os.getenv('COINS_RPCBIND_IP', '127.0.0.1')
 | 
			
		||||
 | 
			
		||||
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))
 | 
			
		||||
TOR_DNS_PORT = int(os.getenv('TOR_DNS_PORT', 5353))
 | 
			
		||||
TEST_TOR_PROXY = toBool(os.getenv('TEST_TOR_PROXY', 'true'))  # Expects a known exit node
 | 
			
		||||
TEST_ONION_LINK = toBool(os.getenv('TEST_ONION_LINK', 'false'))
 | 
			
		||||
 | 
			
		||||
extract_core_overwrite = True
 | 
			
		||||
use_tor_proxy = False
 | 
			
		||||
 | 
			
		||||
default_socket = socket.socket
 | 
			
		||||
default_socket_timeout = socket.getdefaulttimeout()
 | 
			
		||||
default_socket_getaddrinfo = socket.getaddrinfo
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def make_reporthook():
 | 
			
		||||
@ -109,18 +129,64 @@ def make_reporthook():
 | 
			
		||||
    return reporthook
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def downloadFile(url, path):
 | 
			
		||||
    logger.info('Downloading file %s', url)
 | 
			
		||||
    logger.info('To %s', path)
 | 
			
		||||
def getaddrinfo(*args):
 | 
			
		||||
    return [(socket.AF_INET, socket.SOCK_STREAM, 6, "", (args[0], args[1]))]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def setConnectionParameters():
 | 
			
		||||
    opener = urllib.request.build_opener()
 | 
			
		||||
    opener.addheaders = [('User-agent', 'Mozilla/5.0')]
 | 
			
		||||
    urllib.request.install_opener(opener)
 | 
			
		||||
 | 
			
		||||
    # Set one second timeout for urlretrieve connections
 | 
			
		||||
    old_timeout = socket.getdefaulttimeout()
 | 
			
		||||
    if use_tor_proxy:
 | 
			
		||||
        socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, TOR_PROXY_HOST, TOR_PROXY_PORT, rdns=True)
 | 
			
		||||
        socket.socket = socks.socksocket
 | 
			
		||||
        socket.getaddrinfo = getaddrinfo  # Without this accessing .onion links would fail
 | 
			
		||||
 | 
			
		||||
    # Set low timeout for urlretrieve connections
 | 
			
		||||
    socket.setdefaulttimeout(5)
 | 
			
		||||
    urlretrieve(url, path, make_reporthook())
 | 
			
		||||
    socket.setdefaulttimeout(old_timeout)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def popConnectionParameters():
 | 
			
		||||
    if use_tor_proxy:
 | 
			
		||||
        socket.socket = default_socket
 | 
			
		||||
        socket.getaddrinfo = default_socket_getaddrinfo
 | 
			
		||||
    socket.setdefaulttimeout(default_socket_timeout)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def downloadFile(url, path):
 | 
			
		||||
    logger.info('Downloading file %s', url)
 | 
			
		||||
    logger.info('To %s', path)
 | 
			
		||||
    try:
 | 
			
		||||
        setConnectionParameters()
 | 
			
		||||
        urlretrieve(url, path, make_reporthook())
 | 
			
		||||
    finally:
 | 
			
		||||
        popConnectionParameters()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def downloadBytes(url):
 | 
			
		||||
    try:
 | 
			
		||||
        setConnectionParameters()
 | 
			
		||||
        return urllib.request.urlopen(url).read()
 | 
			
		||||
    finally:
 | 
			
		||||
        popConnectionParameters()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def testTorConnection():
 | 
			
		||||
    test_url = 'https://check.torproject.org/'
 | 
			
		||||
    logger.info('Testing TOR connection at: ' + test_url)
 | 
			
		||||
 | 
			
		||||
    test_response = downloadBytes(test_url).decode('utf-8')
 | 
			
		||||
    assert('Congratulations. This browser is configured to use Tor.' in test_response)
 | 
			
		||||
    logger.info('TOR is working.')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def testOnionLink():
 | 
			
		||||
    test_url = 'http://jqyzxhjk6psc6ul5jnfwloamhtyh7si74b4743k2qgpskwwxrzhsxmad.onion'
 | 
			
		||||
    logger.info('Testing onion site: ' + test_url)
 | 
			
		||||
    test_response = downloadBytes(test_url).decode('utf-8')
 | 
			
		||||
    assert('The Tor Project\'s free software protects your privacy online.' in test_response)
 | 
			
		||||
    logger.info('Onion links work.')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def extractCore(coin, version_pair, settings, bin_dir, release_path):
 | 
			
		||||
@ -289,7 +355,7 @@ def prepareCore(coin, version_pair, settings, data_dir):
 | 
			
		||||
 | 
			
		||||
            pubkeyurl = 'https://raw.githubusercontent.com/monero-project/monero/master/utils/gpg_keys/binaryfate.asc'
 | 
			
		||||
            logger.info('Importing public key from url: ' + pubkeyurl)
 | 
			
		||||
            rv = gpg.import_keys(urllib.request.urlopen(pubkeyurl).read())
 | 
			
		||||
            rv = gpg.import_keys(downloadBytes(pubkeyurl))
 | 
			
		||||
            print('import_keys', rv)
 | 
			
		||||
            assert('F0AF4D462A0BDF92' in rv.fingerprints[0])
 | 
			
		||||
            gpg.trust_keys(rv.fingerprints[0], 'TRUST_FULLY')
 | 
			
		||||
@ -304,7 +370,8 @@ def prepareCore(coin, version_pair, settings, data_dir):
 | 
			
		||||
 | 
			
		||||
            pubkeyurl = 'https://raw.githubusercontent.com/tecnovert/basicswap/master/gitianpubkeys/{}_{}.pgp'.format(coin, signing_key_name)
 | 
			
		||||
            logger.info('Importing public key from url: ' + pubkeyurl)
 | 
			
		||||
            rv = gpg.import_keys(urllib.request.urlopen(pubkeyurl).read())
 | 
			
		||||
 | 
			
		||||
            rv = gpg.import_keys(downloadBytes(pubkeyurl))
 | 
			
		||||
 | 
			
		||||
            for key in rv.fingerprints:
 | 
			
		||||
                gpg.trust_keys(key, 'TRUST_FULLY')
 | 
			
		||||
@ -319,7 +386,7 @@ def prepareCore(coin, version_pair, settings, data_dir):
 | 
			
		||||
    extractCore(coin, version_pair, settings, bin_dir, release_path)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def prepareDataDir(coin, settings, chain, particl_mnemonic, use_containers=False):
 | 
			
		||||
def prepareDataDir(coin, settings, chain, particl_mnemonic, use_containers=False, tor_control_password=None):
 | 
			
		||||
    core_settings = settings['chainclients'][coin]
 | 
			
		||||
    bin_dir = core_settings['bindir']
 | 
			
		||||
    data_dir = core_settings['datadir']
 | 
			
		||||
@ -343,11 +410,16 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, use_containers=False
 | 
			
		||||
                fp.write('testnet=1\n')
 | 
			
		||||
            fp.write('data-dir={}\n'.format(data_dir))
 | 
			
		||||
            fp.write('rpc-bind-port={}\n'.format(core_settings['rpcport']))
 | 
			
		||||
            fp.write('rpc-bind-ip={}\n'.format(COINS_BIND_IP))
 | 
			
		||||
            fp.write('rpc-bind-ip={}\n'.format(COINS_RPCBIND_IP))
 | 
			
		||||
            fp.write('zmq-rpc-bind-port={}\n'.format(core_settings['zmqport']))
 | 
			
		||||
            fp.write('zmq-rpc-bind-ip={}\n'.format(COINS_BIND_IP))
 | 
			
		||||
            fp.write('zmq-rpc-bind-ip={}\n'.format(COINS_RPCBIND_IP))
 | 
			
		||||
            fp.write('prune-blockchain=1\n')
 | 
			
		||||
 | 
			
		||||
            if tor_control_password is not None:
 | 
			
		||||
                fp.write(f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}\n')
 | 
			
		||||
                fp.write('proxy-allow-dns-leaks=0\n')
 | 
			
		||||
                fp.write('no-igd=1\n')
 | 
			
		||||
 | 
			
		||||
        wallets_dir = core_settings.get('walletsdir', data_dir)
 | 
			
		||||
        if not os.path.exists(wallets_dir):
 | 
			
		||||
            os.makedirs(wallets_dir)
 | 
			
		||||
@ -361,11 +433,15 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, use_containers=False
 | 
			
		||||
            fp.write('untrusted-daemon=1\n')
 | 
			
		||||
            fp.write('no-dns=1\n')
 | 
			
		||||
            fp.write('rpc-bind-port={}\n'.format(core_settings['walletrpcport']))
 | 
			
		||||
            fp.write('rpc-bind-ip={}\n'.format(COINS_BIND_IP))
 | 
			
		||||
            fp.write('rpc-bind-ip={}\n'.format(COINS_RPCBIND_IP))
 | 
			
		||||
            fp.write('wallet-dir={}\n'.format(os.path.join(data_dir, 'wallets')))
 | 
			
		||||
            fp.write('log-file={}\n'.format(os.path.join(data_dir, 'wallet.log')))
 | 
			
		||||
            fp.write('shared-ringdb-dir={}\n'.format(os.path.join(data_dir, 'shared-ringdb')))
 | 
			
		||||
            fp.write('rpc-login={}:{}\n'.format(core_settings['walletrpcuser'], core_settings['walletrpcpassword']))
 | 
			
		||||
 | 
			
		||||
            if tor_control_password is not None:
 | 
			
		||||
                if not core_settings['manage_daemon']:
 | 
			
		||||
                    fp.write(f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}\n')
 | 
			
		||||
        return
 | 
			
		||||
    core_conf_path = os.path.join(data_dir, coin + '.conf')
 | 
			
		||||
    if os.path.exists(core_conf_path):
 | 
			
		||||
@ -380,20 +456,28 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, use_containers=False
 | 
			
		||||
            else:
 | 
			
		||||
                logger.warning('Unknown chain %s', chain)
 | 
			
		||||
 | 
			
		||||
        if COINS_BIND_IP != '127.0.0.1':
 | 
			
		||||
        if COINS_RPCBIND_IP != '127.0.0.1':
 | 
			
		||||
            fp.write('rpcallowip=127.0.0.1\n')
 | 
			
		||||
            fp.write('rpcallowip=172.0.0.0/8\n')  # Allow 172.x.x.x, range used by docker
 | 
			
		||||
            fp.write('rpcbind={}\n'.format(COINS_BIND_IP))
 | 
			
		||||
            fp.write('rpcbind={}\n'.format(COINS_RPCBIND_IP))
 | 
			
		||||
 | 
			
		||||
        fp.write('rpcport={}\n'.format(core_settings['rpcport']))
 | 
			
		||||
        fp.write('printtoconsole=0\n')
 | 
			
		||||
        fp.write('daemon=0\n')
 | 
			
		||||
        fp.write('wallet=wallet.dat\n')
 | 
			
		||||
 | 
			
		||||
        if tor_control_password is not None:
 | 
			
		||||
            onionport = core_settings['onionport']
 | 
			
		||||
            fp.write(f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}\n')
 | 
			
		||||
            fp.write(f'torpassword={tor_control_password}\n')
 | 
			
		||||
            fp.write(f'torcontrol={TOR_PROXY_HOST}:{TOR_CONTROL_PORT}\n')
 | 
			
		||||
            # -listen is automatically set in InitParameterInteraction when bind is set
 | 
			
		||||
            fp.write(f'bind=0.0.0.0:{onionport}=onion\n')
 | 
			
		||||
 | 
			
		||||
        salt = generate_salt(16)
 | 
			
		||||
        if coin == 'particl':
 | 
			
		||||
            fp.write('debugexclude=libevent\n')
 | 
			
		||||
            fp.write('zmqpubsmsg=tcp://{}:{}\n'.format(COINS_BIND_IP, settings['zmqport']))
 | 
			
		||||
            fp.write('zmqpubsmsg=tcp://{}:{}\n'.format(COINS_RPCBIND_IP, settings['zmqport']))
 | 
			
		||||
            fp.write('spentindex=1\n')
 | 
			
		||||
            fp.write('txindex=1\n')
 | 
			
		||||
            fp.write('staking=0\n')
 | 
			
		||||
@ -421,6 +505,140 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, use_containers=False
 | 
			
		||||
        callrpc_cli(bin_dir, data_dir, chain, '-wallet=wallet.dat create', wallet_util)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def write_torrc(data_dir, tor_control_password):
 | 
			
		||||
    tor_dir = os.path.join(data_dir, 'tor')
 | 
			
		||||
    if not os.path.exists(tor_dir):
 | 
			
		||||
        os.makedirs(tor_dir)
 | 
			
		||||
    torrc_path = os.path.join(tor_dir, 'torrc')
 | 
			
		||||
    if os.path.exists(torrc_path):
 | 
			
		||||
        logger.info(f'torrc file exists at {torrc_path}.')
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    tor_control_hash = rfc2440_hash_password(tor_control_password)
 | 
			
		||||
    with open(torrc_path, 'w') as fp:
 | 
			
		||||
        fp.write(f'SocksPort 0.0.0.0:{TOR_PROXY_PORT}\n')
 | 
			
		||||
        fp.write(f'ControlPort 0.0.0.0:{TOR_CONTROL_PORT}\n')
 | 
			
		||||
        fp.write(f'DNSPort 0.0.0.0:{TOR_DNS_PORT}\n')
 | 
			
		||||
        fp.write(f'HashedControlPassword {tor_control_hash}\n')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def addTorSettings(settings, tor_control_password):
 | 
			
		||||
    settings['tor_control_password'] = tor_control_password
 | 
			
		||||
    settings['use_tor'] = True
 | 
			
		||||
    settings['tor_proxy_host'] = TOR_PROXY_HOST
 | 
			
		||||
    settings['tor_proxy_port'] = TOR_PROXY_PORT
 | 
			
		||||
    settings['tor_control_port'] = TOR_CONTROL_PORT
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def modify_tor_config(settings, coin, tor_control_password=None, enable=False):
 | 
			
		||||
    coin_settings = settings['chainclients'][coin]
 | 
			
		||||
    data_dir = coin_settings['datadir']
 | 
			
		||||
 | 
			
		||||
    if coin == 'monero':
 | 
			
		||||
        core_conf_path = os.path.join(data_dir, coin + 'd.conf')
 | 
			
		||||
        if not os.path.exists(core_conf_path):
 | 
			
		||||
            exitWithError('{} does not exist'.format(core_conf_path))
 | 
			
		||||
        wallets_dir = coin_settings.get('walletsdir', data_dir)
 | 
			
		||||
        wallet_conf_path = os.path.join(wallets_dir, coin + '_wallet.conf')
 | 
			
		||||
        if not os.path.exists(wallet_conf_path):
 | 
			
		||||
            exitWithError('{} does not exist'.format(wallet_conf_path))
 | 
			
		||||
 | 
			
		||||
        # Backup
 | 
			
		||||
        shutil.copyfile(core_conf_path, core_conf_path + '.last')
 | 
			
		||||
        shutil.copyfile(wallet_conf_path, wallet_conf_path + '.last')
 | 
			
		||||
 | 
			
		||||
        daemon_tor_settings = ('proxy=', 'proxy-allow-dns-leak=', 'no-igd=')
 | 
			
		||||
        with open(core_conf_path, 'w') as fp:
 | 
			
		||||
            with open(core_conf_path + '.last') as fp_in:
 | 
			
		||||
                # Disable tor first
 | 
			
		||||
                for line in fp_in:
 | 
			
		||||
                    skip_line = False
 | 
			
		||||
                    for setting in daemon_tor_settings:
 | 
			
		||||
                        if line.startswith(setting):
 | 
			
		||||
                            skip_line = True
 | 
			
		||||
                            break
 | 
			
		||||
                    if not skip_line:
 | 
			
		||||
                        fp.write(line)
 | 
			
		||||
            if enable:
 | 
			
		||||
                fp.write(f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}\n')
 | 
			
		||||
                fp.write('proxy-allow-dns-leaks=0\n')
 | 
			
		||||
                fp.write('no-igd=1\n')
 | 
			
		||||
 | 
			
		||||
        wallet_tor_settings = ('proxy=')
 | 
			
		||||
        with open(wallet_conf_path, 'w') as fp:
 | 
			
		||||
            with open(wallet_conf_path + '.last') as fp_in:
 | 
			
		||||
                # Disable tor first
 | 
			
		||||
                for line in fp_in:
 | 
			
		||||
                    skip_line = False
 | 
			
		||||
                    for setting in wallet_tor_settings:
 | 
			
		||||
                        if line.startswith(setting):
 | 
			
		||||
                            skip_line = True
 | 
			
		||||
                            break
 | 
			
		||||
                    if not skip_line:
 | 
			
		||||
                        fp.write(line)
 | 
			
		||||
            if enable:
 | 
			
		||||
                if not coin_settings['manage_daemon']:
 | 
			
		||||
                    fp.write(f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}\n')
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    config_path = os.path.join(data_dir, coin + '.conf')
 | 
			
		||||
    if not os.path.exists(config_path):
 | 
			
		||||
        exitWithError('{} does not exist'.format(config_path))
 | 
			
		||||
 | 
			
		||||
    if 'onionport' not in coin_settings:
 | 
			
		||||
        default_onionport = 0
 | 
			
		||||
        if coin == 'bitcoin':
 | 
			
		||||
            default_onionport = BTC_ONION_PORT
 | 
			
		||||
        elif coin == 'particl':
 | 
			
		||||
            default_onionport = PART_ONION_PORT
 | 
			
		||||
        elif coin == 'litecoin':
 | 
			
		||||
            default_onionport = LTC_ONION_PORT
 | 
			
		||||
        else:
 | 
			
		||||
            exitWithError('Unknown default onion listening port for {}'.format(coin))
 | 
			
		||||
        coin_settings['onionport'] = default_onionport
 | 
			
		||||
 | 
			
		||||
    # Backup
 | 
			
		||||
    shutil.copyfile(config_path, config_path + '.last')
 | 
			
		||||
 | 
			
		||||
    tor_settings = ('proxy=', 'torpassword=', 'torcontrol=', 'bind=')
 | 
			
		||||
    with open(config_path, 'w') as fp:
 | 
			
		||||
        with open(config_path + '.last') as fp_in:
 | 
			
		||||
            # Disable tor first
 | 
			
		||||
            for line in fp_in:
 | 
			
		||||
                skip_line = False
 | 
			
		||||
                for setting in tor_settings:
 | 
			
		||||
                    if line.startswith(setting):
 | 
			
		||||
                        skip_line = True
 | 
			
		||||
                        break
 | 
			
		||||
                if not skip_line:
 | 
			
		||||
                    fp.write(line)
 | 
			
		||||
        if enable:
 | 
			
		||||
            onionport = coin_settings['onionport']
 | 
			
		||||
            fp.write(f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}\n')
 | 
			
		||||
            fp.write(f'torpassword={tor_control_password}\n')
 | 
			
		||||
            fp.write(f'torcontrol={TOR_PROXY_HOST}:{TOR_CONTROL_PORT}\n')
 | 
			
		||||
            fp.write(f'bind=0.0.0.0:{onionport}=onion\n')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def make_rpc_func(bin_dir, data_dir, chain):
 | 
			
		||||
    bin_dir = bin_dir
 | 
			
		||||
    data_dir = data_dir
 | 
			
		||||
    chain = chain
 | 
			
		||||
 | 
			
		||||
    def rpc_func(cmd):
 | 
			
		||||
        nonlocal bin_dir
 | 
			
		||||
        nonlocal data_dir
 | 
			
		||||
        nonlocal chain
 | 
			
		||||
 | 
			
		||||
        return callrpc_cli(bin_dir, data_dir, chain, cmd, cfg.PARTICL_CLI)
 | 
			
		||||
    return rpc_func
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def exitWithError(error_msg):
 | 
			
		||||
    sys.stderr.write('Error: {}, exiting.\n'.format(error_msg))
 | 
			
		||||
    sys.exit(1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def printVersion():
 | 
			
		||||
    from basicswap import __version__
 | 
			
		||||
    logger.info('Basicswap version: %s', __version__)
 | 
			
		||||
@ -452,31 +670,14 @@ def printHelp():
 | 
			
		||||
    logger.info('--htmlhost=              Interface to host on, default:127.0.0.1.')
 | 
			
		||||
    logger.info('--xmrrestoreheight=n     Block height to restore Monero wallet from, default:{}.'.format(DEFAULT_XMR_RESTORE_HEIGHT))
 | 
			
		||||
    logger.info('--noextractover          Prevent extracting cores if files exist.  Speeds up tests')
 | 
			
		||||
    logger.info('--usetorproxy            Use TOR proxy.  Note that some download links may be inaccessible over TOR.')
 | 
			
		||||
 | 
			
		||||
    logger.info('\n' + 'Known coins: %s', ', '.join(known_coins.keys()))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def make_rpc_func(bin_dir, data_dir, chain):
 | 
			
		||||
    bin_dir = bin_dir
 | 
			
		||||
    data_dir = data_dir
 | 
			
		||||
    chain = chain
 | 
			
		||||
 | 
			
		||||
    def rpc_func(cmd):
 | 
			
		||||
        nonlocal bin_dir
 | 
			
		||||
        nonlocal data_dir
 | 
			
		||||
        nonlocal chain
 | 
			
		||||
 | 
			
		||||
        return callrpc_cli(bin_dir, data_dir, chain, cmd, cfg.PARTICL_CLI)
 | 
			
		||||
    return rpc_func
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def exitWithError(error_msg):
 | 
			
		||||
    sys.stderr.write('Error: {}, exiting.\n'.format(error_msg))
 | 
			
		||||
    sys.exit(1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    global extract_core_overwrite
 | 
			
		||||
    global use_tor_proxy
 | 
			
		||||
    data_dir = None
 | 
			
		||||
    bin_dir = None
 | 
			
		||||
    port_offset = None
 | 
			
		||||
@ -490,6 +691,9 @@ def main():
 | 
			
		||||
    disable_coin = ''
 | 
			
		||||
    htmlhost = '127.0.0.1'
 | 
			
		||||
    xmr_restore_height = DEFAULT_XMR_RESTORE_HEIGHT
 | 
			
		||||
    enable_tor = False
 | 
			
		||||
    disable_tor = False
 | 
			
		||||
    tor_control_password = None
 | 
			
		||||
 | 
			
		||||
    for v in sys.argv[1:]:
 | 
			
		||||
        if len(v) < 2 or v[0] != '-':
 | 
			
		||||
@ -528,6 +732,15 @@ def main():
 | 
			
		||||
        if name == 'noextractover':
 | 
			
		||||
            extract_core_overwrite = False
 | 
			
		||||
            continue
 | 
			
		||||
        if name == 'usetorproxy':
 | 
			
		||||
            use_tor_proxy = True
 | 
			
		||||
            continue
 | 
			
		||||
        if name == 'enabletor':
 | 
			
		||||
            enable_tor = True
 | 
			
		||||
            continue
 | 
			
		||||
        if name == 'disabletor':
 | 
			
		||||
            disable_tor = True
 | 
			
		||||
            continue
 | 
			
		||||
        if len(s) == 2:
 | 
			
		||||
            if name == 'datadir':
 | 
			
		||||
                data_dir = os.path.expanduser(s[1].strip('"'))
 | 
			
		||||
@ -575,6 +788,14 @@ def main():
 | 
			
		||||
 | 
			
		||||
        exitWithError('Unknown argument {}'.format(v))
 | 
			
		||||
 | 
			
		||||
    setConnectionParameters()
 | 
			
		||||
 | 
			
		||||
    if use_tor_proxy and TEST_TOR_PROXY:
 | 
			
		||||
        testTorConnection()
 | 
			
		||||
 | 
			
		||||
    if use_tor_proxy and TEST_ONION_LINK:
 | 
			
		||||
        testOnionLink()
 | 
			
		||||
 | 
			
		||||
    if data_dir is None:
 | 
			
		||||
        data_dir = os.path.join(os.path.expanduser(cfg.DEFAULT_DATADIR))
 | 
			
		||||
    if bin_dir is None:
 | 
			
		||||
@ -598,6 +819,7 @@ def main():
 | 
			
		||||
            'manage_daemon': True if ('particl' in with_coins and PART_RPC_HOST == '127.0.0.1') else False,
 | 
			
		||||
            'rpchost': PART_RPC_HOST,
 | 
			
		||||
            'rpcport': PART_RPC_PORT + port_offset,
 | 
			
		||||
            'onionport': PART_ONION_PORT + port_offset,
 | 
			
		||||
            'datadir': os.getenv('PART_DATA_DIR', os.path.join(data_dir, 'particl')),
 | 
			
		||||
            'bindir': os.path.join(bin_dir, 'particl'),
 | 
			
		||||
            'blocks_confirmed': 2,
 | 
			
		||||
@ -611,6 +833,7 @@ def main():
 | 
			
		||||
            'manage_daemon': True if ('litecoin' in with_coins and LTC_RPC_HOST == '127.0.0.1') else False,
 | 
			
		||||
            'rpchost': LTC_RPC_HOST,
 | 
			
		||||
            'rpcport': LTC_RPC_PORT + port_offset,
 | 
			
		||||
            'onionport': LTC_ONION_PORT + port_offset,
 | 
			
		||||
            'datadir': os.getenv('LTC_DATA_DIR', os.path.join(data_dir, 'litecoin')),
 | 
			
		||||
            'bindir': os.path.join(bin_dir, 'litecoin'),
 | 
			
		||||
            'use_segwit': True,
 | 
			
		||||
@ -624,6 +847,7 @@ def main():
 | 
			
		||||
            'manage_daemon': True if ('bitcoin' in with_coins and BTC_RPC_HOST == '127.0.0.1') else False,
 | 
			
		||||
            'rpchost': BTC_RPC_HOST,
 | 
			
		||||
            'rpcport': BTC_RPC_PORT + port_offset,
 | 
			
		||||
            'onionport': BTC_ONION_PORT + port_offset,
 | 
			
		||||
            'datadir': os.getenv('BTC_DATA_DIR', os.path.join(data_dir, 'bitcoin')),
 | 
			
		||||
            'bindir': os.path.join(bin_dir, 'bitcoin'),
 | 
			
		||||
            'use_segwit': True,
 | 
			
		||||
@ -677,6 +901,48 @@ def main():
 | 
			
		||||
 | 
			
		||||
    chainclients['monero']['walletsdir'] = os.getenv('XMR_WALLETS_DIR', chainclients['monero']['datadir'])
 | 
			
		||||
 | 
			
		||||
    if enable_tor:
 | 
			
		||||
        logger.info('Enabling TOR')
 | 
			
		||||
 | 
			
		||||
        if not os.path.exists(config_path):
 | 
			
		||||
            exitWithError('{} does not exist'.format(config_path))
 | 
			
		||||
        with open(config_path) as fs:
 | 
			
		||||
            settings = json.load(fs)
 | 
			
		||||
 | 
			
		||||
        tor_control_password = settings.get('tor_control_password', None)
 | 
			
		||||
        if tor_control_password is None:
 | 
			
		||||
            tor_control_password = generate_salt(24)
 | 
			
		||||
            settings['tor_control_password'] = tor_control_password
 | 
			
		||||
        write_torrc(data_dir, tor_control_password)
 | 
			
		||||
 | 
			
		||||
        addTorSettings(settings, tor_control_password)
 | 
			
		||||
        for coin in settings['chainclients']:
 | 
			
		||||
            modify_tor_config(settings, coin, tor_control_password, enable=True)
 | 
			
		||||
 | 
			
		||||
        with open(config_path, 'w') as fp:
 | 
			
		||||
            json.dump(settings, fp, indent=4)
 | 
			
		||||
 | 
			
		||||
        logger.info('Done.')
 | 
			
		||||
        return 0
 | 
			
		||||
 | 
			
		||||
    if disable_tor:
 | 
			
		||||
        logger.info('Disabling TOR')
 | 
			
		||||
 | 
			
		||||
        if not os.path.exists(config_path):
 | 
			
		||||
            exitWithError('{} does not exist'.format(config_path))
 | 
			
		||||
        with open(config_path) as fs:
 | 
			
		||||
            settings = json.load(fs)
 | 
			
		||||
 | 
			
		||||
        settings['use_tor'] = False
 | 
			
		||||
        for coin in settings['chainclients']:
 | 
			
		||||
            modify_tor_config(settings, coin, tor_control_password=None, enable=False)
 | 
			
		||||
 | 
			
		||||
        with open(config_path, 'w') as fp:
 | 
			
		||||
            json.dump(settings, fp, indent=4)
 | 
			
		||||
 | 
			
		||||
        logger.info('Done.')
 | 
			
		||||
        return 0
 | 
			
		||||
 | 
			
		||||
    if disable_coin != '':
 | 
			
		||||
        logger.info('Disabling coin: %s', disable_coin)
 | 
			
		||||
        if not os.path.exists(config_path):
 | 
			
		||||
@ -715,6 +981,7 @@ def main():
 | 
			
		||||
            exitWithError('{} is already in the settings file'.format(add_coin))
 | 
			
		||||
 | 
			
		||||
        settings['chainclients'][add_coin] = chainclients[add_coin]
 | 
			
		||||
        settings['use_tor_proxy'] = use_tor_proxy
 | 
			
		||||
 | 
			
		||||
        if not no_cores:
 | 
			
		||||
            prepareCore(add_coin, known_coins[add_coin], settings, data_dir)
 | 
			
		||||
@ -754,6 +1021,10 @@ def main():
 | 
			
		||||
            'check_expired_seconds': 60
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    if use_tor_proxy:
 | 
			
		||||
        tor_control_password = generate_salt(24)
 | 
			
		||||
        addTorSettings(settings, tor_control_password)
 | 
			
		||||
 | 
			
		||||
    if not no_cores:
 | 
			
		||||
        for c in with_coins:
 | 
			
		||||
            prepareCore(c, known_coins[c], settings, data_dir)
 | 
			
		||||
@ -763,7 +1034,7 @@ def main():
 | 
			
		||||
        return 0
 | 
			
		||||
 | 
			
		||||
    for c in with_coins:
 | 
			
		||||
        prepareDataDir(c, settings, chain, particl_wallet_mnemonic, use_containers=use_containers)
 | 
			
		||||
        prepareDataDir(c, settings, chain, particl_wallet_mnemonic, use_containers=use_containers, tor_control_password=tor_control_password)
 | 
			
		||||
 | 
			
		||||
    with open(config_path, 'w') as fp:
 | 
			
		||||
        json.dump(settings, fp, indent=4)
 | 
			
		||||
@ -778,7 +1049,11 @@ def main():
 | 
			
		||||
    partRpc = make_rpc_func(particl_settings['bindir'], particl_settings['datadir'], chain)
 | 
			
		||||
 | 
			
		||||
    daemons = []
 | 
			
		||||
    daemons.append(startDaemon(particl_settings['datadir'], particl_settings['bindir'], cfg.PARTICLD, ['-noconnect', '-nofindpeers', '-nostaking', '-nodnsseed', '-nolisten']))
 | 
			
		||||
    daemon_args = ['-noconnect', '-nodnsseed']
 | 
			
		||||
    if not use_tor_proxy:
 | 
			
		||||
        # Cannot set -bind or -whitebind together with -listen=0
 | 
			
		||||
        daemon_args.append('-nolisten')
 | 
			
		||||
    daemons.append(startDaemon(particl_settings['datadir'], particl_settings['bindir'], cfg.PARTICLD, daemon_args + ['-nofindpeers', '-nostaking']))
 | 
			
		||||
    try:
 | 
			
		||||
        waitForRPC(partRpc)
 | 
			
		||||
 | 
			
		||||
@ -811,7 +1086,7 @@ def main():
 | 
			
		||||
                    if not coin_settings['manage_daemon']:
 | 
			
		||||
                        continue
 | 
			
		||||
                    filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '')
 | 
			
		||||
                    daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, ['-noconnect', '-nodnsseed', '-nolisten']))
 | 
			
		||||
                    daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args))
 | 
			
		||||
                swap_client.setDaemonPID(c, daemons[-1].pid)
 | 
			
		||||
                swap_client.setCoinRunParams(c)
 | 
			
		||||
                swap_client.createCoinInterface(c)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										36
									
								
								doc/tor.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								doc/tor.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
			
		||||
## Tor
 | 
			
		||||
 | 
			
		||||
Basicswap can be configured to route all traffic through a tor proxy.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### basicswap-prepare
 | 
			
		||||
 | 
			
		||||
basicswap-prepare can be configured to download all binaries through tor and to enable or disable tor in all active coin config files.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### For a new install
 | 
			
		||||
 | 
			
		||||
Note that some download links, notably for Litecoin, are unreachable when using tor.
 | 
			
		||||
 | 
			
		||||
If running through docker start the tor container with the following command as the torrc configuration file won't exist yet.
 | 
			
		||||
 | 
			
		||||
    docker compose -f docker-compose_with_tor.yml run --name tor --rm tor \
 | 
			
		||||
        tor --allow-missing-torrc --SocksPort 0.0.0.0:9050
 | 
			
		||||
 | 
			
		||||
    docker compose -f docker-compose_with_tor.yml run -e TOR_PROXY_HOST=tor --rm swapclient \
 | 
			
		||||
            basicswap-prepare --usetorproxy --datadir=/coindata --withcoins=monero,particl
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Start Basicswap with:
 | 
			
		||||
 | 
			
		||||
    docker compose -f docker-compose_with_tor.yml up
 | 
			
		||||
 | 
			
		||||
#### Enable tor on an existing datadir
 | 
			
		||||
 | 
			
		||||
    docker compose -f docker-compose_with_tor.yml run -e TOR_PROXY_HOST=tor --rm swapclient \
 | 
			
		||||
            basicswap-prepare --datadir=/coindata --enabletor
 | 
			
		||||
 | 
			
		||||
#### Disable tor on an existing datadir
 | 
			
		||||
 | 
			
		||||
    docker compose -f docker-compose_with_tor.yml run --rm swapclient \
 | 
			
		||||
            basicswap-prepare --datadir=/coindata --disabletor
 | 
			
		||||
@ -1,6 +1,5 @@
 | 
			
		||||
version: '3'
 | 
			
		||||
version: '3.4'
 | 
			
		||||
services:
 | 
			
		||||
 | 
			
		||||
    swapclient:
 | 
			
		||||
        image: i_swapclient
 | 
			
		||||
        stop_grace_period: 5m
 | 
			
		||||
@ -19,6 +18,6 @@ services:
 | 
			
		||||
                max-file: "5"
 | 
			
		||||
 | 
			
		||||
volumes:
 | 
			
		||||
  coindata:
 | 
			
		||||
    driver: local
 | 
			
		||||
    coindata:
 | 
			
		||||
        driver: local
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										44
									
								
								docker/docker-compose_with_tor.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								docker/docker-compose_with_tor.yml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,44 @@
 | 
			
		||||
version: '3.4'
 | 
			
		||||
services:
 | 
			
		||||
 | 
			
		||||
    swapclient:
 | 
			
		||||
        image: i_swapclient
 | 
			
		||||
        container_name: swapclient
 | 
			
		||||
        stop_grace_period: 5m
 | 
			
		||||
        build:
 | 
			
		||||
            context: ../
 | 
			
		||||
        volumes:
 | 
			
		||||
            - ${COINDATA_PATH}:/coindata
 | 
			
		||||
        ports:
 | 
			
		||||
            - "${HTML_PORT}"  # Expose only to localhost, see .env
 | 
			
		||||
        environment:
 | 
			
		||||
            - TZ
 | 
			
		||||
            - TOR_PROXY_HOST
 | 
			
		||||
        logging:
 | 
			
		||||
            driver: "json-file"
 | 
			
		||||
            options:
 | 
			
		||||
                max-size: "10m"
 | 
			
		||||
                max-file: "5"
 | 
			
		||||
 | 
			
		||||
    tor:
 | 
			
		||||
        image: i_tor
 | 
			
		||||
        container_name: tor
 | 
			
		||||
        build:
 | 
			
		||||
            context: ./tor
 | 
			
		||||
        volumes:
 | 
			
		||||
            - ${COINDATA_PATH}/tor/data:/var/lib/tor/
 | 
			
		||||
            - ${COINDATA_PATH}/tor/torrc:/etc/tor/torrc
 | 
			
		||||
        logging:
 | 
			
		||||
            driver: "json-file"
 | 
			
		||||
            options:
 | 
			
		||||
                max-size: "10m"
 | 
			
		||||
                max-file: "5"
 | 
			
		||||
 | 
			
		||||
volumes:
 | 
			
		||||
    coindata:
 | 
			
		||||
        driver: local
 | 
			
		||||
 | 
			
		||||
networks:
 | 
			
		||||
    default:
 | 
			
		||||
        external:
 | 
			
		||||
            name: coinswap_network
 | 
			
		||||
@ -19,4 +19,4 @@ BTC_DATA_DIR=/data/bitcoin
 | 
			
		||||
XMR_DATA_DIR=/data/monero_daemon
 | 
			
		||||
XMR_WALLETS_DIR=/data/monero_wallet
 | 
			
		||||
 | 
			
		||||
COINS_BIND_IP=0.0.0.0
 | 
			
		||||
COINS_RPCBIND_IP=0.0.0.0
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,7 @@ services:
 | 
			
		||||
            options:
 | 
			
		||||
                max-size: "10m"
 | 
			
		||||
                max-file: "3"
 | 
			
		||||
        restart: unless-stopped
 | 
			
		||||
        #restart: unless-stopped
 | 
			
		||||
    #bitcoin_core:
 | 
			
		||||
        #image: i_bitcoin
 | 
			
		||||
        #build:
 | 
			
		||||
@ -50,7 +50,7 @@ services:
 | 
			
		||||
            options:
 | 
			
		||||
                max-size: "10m"
 | 
			
		||||
                max-file: "3"
 | 
			
		||||
        restart: unless-stopped
 | 
			
		||||
        #restart: unless-stopped
 | 
			
		||||
    #monero_daemon:
 | 
			
		||||
        #image: i_monero_daemon
 | 
			
		||||
        #build:
 | 
			
		||||
@ -147,7 +147,7 @@ services:
 | 
			
		||||
            - BTC_DATA_DIR
 | 
			
		||||
            - XMR_DATA_DIR
 | 
			
		||||
            - XMR_WALLETS_DIR
 | 
			
		||||
            - COINS_BIND_IP
 | 
			
		||||
            - COINS_RPCBIND_IP
 | 
			
		||||
        restart: "no"
 | 
			
		||||
networks:
 | 
			
		||||
    default:
 | 
			
		||||
 | 
			
		||||
@ -18,4 +18,4 @@ BTC_DATA_DIR=/data/bitcoin
 | 
			
		||||
XMR_DATA_DIR=/data/monero_daemon
 | 
			
		||||
XMR_WALLETS_DIR=/data/monero_wallet
 | 
			
		||||
 | 
			
		||||
COINS_BIND_IP=0.0.0.0
 | 
			
		||||
COINS_RPCBIND_IP=0.0.0.0
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								docker/tor/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docker/tor/Dockerfile
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
FROM alpine:latest
 | 
			
		||||
 | 
			
		||||
# 9050      SOCKS port
 | 
			
		||||
# 9051      control port
 | 
			
		||||
# 5353      DNS port
 | 
			
		||||
EXPOSE 9050 9051 5353
 | 
			
		||||
RUN apk add --no-cache tor
 | 
			
		||||
CMD tor -f /etc/tor/torrc
 | 
			
		||||
@ -6,3 +6,4 @@ python-gnupg
 | 
			
		||||
Jinja2
 | 
			
		||||
requests
 | 
			
		||||
pycryptodome
 | 
			
		||||
PySocks
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								setup.py
									
									
									
									
									
								
							@ -39,6 +39,7 @@ setuptools.setup(
 | 
			
		||||
        "Jinja2",
 | 
			
		||||
        "requests",
 | 
			
		||||
        "pycryptodome",
 | 
			
		||||
        "PySocks",
 | 
			
		||||
    ],
 | 
			
		||||
    entry_points={
 | 
			
		||||
        "console_scripts": [
 | 
			
		||||
 | 
			
		||||
@ -24,9 +24,11 @@ from basicswap.basicswap import (
 | 
			
		||||
)
 | 
			
		||||
from basicswap.util import (
 | 
			
		||||
    COIN,
 | 
			
		||||
    toWIF,
 | 
			
		||||
    dumpj,
 | 
			
		||||
)
 | 
			
		||||
from basicswap.util.address import (
 | 
			
		||||
    toWIF,
 | 
			
		||||
)
 | 
			
		||||
from basicswap.rpc import (
 | 
			
		||||
    callrpc,
 | 
			
		||||
    callrpc_cli,
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,8 @@ from basicswap.basicswap import (
 | 
			
		||||
)
 | 
			
		||||
from basicswap.util import (
 | 
			
		||||
    COIN,
 | 
			
		||||
)
 | 
			
		||||
from basicswap.util.address import (
 | 
			
		||||
    toWIF,
 | 
			
		||||
)
 | 
			
		||||
from basicswap.rpc import (
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
# Copyright (c) 2019-2021 tecnovert
 | 
			
		||||
# Copyright (c) 2019-2022 tecnovert
 | 
			
		||||
# Distributed under the MIT software license, see the accompanying
 | 
			
		||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,8 @@ from coincurve.ecdsaotves import (
 | 
			
		||||
from coincurve.keys import (
 | 
			
		||||
    PrivateKey)
 | 
			
		||||
 | 
			
		||||
from basicswap.ecc_util import i2b, h2b
 | 
			
		||||
from basicswap.util import i2b, h2b
 | 
			
		||||
from basicswap.util.rfc2440 import rfc2440_hash_password
 | 
			
		||||
from basicswap.interface_btc import BTCInterface
 | 
			
		||||
from basicswap.interface_xmr import XMRInterface
 | 
			
		||||
 | 
			
		||||
@ -279,6 +280,13 @@ class Test(unittest.TestCase):
 | 
			
		||||
        amount_to_recreate = int((amount_from * rate) // (10 ** scale_from))
 | 
			
		||||
        assert('10.00000000' == format_amount(amount_to_recreate, scale_to))
 | 
			
		||||
 | 
			
		||||
    def test_rfc2440(self):
 | 
			
		||||
        password = 'test'
 | 
			
		||||
        salt = bytes.fromhex('B7A94A7E4988630E')
 | 
			
		||||
        password_hash = rfc2440_hash_password(password, salt=salt)
 | 
			
		||||
 | 
			
		||||
        assert(password_hash == '16:B7A94A7E4988630E6095334BA67F06FBA509B2A7136A04C9C1B430F539')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    unittest.main()
 | 
			
		||||
 | 
			
		||||
@ -31,10 +31,12 @@ from basicswap.basicswap_util import (
 | 
			
		||||
)
 | 
			
		||||
from basicswap.util import (
 | 
			
		||||
    COIN,
 | 
			
		||||
    toWIF,
 | 
			
		||||
    make_int,
 | 
			
		||||
    format_amount,
 | 
			
		||||
)
 | 
			
		||||
from basicswap.util.address import (
 | 
			
		||||
    toWIF,
 | 
			
		||||
)
 | 
			
		||||
from basicswap.rpc import (
 | 
			
		||||
    callrpc,
 | 
			
		||||
    callrpc_cli,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user