Main Code
This commit is contained in:
parent
969a44ff0c
commit
0be3c6b5f4
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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']
|
|
@ -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
|
|
@ -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()
|
|
@ -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)
|
|
@ -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('<I', seq)[-1])
|
||||||
|
if topic == b"hashblock":
|
||||||
|
print('- HASH BLOCK ('+sequence+') -')
|
||||||
|
print(binascii.hexlify(body))
|
||||||
|
elif topic == b"hashtx":
|
||||||
|
print('- HASH TX ('+sequence+') -')
|
||||||
|
print(binascii.hexlify(body))
|
||||||
|
elif topic == b"rawblock":
|
||||||
|
print('- RAW BLOCK HEADER ('+sequence+') -')
|
||||||
|
print(binascii.hexlify(body[:80]))
|
||||||
|
elif topic == b"rawtx":
|
||||||
|
print('- RAW TX ('+sequence+') -')
|
||||||
|
#decode = rpc.call('decoderawtransaction', binascii.hexlify(body).decode('utf-8'))
|
||||||
|
#data = json.dumps(decode)
|
||||||
|
#print(data)
|
||||||
|
|
||||||
|
#print(binascii.hexlify(body))
|
||||||
|
elif topic == b"sequence":
|
||||||
|
hash = binascii.hexlify(body[:32])
|
||||||
|
label = chr(body[32])
|
||||||
|
mempool_sequence = None if len(body) != 32+1+8 else struct.unpack("<Q", body[32+1:])[0]
|
||||||
|
print('- SEQUENCE ('+sequence+') -')
|
||||||
|
print(hash, label, mempool_sequence)
|
||||||
|
else:
|
||||||
|
print(binascii.hexlify(body))
|
||||||
|
# schedule ourselves to receive the next message
|
||||||
|
asyncio.ensure_future(self.handle())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.loop.add_signal_handler(signal.SIGINT, self.stop)
|
||||||
|
self.loop.create_task(self.handle())
|
||||||
|
self.loop.run_forever()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.loop.stop()
|
||||||
|
self.zmqContext.destroy()
|
||||||
|
|
||||||
|
daemon = ZMQHandler()
|
||||||
|
daemon.start()
|
Loading…
Reference in New Issue