diff --git a/README.md b/README.md new file mode 100644 index 0000000..4402136 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# fastapi +# yes + diff --git a/__pycache__/config.cpython-310.pyc b/__pycache__/config.cpython-310.pyc new file mode 100644 index 0000000..a611641 Binary files /dev/null and b/__pycache__/config.cpython-310.pyc differ diff --git a/__pycache__/config.cpython-39.pyc b/__pycache__/config.cpython-39.pyc new file mode 100644 index 0000000..9baa7ef Binary files /dev/null and b/__pycache__/config.cpython-39.pyc differ diff --git a/__pycache__/functions.cpython-310.pyc b/__pycache__/functions.cpython-310.pyc new file mode 100644 index 0000000..024a406 Binary files /dev/null and b/__pycache__/functions.cpython-310.pyc differ diff --git a/__pycache__/functions.cpython-38.pyc b/__pycache__/functions.cpython-38.pyc new file mode 100755 index 0000000..c7fdfdb Binary files /dev/null and b/__pycache__/functions.cpython-38.pyc differ diff --git a/__pycache__/functions.cpython-39.pyc b/__pycache__/functions.cpython-39.pyc new file mode 100644 index 0000000..913e62c Binary files /dev/null and b/__pycache__/functions.cpython-39.pyc differ diff --git a/__pycache__/main.cpython-310.pyc b/__pycache__/main.cpython-310.pyc new file mode 100644 index 0000000..b4e0df8 Binary files /dev/null and b/__pycache__/main.cpython-310.pyc differ diff --git a/__pycache__/main.cpython-38.pyc b/__pycache__/main.cpython-38.pyc new file mode 100755 index 0000000..fc81907 Binary files /dev/null and b/__pycache__/main.cpython-38.pyc differ diff --git a/__pycache__/main.cpython-39.pyc b/__pycache__/main.cpython-39.pyc new file mode 100644 index 0000000..d444483 Binary files /dev/null and b/__pycache__/main.cpython-39.pyc differ diff --git a/__pycache__/meta.cpython-310.pyc b/__pycache__/meta.cpython-310.pyc new file mode 100644 index 0000000..7cae92f Binary files /dev/null and b/__pycache__/meta.cpython-310.pyc differ diff --git a/__pycache__/models.cpython-310.pyc b/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000..f9caba6 Binary files /dev/null and b/__pycache__/models.cpython-310.pyc differ diff --git a/__pycache__/models.cpython-38.pyc b/__pycache__/models.cpython-38.pyc new file mode 100755 index 0000000..675b463 Binary files /dev/null and b/__pycache__/models.cpython-38.pyc differ diff --git a/__pycache__/models.cpython-39.pyc b/__pycache__/models.cpython-39.pyc new file mode 100644 index 0000000..ad7bd0e Binary files /dev/null and b/__pycache__/models.cpython-39.pyc differ diff --git a/__pycache__/rpcs.cpython-310.pyc b/__pycache__/rpcs.cpython-310.pyc new file mode 100644 index 0000000..d034b6a Binary files /dev/null and b/__pycache__/rpcs.cpython-310.pyc differ diff --git a/__pycache__/rpcs.cpython-38.pyc b/__pycache__/rpcs.cpython-38.pyc new file mode 100755 index 0000000..5afee60 Binary files /dev/null and b/__pycache__/rpcs.cpython-38.pyc differ diff --git a/__pycache__/rpcs.cpython-39.pyc b/__pycache__/rpcs.cpython-39.pyc new file mode 100644 index 0000000..7455db4 Binary files /dev/null and b/__pycache__/rpcs.cpython-39.pyc differ diff --git a/__pycache__/segwit_addr.cpython-38.pyc b/__pycache__/segwit_addr.cpython-38.pyc new file mode 100755 index 0000000..44c6dc2 Binary files /dev/null and b/__pycache__/segwit_addr.cpython-38.pyc differ diff --git a/config.py b/config.py new file mode 100755 index 0000000..ab0738e --- /dev/null +++ b/config.py @@ -0,0 +1,88 @@ +### +# RPC Lists +rpcs = { + 'BTC': { + 'host': '172.16.0.2', + 'port': 8332, + 'user': '55c44311a1e1708ec1afbf9949c96d08', + 'pass': 'lzD8NH4sexdTHnRJ1BxZfBvUxZHlvmW2bwDsF' + }, + 'BTCT': { + 'host': '172.16.2.2', + 'port': 18332, + 'user': '55c44311a1e1708ec1afbf9949c96d08', + 'pass': 'lzD8NH4sexdTHnRJ1BxZfBvUxZHlvmW2bwDsF' + }, + 'LTC': { + 'host': '172.16.1.2', + 'port': 9332, + 'user': '55c44311a1e1708ec1afbf9949c96d08', + 'pass': 'lzD8NH4sexdTHnRJ1BxZfBvUxZHlvmW2bwDsF' + }, + 'BCH': { + 'host': '172.16.3.2', + 'port': 8552, + 'user': '55c44311a1e1708ec1afbf9949c96d08', + 'pass': 'lzD8NH4sexdTHnRJ1BxZfBvUxZHlvmW2bwDsF' + }, + 'ZEC': { + 'host': '172.16.5.2', + 'port': 8552, + 'user': '55c44311a1e1708ec1afbf9949c96d08', + 'pass': 'lzD8NH4sexdTHnRJ1BxZfBvUxZHlvmW2bwDsF' + }, + 'DOGE': { + 'host': '172.16.6.2', + 'port': 25555, + 'user': '55c44311a1e1708ec1afbf9949c96d08', + 'pass': 'lzD8NH4sexdTHnRJ1BxZfBvUxZHlvmW2bwDsF' + }, + 'XMR': { + 'host': 'http://172.16.4.3:18083/json_rpc', + 'user': '55c44311a1e1708ec1afbf9949c96d08', + 'pass': 'lzD8NH4sexdTHnRJ1BxZfBvUxZHlvmW2bwDsF' + }, +} + +DB = { + 'host': 'localhost', + 'port': 3306, + 'user': 'root', + 'pass': 'xegh3kAJyDLaRu' + +} + +fee = { + "REGULAR": { + 'BTC': 0.02, + 'BTCLN': 0.02, + 'BTCT': 0.005, + 'LTC': 0.02, + 'BCH': 0.02, + 'DCR': 0.02, + 'DASH': 0.02, + 'XRP': 0.02, + 'DOGE': 0.02, + 'XMR': 0.06, + 'ZEC': 0.06, + 'USDT': 0.02, + 'ETH': 0.06, + 'XHV': 0.06 + }, + "MERCHANT": { + 'BTC': 0.01, + 'BTCLN': 0.01, + 'BTCT': 0.005, + 'LTC': 0.01, + 'BCH': 0.01, + 'DCR': 0.01, + 'DASH': 0.01, + 'XRP': 0.01, + 'DOGE': 0.01, + 'XMR': 0.01, + 'ZEC': 0.01, + 'USDT': 0.01, + 'ETH': 0.01, + 'XHV': 0.01 + } +} diff --git a/dependencies/__pycache__/bchconvert.cpython-310.pyc b/dependencies/__pycache__/bchconvert.cpython-310.pyc new file mode 100644 index 0000000..6568ee9 Binary files /dev/null and b/dependencies/__pycache__/bchconvert.cpython-310.pyc differ diff --git a/dependencies/__pycache__/bchconvert.cpython-38.pyc b/dependencies/__pycache__/bchconvert.cpython-38.pyc new file mode 100755 index 0000000..9f58402 Binary files /dev/null and b/dependencies/__pycache__/bchconvert.cpython-38.pyc differ diff --git a/dependencies/__pycache__/bchconvert.cpython-39.pyc b/dependencies/__pycache__/bchconvert.cpython-39.pyc new file mode 100644 index 0000000..25a280f Binary files /dev/null and b/dependencies/__pycache__/bchconvert.cpython-39.pyc differ diff --git a/dependencies/__pycache__/crypto.cpython-310.pyc b/dependencies/__pycache__/crypto.cpython-310.pyc new file mode 100644 index 0000000..457da7a Binary files /dev/null and b/dependencies/__pycache__/crypto.cpython-310.pyc differ diff --git a/dependencies/__pycache__/crypto.cpython-38.pyc b/dependencies/__pycache__/crypto.cpython-38.pyc new file mode 100755 index 0000000..8dfee76 Binary files /dev/null and b/dependencies/__pycache__/crypto.cpython-38.pyc differ diff --git a/dependencies/__pycache__/crypto.cpython-39.pyc b/dependencies/__pycache__/crypto.cpython-39.pyc new file mode 100644 index 0000000..3f6babb Binary files /dev/null and b/dependencies/__pycache__/crypto.cpython-39.pyc differ diff --git a/dependencies/__pycache__/moneropy.cpython-310.pyc b/dependencies/__pycache__/moneropy.cpython-310.pyc new file mode 100644 index 0000000..b487422 Binary files /dev/null and b/dependencies/__pycache__/moneropy.cpython-310.pyc differ diff --git a/dependencies/__pycache__/moneropy.cpython-38.pyc b/dependencies/__pycache__/moneropy.cpython-38.pyc new file mode 100755 index 0000000..476f93e Binary files /dev/null and b/dependencies/__pycache__/moneropy.cpython-38.pyc differ diff --git a/dependencies/__pycache__/moneropy.cpython-39.pyc b/dependencies/__pycache__/moneropy.cpython-39.pyc new file mode 100644 index 0000000..c3f328b Binary files /dev/null and b/dependencies/__pycache__/moneropy.cpython-39.pyc differ diff --git a/dependencies/__pycache__/segwit_addr.cpython-310.pyc b/dependencies/__pycache__/segwit_addr.cpython-310.pyc new file mode 100644 index 0000000..6cd864f Binary files /dev/null and b/dependencies/__pycache__/segwit_addr.cpython-310.pyc differ diff --git a/dependencies/__pycache__/segwit_addr.cpython-38.pyc b/dependencies/__pycache__/segwit_addr.cpython-38.pyc new file mode 100755 index 0000000..b568bb3 Binary files /dev/null and b/dependencies/__pycache__/segwit_addr.cpython-38.pyc differ diff --git a/dependencies/__pycache__/segwit_addr.cpython-39.pyc b/dependencies/__pycache__/segwit_addr.cpython-39.pyc new file mode 100644 index 0000000..bf3e190 Binary files /dev/null and b/dependencies/__pycache__/segwit_addr.cpython-39.pyc differ diff --git a/dependencies/bchconvert.py b/dependencies/bchconvert.py new file mode 100755 index 0000000..190bc64 --- /dev/null +++ b/dependencies/bchconvert.py @@ -0,0 +1,129 @@ +from dependencies.crypto import * +from base58 import b58decode_check, b58encode_check +import sys + +class InvalidAddress(Exception): + pass + + +class Address: + VERSION_MAP = { + 'legacy': [ + ('P2SH', 5, False), + ('P2PKH', 0, False), + ('P2SH-TESTNET', 196, True), + ('P2PKH-TESTNET', 111, True) + ], + 'cash': [ + ('P2SH', 8, False), + ('P2PKH', 0, False), + ('P2SH-TESTNET', 8, True), + ('P2PKH-TESTNET', 0, True) + ] + } + MAINNET_PREFIX = 'bitcoincash' + TESTNET_PREFIX = 'bchtest' + + def __init__(self, version, payload, prefix=None): + self.version = version + self.payload = payload + if prefix: + self.prefix = prefix + else: + if Address._address_type('cash', self.version)[2]: + self.prefix = self.TESTNET_PREFIX + else: + self.prefix = self.MAINNET_PREFIX + + def __str__(self): + return 'version: {}\npayload: {}\nprefix: {}'.format(self.version, self.payload, self.prefix) + + def legacy_address(self): + version_int = Address._address_type('legacy', self.version)[1] + return b58encode_check(Address.code_list_to_string([version_int] + self.payload)) + + def cash_address(self): + version_int = Address._address_type('cash', self.version)[1] + payload = [version_int] + self.payload + payload = convertbits(payload, 8, 5) + checksum = calculate_checksum(self.prefix, payload) + return self.prefix + ':' + b32encode(payload + checksum) + + @staticmethod + def code_list_to_string(code_list): + if sys.version_info > (3, 0): + output = bytes() + for code in code_list: + output += bytes([code]) + else: + output = '' + for code in code_list: + output += chr(code) + return output + + @staticmethod + def _address_type(address_type, version): + for mapping in Address.VERSION_MAP[address_type]: + if mapping[0] == version or mapping[1] == version: + return mapping + raise InvalidAddress('Could not determine address version') + + @staticmethod + def from_string(address_string): + try: + address_string = str(address_string) + except Exception: + raise InvalidAddress('Expected string as input') + if ':' not in address_string: + return Address._legacy_string(address_string) + else: + return Address._cash_string(address_string) + + @staticmethod + def _legacy_string(address_string): + try: + decoded = bytearray(b58decode_check(address_string)) + except ValueError: + raise InvalidAddress('Could not decode legacy address') + version = Address._address_type('legacy', decoded[0])[0] + payload = list() + for letter in decoded[1:]: + payload.append(letter) + return Address(version, payload) + + @staticmethod + def _cash_string(address_string): + if address_string.upper() != address_string and address_string.lower() != address_string: + raise InvalidAddress('Cash address contains uppercase and lowercase characters') + address_string = address_string.lower() + colon_count = address_string.count(':') + if colon_count == 0: + address_string = Address.MAINNET_PREFIX + ':' + address_string + elif colon_count > 1: + raise InvalidAddress('Cash address contains more than one colon character') + prefix, base32string = address_string.split(':') + decoded = b32decode(base32string) + if not verify_checksum(prefix, decoded): + raise InvalidAddress('Bad cash address checksum') + converted = convertbits(decoded, 5, 8) + version = Address._address_type('cash', converted[0])[0] + if prefix == Address.TESTNET_PREFIX: + version += '-TESTNET' + payload = converted[1:-6] + return Address(version, payload, prefix) + + +def to_cash_address(address): + return Address.from_string(address).cash_address() + + +def to_legacy_address(address): + return Address.from_string(address).legacy_address() + + +def is_valid(address): + try: + Address.from_string(address) + return True + except InvalidAddress: + return False diff --git a/dependencies/crypto.py b/dependencies/crypto.py new file mode 100755 index 0000000..2e32f9e --- /dev/null +++ b/dependencies/crypto.py @@ -0,0 +1,70 @@ +CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l' + + +def polymod(values): + chk = 1 + generator = [ + (0x01, 0x98f2bc8e61), + (0x02, 0x79b76d99e2), + (0x04, 0xf33e5fb3c4), + (0x08, 0xae2eabe2a8), + (0x10, 0x1e4f43e470)] + for value in values: + top = chk >> 35 + chk = ((chk & 0x07ffffffff) << 5) ^ value + for i in generator: + if top & i[0] != 0: + chk ^= i[1] + return chk ^ 1 + + +def prefix_expand(prefix): + return [ord(x) & 0x1f for x in prefix] + [0] + + +def calculate_checksum(prefix, payload): + poly = polymod(prefix_expand(prefix) + payload + [0, 0, 0, 0, 0, 0, 0, 0]) + out = list() + for i in range(8): + out.append((poly >> 5 * (7 - i)) & 0x1f) + return out + + +def verify_checksum(prefix, payload): + return polymod(prefix_expand(prefix) + payload) == 0 + + +def b32decode(inputs): + out = list() + for letter in inputs: + out.append(CHARSET.find(letter)) + return out + + +def b32encode(inputs): + out = '' + for char_code in inputs: + out += CHARSET[char_code] + return out + + +def convertbits(data, frombits, tobits, pad=True): + acc = 0 + bits = 0 + ret = [] + maxv = (1 << tobits) - 1 + max_acc = (1 << (frombits + tobits - 1)) - 1 + for value in data: + if value < 0 or (value >> frombits): + return None + acc = ((acc << frombits) | value) & max_acc + bits += frombits + while bits >= tobits: + bits -= tobits + ret.append((acc >> bits) & maxv) + if pad: + if bits: + ret.append((acc << (tobits - bits)) & maxv) + elif bits >= frombits or ((acc << (tobits - bits)) & maxv): + return None + return ret diff --git a/dependencies/moneropy.py b/dependencies/moneropy.py new file mode 100755 index 0000000..a05aaed --- /dev/null +++ b/dependencies/moneropy.py @@ -0,0 +1,185 @@ +# MoneroPy - A python toolbox for Monero +# Copyright (C) 2016 The MoneroPy Developers. +# +# MoneroPy is released under the BSD 3-Clause license. Use and redistribution of +# this software is subject to the license terms in the LICENSE file found in the +# top-level directory of this distribution. +# +# Modified by emesik and rooterkyberian: +# + optimized +# + proper exceptions instead of returning errors as results + +__alphabet = [ + ord(s) for s in "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" +] +__b58base = 58 +__UINT64MAX = 2 ** 64 +__encodedBlockSizes = [0, 2, 3, 5, 6, 7, 9, 10, 11] +__fullBlockSize = 8 +__fullEncodedBlockSize = 11 + + +def _hexToBin(hex_): + if len(hex_) % 2 != 0: + raise ValueError("Hex string has invalid length: %d" % len(hex_)) + return [int(hex_[i : i + 2], 16) for i in range(0, len(hex_), 2)] + + +def _binToHex(bin_): + return "".join("%02x" % int(b) for b in bin_) + + +def _uint8be_to_64(data): + if not (1 <= len(data) <= 8): + raise ValueError("Invalid input length: %d" % len(data)) + + res = 0 + for b in data: + res = res << 8 | b + return res + + +def _uint64_to_8be(num, size): + if size < 1 or size > 8: + raise ValueError("Invalid input length: %d" % size) + res = [0] * size + + twopow8 = 2 ** 8 + for i in range(size - 1, -1, -1): + res[i] = num % twopow8 + num = num // twopow8 + + return res + + +def encode_block(data, buf, index): + l_data = len(data) + + if l_data < 1 or l_data > __fullEncodedBlockSize: + raise ValueError("Invalid block length: %d" % l_data) + + num = _uint8be_to_64(data) + i = __encodedBlockSizes[l_data] - 1 + + while num > 0: + remainder = num % __b58base + num = num // __b58base + buf[index + i] = __alphabet[remainder] + i -= 1 + + return buf + + +def encode(hex): + """Encode hexadecimal string as base58 (ex: encoding a Monero address).""" + data = _hexToBin(hex) + l_data = len(data) + + if l_data == 0: + return "" + + full_block_count = l_data // __fullBlockSize + last_block_size = l_data % __fullBlockSize + res_size = ( + full_block_count * __fullEncodedBlockSize + __encodedBlockSizes[last_block_size] + ) + + res = bytearray([__alphabet[0]] * res_size) + + for i in range(full_block_count): + res = encode_block( + data[(i * __fullBlockSize) : (i * __fullBlockSize + __fullBlockSize)], + res, + i * __fullEncodedBlockSize, + ) + + if last_block_size > 0: + res = encode_block( + data[ + (full_block_count * __fullBlockSize) : ( + full_block_count * __fullBlockSize + last_block_size + ) + ], + res, + full_block_count * __fullEncodedBlockSize, + ) + + return bytes(res).decode("ascii") + + +def decode_block(data, buf, index): + l_data = len(data) + + if l_data < 1 or l_data > __fullEncodedBlockSize: + raise ValueError("Invalid block length: %d" % l_data) + + res_size = __encodedBlockSizes.index(l_data) + if res_size <= 0: + raise ValueError("Invalid block size: %d" % res_size) + + res_num = 0 + order = 1 + for i in range(l_data - 1, -1, -1): + digit = __alphabet.index(data[i]) + if digit < 0: + raise ValueError("Invalid symbol: %s" % data[i]) + + product = order * digit + res_num + if product > __UINT64MAX: + raise ValueError( + "Overflow: %d * %d + %d = %d" % (order, digit, res_num, product) + ) + + res_num = product + order = order * __b58base + + if res_size < __fullBlockSize and 2 ** (8 * res_size) <= res_num: + raise ValueError("Overflow: %d doesn't fit in %d bit(s)" % (res_num, res_size)) + + tmp_buf = _uint64_to_8be(res_num, res_size) + buf[index : index + len(tmp_buf)] = tmp_buf + + return buf + + +def decode(enc): + """Decode a base58 string (ex: a Monero address) into hexidecimal form.""" + enc = bytearray(enc, encoding="ascii") + l_enc = len(enc) + + if l_enc == 0: + return "" + + full_block_count = l_enc // __fullEncodedBlockSize + last_block_size = l_enc % __fullEncodedBlockSize + try: + last_block_decoded_size = __encodedBlockSizes.index(last_block_size) + except ValueError: + raise ValueError("Invalid encoded length: %d" % l_enc) + + data_size = full_block_count * __fullBlockSize + last_block_decoded_size + + data = bytearray(data_size) + for i in range(full_block_count): + data = decode_block( + enc[ + (i * __fullEncodedBlockSize) : ( + i * __fullEncodedBlockSize + __fullEncodedBlockSize + ) + ], + data, + i * __fullBlockSize, + ) + + if last_block_size > 0: + data = decode_block( + enc[ + (full_block_count * __fullEncodedBlockSize) : ( + full_block_count * __fullEncodedBlockSize + last_block_size + ) + ], + data, + full_block_count * __fullBlockSize, + ) + + return _binToHex(data) diff --git a/dependencies/segwit_addr.py b/dependencies/segwit_addr.py new file mode 100755 index 0000000..84c7241 --- /dev/null +++ b/dependencies/segwit_addr.py @@ -0,0 +1,139 @@ + +# Copyright (c) 2017, 2020 Pieter Wuille +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +"""Reference implementation for Bech32/Bech32m and segwit addresses.""" + + +from enum import Enum + +class Encoding(Enum): + """Enumeration type to list the various supported encodings.""" + BECH32 = 1 + BECH32M = 2 + +CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" +BECH32M_CONST = 0x2bc830a3 + +def bech32_polymod(values): + """Internal function that computes the Bech32 checksum.""" + generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] + chk = 1 + for value in values: + top = chk >> 25 + chk = (chk & 0x1ffffff) << 5 ^ value + for i in range(5): + chk ^= generator[i] if ((top >> i) & 1) else 0 + return chk + + +def bech32_hrp_expand(hrp): + """Expand the HRP into values for checksum computation.""" + return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp] + + +def bech32_verify_checksum(hrp, data): + """Verify a checksum given HRP and converted data characters.""" + const = bech32_polymod(bech32_hrp_expand(hrp) + data) + if const == 1: + return Encoding.BECH32 + if const == BECH32M_CONST: + return Encoding.BECH32M + return None + +def bech32_create_checksum(hrp, data, spec): + """Compute the checksum values given HRP and data.""" + values = bech32_hrp_expand(hrp) + data + const = BECH32M_CONST if spec == Encoding.BECH32M else 1 + polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const + return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] + + +def bech32_encode(hrp, data, spec): + """Compute a Bech32 string given HRP and data values.""" + combined = data + bech32_create_checksum(hrp, data, spec) + return hrp + '1' + ''.join([CHARSET[d] for d in combined]) + +def bech32_decode(bech): + """Validate a Bech32/Bech32m string, and determine HRP and data.""" + if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or + (bech.lower() != bech and bech.upper() != bech)): + return (None, None, None) + bech = bech.lower() + pos = bech.rfind('1') + if pos < 1 or pos + 7 > len(bech) or len(bech) > 90: + return (None, None, None) + if not all(x in CHARSET for x in bech[pos+1:]): + return (None, None, None) + hrp = bech[:pos] + data = [CHARSET.find(x) for x in bech[pos+1:]] + spec = bech32_verify_checksum(hrp, data) + if spec is None: + return (None, None, None) + return (hrp, data[:-6], spec) + +def convertbits(data, frombits, tobits, pad=True): + """General power-of-2 base conversion.""" + acc = 0 + bits = 0 + ret = [] + maxv = (1 << tobits) - 1 + max_acc = (1 << (frombits + tobits - 1)) - 1 + for value in data: + if value < 0 or (value >> frombits): + return None + acc = ((acc << frombits) | value) & max_acc + bits += frombits + while bits >= tobits: + bits -= tobits + ret.append((acc >> bits) & maxv) + if pad: + if bits: + ret.append((acc << (tobits - bits)) & maxv) + elif bits >= frombits or ((acc << (tobits - bits)) & maxv): + return None + return ret + + +def decode(hrp, addr): + """Decode a segwit address.""" + hrpgot, data, spec = bech32_decode(addr) + if hrpgot != hrp: + return (None, None) + decoded = convertbits(data[1:], 5, 8, False) + if decoded is None or len(decoded) < 2 or len(decoded) > 40: + return (None, None) + if data[0] > 16: + return (None, None) + if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32: + return (None, None) + if data[0] == 0 and spec != Encoding.BECH32 or data[0] != 0 and spec != Encoding.BECH32M: + return (None, None) + return (data[0], decoded) + + +def encode(hrp, witver, witprog): + """Encode a segwit address.""" + spec = Encoding.BECH32 if witver == 0 else Encoding.BECH32M + ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5), spec) + if decode(hrp, ret) == (None, None): + return None + return ret + diff --git a/functions.py b/functions.py new file mode 100755 index 0000000..dc75481 --- /dev/null +++ b/functions.py @@ -0,0 +1,58 @@ +import dependencies.segwit_addr as segwit_addr +import dependencies.bchconvert as bchconvert +import dependencies.moneropy as moneropy +import base58, hashlib, binascii +import socket + +def decodeBase58(address): + decoded = base58.b58decode(address).hex() + prefixAndHash = decoded[:len(decoded)-8] + checksum = decoded[len(decoded)-8:] + hash = prefixAndHash + for _ in range(1,3): + hash = hashlib.sha256(binascii.unhexlify(hash)).hexdigest() + if(checksum == hash[:8]): + return True + return False + +def decodeMonero(address): + # decode monero address + if len(address) == 95 or len(address) == 106: + # Decode the address from Base58 + decoded = moneropy.decode(address) + + # Verify address type byte + if decoded[0:2] not in ["12", "2a", "13"]: + return False + + # Verify checksum + # later + return True + return False + +def checksumCheck(method, address): + match method.lower(): + case 'btc': + return decodeBase58(address) if address[0] == '1' or address[0] == '3' else True if address[0:3] == 'bc1' and segwit_addr.decode("bc", address)[0] != None else False + case 'ltc': + return decodeBase58(address) if address[0] == '3' or address[0] == 'M' or address[0] == 'L' else True if address[0:4] == 'ltc1' and segwit_addr.decode("ltc", address)[0] != None else False + case 'bch': + return bchconvert.is_valid(address) if address[0] == '1' else True if bchconvert.is_valid('bitcoincash:'+address) == True else False + case 'zec': + return decodeBase58(address) if address[0] == 't' or address[0] == 'z' else False + case 'xmr': + #needs new function to check if address is valid, a decoder maybe + return decodeMonero(address) + case _: + return False + +def validDns(d): + try: + a = socket.gethostbyname(d) + except: + return False + # ip validation + if a.split('.')[0] in ['127', '0'] or '.'.join([a.split('.')[0], a.split('.')[1]]) == '192.168' or a in ['1.1.1.1','2.2.2.2', '3.3.3.3']: + return False + else: + return True \ No newline at end of file diff --git a/main.py b/main.py new file mode 100755 index 0000000..3d4427f --- /dev/null +++ b/main.py @@ -0,0 +1,139 @@ +from typing import Optional, Union +from urllib.parse import urlparse +from fastapi import FastAPI, Request +from fastapi.responses import JSONResponse +import time, socket +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from sqlalchemy.pool import QueuePool +import uvicorn + +#local +from rpcs import RPCHost, RPCXMR +from models import Return +from meta import Queue +from functions import checksumCheck, validDns +import config + +class ErrorException(Exception): + def __init__(self, code: str, status: str, status_message: str): + self.status = status + self.code = code + self.status_message = status_message + +app = FastAPI() + +@app.get("/") +def root(): + return + +@app.exception_handler(ErrorException) +def unicorn_exception_handler(request: Request, exc: ErrorException): + return JSONResponse( + status_code=exc.code, + content={ + "status": f"{exc.status}", + "message": f"{exc.status_message}" + }, + ) + +@app.get("/api/receive") +def receive(method: str, address: str, callback: Union[str, None] = None): + if not method: + raise ErrorException(code=422,status="error",status_message='Empty method used') + if not address: + raise ErrorException(code=422,status="error",status_message='Empty destination address') + if method.lower() not in ['btc', 'btct', 'ltc', 'doge', 'zec', 'bch', 'xmr']: + raise ErrorException(code=422,status="error",status_message='Invalid method used') + if checksumCheck(method.lower(), address) == False: + raise ErrorException(code=422,status="error",status_message='Invalid Destination Address') + + if callback: + try: + data = urlparse(callback) + #scheme validation + if data.scheme == 'http' or data.scheme == 'https': + #domain validation + if validDns(data.netloc) != True: + raise ErrorException(code=422,status="error",status_message='Invalid callback: domain name does not resolve') + else: + raise ErrorException(code=422,status="error",status_message='Invalid callback: wrong url scheme, we accept http or https only') + except: + callback = 'None' + callback_req_n = 0 + else: + callback = 'None' + callback_req_n = 1 + + ## RPC connection to Demons + match method.upper(): + case 'BTC' | 'LTC' | 'ZEC' | 'BCH' | 'BTCT' | 'DOGE': + rpc = RPCHost("http://%s:%s@%s:%s" % (config.rpcs[method.upper()]['user'], config.rpcs[method.upper()]['pass'], config.rpcs[method.upper()]['host'], config.rpcs[method.upper()]['port'])) + case 'XMR': + rpc = RPCXMR(config.rpcs[method.upper()]['host'], config.rpcs[method.upper()]['user'], config.rpcs[method.upper()]['pass']) + case _: + raise ErrorException(code=422,status="error",status_message='RPC undergoing maintenance') + + ## checking if wallet is loaded + # wallet = rpc.call('createwallet','test',False,False,'exdTHnRJ1BxZfBvU',False,True,True,False) + + try: + match method.upper(): + case 'XMR': + wallet = rpc.call('create_address',{"account_index": 0}) + wallet = wallet['address'] + case 'LTC' | 'ZEC' | 'DOGE': + #wallet = rpc.call('createwallet','test',False,False,'exdTHnRJ1BxZfBvU',False,False,True) + wallet = rpc.call('getnewaddress') + case 'BTC' | 'BTCT': + #wallet = rpc.call('createwallet','test',False,False,'exdTHnRJ1BxZfBvU',False,True,True,False) + wallet = rpc.call('getnewaddress','', 'p2sh-segwit') + case 'BCH': + wallet = rpc.call('getnewaddress') + wallet = wallet.replace('bitcoincash:', '') + case _: + ## notify admin about the error + raise ErrorException(code=422, status="error", status_message='RPC unavailable') + except Exception as error: + ## notify admin about the error + # Failed to connect for remote procedure call. + raise ErrorException(code=422, status="error", status_message=error) + + + ## SQL Connection with SQLAlchemy + engine = create_engine( + "mysql+pymysql://%s:%s@%s:%s/dev" % (config.DB['user'], config.DB['pass'], config.DB['host'], config.DB['port']), + poolclass=QueuePool, + #echo=True, #debug sqlalchemy + pool_size=4, + max_overflow=0, + pool_timeout=15, # Wait 15s for a connection + ) + Session = sessionmaker(bind = engine) + session = Session() + + if wallet: + try: + q = Queue(txhash = 'None', time = int(time.time()), account = method.upper(), fee = config.fee['REGULAR'][method.upper()], ready = 0, confirmations =0, callbackurl = callback, generated_address = wallet, destination = address, balance_received = '0.00000000', callback_req = callback_req_n, ip = socket.gethostbyname(socket.gethostname()) , hostname = socket.gethostname(), merchantId = 'None', dateTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + session.add(q) + session.commit() + session.close() + except Exception as error: + ## notify admin about the error + raise ErrorException(code=422,status="error",status_message='Invalid response from dbServer') + else: + ## notify admin about the error + raise ErrorException(code=422,status="error",status_message='Invalid response from rpcServer') + + return Return( + address = wallet, + destination = address, + callback_url = callback, + fee = config.fee['REGULAR'][method.upper()], + status = 'success' + ) + +# Run +if __name__ == '__main__': + uvicorn.run('main:app', host='0.0.0.0') + #workers=4 (doesn't work with reload) \ No newline at end of file diff --git a/meta.py b/meta.py new file mode 100644 index 0000000..26129ae --- /dev/null +++ b/meta.py @@ -0,0 +1,24 @@ +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import Column, Integer, String + +Base = declarative_base() + +class Queue(Base): + __tablename__ = 'queue' + + id = Column(Integer, primary_key = True) + txhash = Column(String) + time = Column(String) + account = Column(String) + fee = Column(String) + ready = Column(String) + confirmations = Column(String) + callbackurl = Column(String) + generated_address = Column(String) + destination = Column(String) + balance_received = Column(String) + callback_req = Column(String) + ip = Column(String) + hostname = Column(String) + merchantId = Column(String) + dateTime = Column(String) \ No newline at end of file diff --git a/models.py b/models.py new file mode 100755 index 0000000..ffe52ab --- /dev/null +++ b/models.py @@ -0,0 +1,15 @@ +from decimal import Decimal +from typing import Optional +from pydantic import BaseModel + +class Receive(BaseModel): + method: str + address: str + callback: Optional[str] + +class Return(BaseModel): + address: str + destination: str + callback_url: Optional[str] + fee: Decimal + status: str diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1024b0d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,18 @@ +anyio==3.6.2 +base58==2.1.1 +certifi==2022.12.7 +charset-normalizer==3.0.1 +click==8.1.3 +fastapi==0.92.0 +greenlet==2.0.1 +h11==0.14.0 +idna==3.4 +pydantic==1.10.5 +PyMySQL==1.0.2 +requests==2.28.2 +sniffio==1.3.0 +SQLAlchemy==2.0.4 +starlette==0.25.0 +typing_extensions==4.5.0 +urllib3==1.26.14 +uvicorn==0.20.0 diff --git a/rpcs.py b/rpcs.py new file mode 100755 index 0000000..3210dd2 --- /dev/null +++ b/rpcs.py @@ -0,0 +1,66 @@ +import time, json, requests +from requests.auth import HTTPDigestAuth + +class RPCHost(object): + def __init__(self, url): + self._session = requests.Session() + self._url = url + self._headers = {'content-type': 'application/json'} + def call(self, rpcMethod, *params): + payload = json.dumps({"method": rpcMethod, "params": list(params), "jsonrpc": "2.0"}) + tries = 3 + hadConnectionFailures = False + while True: + try: + response = self._session.post(self._url, headers=self._headers, data=payload, timeout=15) + except requests.exceptions.ConnectionError: + tries -= 1 + if tries == 0: + raise Exception('Failed to connect for remote procedure call.') + hadFailedConnections = True + print("Couldn't connect for remote procedure call, will sleep for two seconds and then try again ({} more tries)".format(tries)) + #time.sleep(2) + else: + if hadConnectionFailures: + print('Connected for remote procedure call after retry.') + break + if not response.status_code in (200, 500): + raise Exception('RPC connection failure: ' + str(response.status_code) + ' ' + response.reason) + responseJSON = response.json() + if 'error' in responseJSON and responseJSON['error'] != None: + raise Exception('Error in RPC call: ' + str(responseJSON['error'])) + return responseJSON['result'] + +# class +class RPCXMR(object): + def __init__(self, url, user, password): + self._session = requests.Session() + self._url = url + self._user = user + self._pass = password + self._headers = {} + + def call(self, rpcMethod, params): + payload = json.dumps({"method": rpcMethod, "params": params, "jsonrpc": "2.0"}) + tries = 3 + hadConnectionFailures = False + while True: + try: + response = self._session.post(self._url, headers=self._headers, data=payload, auth=HTTPDigestAuth(self._user, self._pass), timeout=15) + except requests.exceptions.ConnectionError: + tries -= 1 + if tries == 0: + raise Exception('Failed to connect for remote procedure call.') + hadFailedConnections = True + print("Couldn't connect for remote procedure call, will sleep for two seconds and then try again ({} more tries)".format(tries)) + #time.sleep(2) + else: + if hadConnectionFailures: + print('Connected for remote procedure call after retry.') + break + if not response.status_code in (200, 500): + raise Exception('RPC connection failure: ' + str(response.status_code) + ' ' + response.reason) + responseJSON = response.json() + if 'error' in responseJSON and responseJSON['error'] != None: + raise Exception('Error in RPC call: ' + str(responseJSON['error'])) + return responseJSON['result'] \ No newline at end of file diff --git a/scratch_file b/scratch_file new file mode 100644 index 0000000..b315c66 --- /dev/null +++ b/scratch_file @@ -0,0 +1,183 @@ +''' + +def checksumCheck(method, address): + if method == 'btc': + if address[0] == '1' or address[0] == '3': + if decodeBase58(address): + return True + return False + elif address[0:3] == 'bc1': + witver = segwit_addr.decode("bc", address) + if witver[0] == None: + return False + return True + else: + return False + elif method == 'ltc': + if address[0] == '3' or address[0] == 'M' or address[0] == 'L': + if decodeBase58(address): + return True + return False + elif address[0:4] == 'ltc1': + witver = segwit_addr.decode("ltc", address) + if witver[0] != None: + return True + return False + else: + return False + elif method == 'bch': + if address[0] == '1': + if bchconvert.is_valid(address) == True: + return True + return False + elif bchconvert.is_valid('bitcoincash:'+address) == True: + return True + return False + elif method == 'zec': + if address[0] == 't' or address[0] == 'z': + if decodeBase58(address): + return True + return False + else: + return True + elif method == 'xmr': + length = len(address) + if length == 95: + checksum = moneropy.decode(address) + start = checksum[0:2].lower() + if start == '12' or start == '2a': + return True + return False + elif length == 106: + checksum = moneropy.decode(address) + start = checksum[0:2].lower() + if start == '13': + return True + return False + else: + return False + + else: + return False +''' + +''' + k = sha3.keccak_256() + print("First 65: " + str(address_type[:-65])) + print(hash64) + k.update(hash64) + a = k.hexdigest() + print("sha3 digest: " + a) + print("base58 decoded: " + address_type) + first4 = a[:-4] + last = address_type[-4:] + ''' + return start + + + + + ''' + valid = re.compile(r"^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$") + if valid.match(address) is None: + return False + else: + return True + + + else: + return "END" + ''' + + + + + + + + + +def validDns(d): + b = False + try: + a = socket.gethostbyname(d) + b = True + except: + return False + # ip validation + if b == True: + if a.split('.')[0] in ['127', '0']: + return False + if '.'.join([a.split('.')[0], a.split('.')[1]]) == '192.168': + return False + if a in ['1.1.1.1','2.2.2.2', '3.3.3.3']: + return False + return True + + +import base58 +import hashlib + +# Modified Base58 alphabet used in Monero +MONERO_BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' + +def encode_monero_address(address, version): + # Compute checksum + hash1 = hashlib.sha256(bytes.fromhex(version + address)).digest() + hash2 = hashlib.sha256(hash1).digest() + checksum = hash2[:4] + + # Concatenate version, address, and checksum + data = bytes.fromhex(version + address) + checksum + + # Encode using modified Base58 alphabet + encoded = base58.b58encode(data, alphabet=MONERO_BASE58_ALPHABET) + + return encoded.decode() + +def decode_monero_address(encoded): + # Decode using modified Base58 alphabet + data = base58.b58decode(encoded, alphabet=MONERO_BASE58_ALPHABET) + + # Extract version, address, and checksum + version = data[:2].hex() + address = data[2:-4].hex() + checksum = data[-4:] + + # Verify checksum + hash1 = hashlib.sha256(bytes.fromhex(version + address)).digest() + hash2 = hashlib.sha256(hash1).digest() + if checksum != hash2[:4]: + raise ValueError('Invalid checksum') + + return version, address + + +def decode_monero_address(encoded): + from Crypto.Hash import keccak + import binascii + # Decode using modified Base58 alphabet + print(encoded) + data = moneropy.decode(encoded) + print(data) + + # Extract version, address, and checksum + version = data[:2].encode() + address1 = data[2:66].encode() + address2 = data[66:-8].encode() + checksum = data[-8:] + + print(str(version + address1 + address2)) + print(hex(version)) + + + # Verify checksum + #keccak256 = keccak.new(digest_bits=256) + #keccak256.update(version + address1 + address2) + #print(keccak256.hexdigest()) + + + #print("Keccak256:", binascii.hexlify(keccak256)) + + #print(binascii.hexlify(keccak256), checksum) + return False \ No newline at end of file diff --git a/sql_app/database.py b/sql_app/database.py new file mode 100644 index 0000000..0093632 --- /dev/null +++ b/sql_app/database.py @@ -0,0 +1,15 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +import config + +# SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db" +# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db" + +# create_engine("mysql+pymysql://%:%@%:%/dev", connect_args={"check_same_thread": False}) % (config.DB('user'), config.DB('pass'), config.DB('host'), config.DB('port')) + +engine = create_engine("mysql+pymysql://%:%@%:%/dev") % (config.DB('user'), config.DB('pass'), config.DB('host'), config.DB('port')) + +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +Base = declarative_base() diff --git a/sql_app/models.py b/sql_app/models.py new file mode 100644 index 0000000..16420ea --- /dev/null +++ b/sql_app/models.py @@ -0,0 +1,25 @@ +from decimal import Decimal +from sqlalchemy import Boolean, Column, ForeignKey, Integer, String +from sqlalchemy.orm import relationship + +from .database import Base + + +class Queue(Base): + __tablename__ = "queue" + + id = Column(Integer, primary_key=True, index=True) + txhash = Column(String, unique=True, index=True) + accountId = Column(Integer) + method = Column(String) + fee = Column(Decimal) + status = Column(Integer) + confirmations = Column(Integer) + callback_url = Column(String) + local_address = Column(String) + remote_address = Column(String) + balance = Column(Decimal) + callback_requirement = Column(Integer) + unixtime = Column(Integer) + date = Column(String) + ip = Column(String) diff --git a/sql_app/schemas.py b/sql_app/schemas.py new file mode 100644 index 0000000..e69de29 diff --git a/sub.py b/sub.py new file mode 100644 index 0000000..2a57360 --- /dev/null +++ b/sub.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +""" + ZMQ example using python3's asyncio + + Litecoind should be started with the command line arguments: + litecoind -testnet -daemon \ + -zmqpubrawtx=tcp://127.0.0.1:28332 \ + -zmqpubrawblock=tcp://127.0.0.1:28332 \ + -zmqpubhashtx=tcp://127.0.0.1:28332 \ + -zmqpubhashblock=tcp://127.0.0.1:28332 \ + -zmqpubsequence=tcp://127.0.0.1:28332 + + We use the asyncio library here. `self.handle()` installs itself as a + future at the end of the function. Since it never returns with the event + loop having an empty stack of futures, this creates an infinite loop. An + alternative is to wrap the contents of `handle` inside `while True`. + + A blocking example using python 2.7 can be obtained from the git history: + https://github.com/bitcoin/bitcoin/blob/37a7fe9e440b83e2364d5498931253937abe9294/contrib/zmq/zmq_sub.py +""" + +import binascii +import asyncio +import zmq +import zmq.asyncio +import signal +import struct +import sys +import time, requests, json + +if (sys.version_info.major, sys.version_info.minor) < (3, 5): + print("This example only works with Python 3.5 and greater") + sys.exit(1) + +class RPCHost(object): + def __init__(self, url): + self._session = requests.Session() + self._url = url + self._headers = {'content-type': 'application/json'} + def call(self, rpcMethod, *params): + payload = json.dumps({"method": rpcMethod, "params": list(params), "jsonrpc": "2.0"}) + tries = 5 + hadConnectionFailures = False + while True: + try: + response = self._session.post(self._url, headers=self._headers, data=payload) + except requests.exceptions.ConnectionError: + tries -= 1 + if tries == 0: + raise Exception('Failed to connect for remote procedure call.') + hadFailedConnections = True + print("Couldn't connect for remote procedure call, will sleep for five seconds and then try again ({} more tries)".format(tries)) + time.sleep(10) + else: + if hadConnectionFailures: + print('Connected for remote procedure call after retry.') + break + if not response.status_code in (200, 500): + raise Exception('RPC connection failure: ' + str(response.status_code) + ' ' + response.reason) + responseJSON = response.json() + if 'error' in responseJSON and responseJSON['error'] != None: + raise Exception('Error in RPC call: ' + str(responseJSON['error'])) + return responseJSON['result'] + +ZMQ_PORT = 28442 +SERVER_IP = '172.16.0.10' +SERVER_PORT = 8442 +SERVER_USER = 'litecoinrpc' +SERVER_PASS = 'uhalalala' + +serverURL = "http://%s:%s@%s:%s" % (SERVER_USER, SERVER_PASS, SERVER_IP, SERVER_PORT) +rpc = RPCHost(serverURL) + +class ZMQHandler(): + def __init__(self): + self.loop = asyncio.get_event_loop() + self.zmqContext = zmq.asyncio.Context() + + self.zmqSubSocket = self.zmqContext.socket(zmq.SUB) + self.zmqSubSocket.setsockopt(zmq.RCVHWM, 0) + #self.zmqSubSocket.setsockopt_string(zmq.SUBSCRIBE, "") + #self.zmqSubSocket.setsockopt_string(zmq.SUBSCRIBE, "hashtx") + #self.zmqSubSocket.setsockopt_string(zmq.SUBSCRIBE, "rawblock") + #self.zmqSubSocket.setsockopt_string(zmq.SUBSCRIBE, "rawtx") + #self.zmqSubSocket.setsockopt_string(zmq.SUBSCRIBE, "sequence") + self.zmqSubSocket.connect("tcp://%s:%i" % (SERVER_IP, ZMQ_PORT)) + + async def handle(self) : + topic, body, seq = await self.zmqSubSocket.recv_multipart() + sequence = "Unknown" + if len(seq) == 4: + sequence = str(struct.unpack('