Replace makeInt with make_int
This commit is contained in:
parent
4636d31ea9
commit
5d84d54e6f
@ -19,6 +19,11 @@ import secrets
|
|||||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||||
from enum import IntEnum, auto
|
from enum import IntEnum, auto
|
||||||
|
|
||||||
|
from .interface_part import PARTInterface
|
||||||
|
from .interface_btc import BTCInterface
|
||||||
|
from .interface_ltc import LTCInterface
|
||||||
|
from .interface_xmr import XMRInterface
|
||||||
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
from .util import (
|
from .util import (
|
||||||
COIN,
|
COIN,
|
||||||
@ -31,7 +36,7 @@ from .util import (
|
|||||||
decodeWif,
|
decodeWif,
|
||||||
toWIF,
|
toWIF,
|
||||||
getKeyID,
|
getKeyID,
|
||||||
makeInt,
|
make_int,
|
||||||
)
|
)
|
||||||
from .chainparams import (
|
from .chainparams import (
|
||||||
chainparams,
|
chainparams,
|
||||||
@ -417,6 +422,27 @@ class BasicSwap(BaseApp):
|
|||||||
'chain_lookups': chain_client_settings.get('chain_lookups', 'local'),
|
'chain_lookups': chain_client_settings.get('chain_lookups', 'local'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.coin_clients[coin]['connection_type'] == 'rpc':
|
||||||
|
if coin == Coins.XMR:
|
||||||
|
self.coin_clients[coin]['walletrpcport'] = chain_client_settings.get('walletrpcport', chainparams[coin][self.chain]['walletrpcport'])
|
||||||
|
if 'walletrpcpassword' in chain_client_settings:
|
||||||
|
self.coin_clients[coin]['walletrpcauth'] = chain_client_settings['walletrpcuser'] + ':' + chain_client_settings['walletrpcpassword']
|
||||||
|
else:
|
||||||
|
raise ValueError('Missing XMR wallet rpc credentials.')
|
||||||
|
self.coin_clients[coin]['interface'] = self.createInterface(coin)
|
||||||
|
|
||||||
|
def createInterface(self, coin):
|
||||||
|
if coin == Coins.PART:
|
||||||
|
return PARTInterface(self.coin_clients[coin])
|
||||||
|
elif coin == Coins.BTC:
|
||||||
|
return BTCInterface(self.coin_clients[coin])
|
||||||
|
elif coin == Coins.LTC:
|
||||||
|
return LTCInterface(self.coin_clients[coin])
|
||||||
|
elif coin == Coins.XMR:
|
||||||
|
return XMRInterface(self.coin_clients[coin])
|
||||||
|
else:
|
||||||
|
raise ValueError('Unknown coin type')
|
||||||
|
|
||||||
def setCoinRunParams(self, coin):
|
def setCoinRunParams(self, coin):
|
||||||
cc = self.coin_clients[coin]
|
cc = self.coin_clients[coin]
|
||||||
if cc['connection_type'] == 'rpc' and cc['rpcauth'] is None:
|
if cc['connection_type'] == 'rpc' and cc['rpcauth'] is None:
|
||||||
@ -1699,7 +1725,7 @@ class BasicSwap(BaseApp):
|
|||||||
continue
|
continue
|
||||||
# Verify amount
|
# Verify amount
|
||||||
if assert_amount:
|
if assert_amount:
|
||||||
assert(makeInt(o['amount']) == int(assert_amount)), 'Incorrect output amount in txn {}: {} != {}.'.format(assert_txid, makeInt(o['amount']), int(assert_amount))
|
assert(make_int(o['amount']) == int(assert_amount)), 'Incorrect output amount in txn {}: {} != {}.'.format(assert_txid, make_int(o['amount']), int(assert_amount))
|
||||||
|
|
||||||
if not sum_output:
|
if not sum_output:
|
||||||
if o['height'] > 0:
|
if o['height'] > 0:
|
||||||
@ -1711,7 +1737,7 @@ class BasicSwap(BaseApp):
|
|||||||
'index': o['vout'],
|
'index': o['vout'],
|
||||||
'height': o['height'],
|
'height': o['height'],
|
||||||
'n_conf': n_conf,
|
'n_conf': n_conf,
|
||||||
'value': makeInt(o['amount']),
|
'value': make_int(o['amount']),
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
sum_unspent += o['amount'] * COIN
|
sum_unspent += o['amount'] * COIN
|
||||||
@ -1744,7 +1770,7 @@ class BasicSwap(BaseApp):
|
|||||||
# Verify amount
|
# Verify amount
|
||||||
vout = getVoutByAddress(initiate_txn, p2sh)
|
vout = getVoutByAddress(initiate_txn, p2sh)
|
||||||
|
|
||||||
out_value = makeInt(initiate_txn['vout'][vout]['value'])
|
out_value = make_int(initiate_txn['vout'][vout]['value'])
|
||||||
assert(out_value == int(bid.amount)), 'Incorrect output amount in initiate txn {}: {} != {}.'.format(initiate_txnid_hex, out_value, int(bid.amount))
|
assert(out_value == int(bid.amount)), 'Incorrect output amount in initiate txn {}: {} != {}.'.format(initiate_txnid_hex, out_value, int(bid.amount))
|
||||||
|
|
||||||
bid.initiate_tx.conf = initiate_txn['confirmations']
|
bid.initiate_tx.conf = initiate_txn['confirmations']
|
||||||
@ -2442,8 +2468,8 @@ class BasicSwap(BaseApp):
|
|||||||
'deposit_address': self.getCachedAddressForCoin(coin),
|
'deposit_address': self.getCachedAddressForCoin(coin),
|
||||||
'name': chainparams[coin]['name'].capitalize(),
|
'name': chainparams[coin]['name'].capitalize(),
|
||||||
'blocks': blockchaininfo['blocks'],
|
'blocks': blockchaininfo['blocks'],
|
||||||
'balance': format8(makeInt(walletinfo['balance'])),
|
'balance': format8(make_int(walletinfo['balance'])),
|
||||||
'unconfirmed': format8(makeInt(walletinfo.get('unconfirmed_balance'))),
|
'unconfirmed': format8(make_int(walletinfo.get('unconfirmed_balance'))),
|
||||||
'synced': '{0:.2f}'.format(round(blockchaininfo['verificationprogress'], 2)),
|
'synced': '{0:.2f}'.format(round(blockchaininfo['verificationprogress'], 2)),
|
||||||
}
|
}
|
||||||
return rv
|
return rv
|
||||||
|
@ -14,8 +14,9 @@ class Coins(IntEnum):
|
|||||||
PART = 1
|
PART = 1
|
||||||
BTC = 2
|
BTC = 2
|
||||||
LTC = 3
|
LTC = 3
|
||||||
# DCR = 4
|
#DCR = 4
|
||||||
NMC = 5
|
NMC = 5
|
||||||
|
XMR = 6
|
||||||
|
|
||||||
|
|
||||||
chainparams = {
|
chainparams = {
|
||||||
@ -156,5 +157,26 @@ chainparams = {
|
|||||||
'min_amount': 1000,
|
'min_amount': 1000,
|
||||||
'max_amount': 100000 * COIN,
|
'max_amount': 100000 * COIN,
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
Coins.XMR: {
|
||||||
|
'name': 'monero',
|
||||||
|
'ticker': 'XMR',
|
||||||
|
'client': 'xmr',
|
||||||
|
'mainnet': {
|
||||||
|
'rpcport': 18081,
|
||||||
|
'walletrpcport': 18082,
|
||||||
|
},
|
||||||
|
'testnet': {
|
||||||
|
'rpcport': 28081,
|
||||||
|
'walletrpcport': 28082,
|
||||||
|
},
|
||||||
|
'regtest': {
|
||||||
|
'rpcport': 18081,
|
||||||
|
'walletrpcport': 18082,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CoinInterface:
|
||||||
|
pass
|
||||||
|
|
||||||
|
0
basicswap/contrib/MoneroPy/__init__.py
Normal file
0
basicswap/contrib/MoneroPy/__init__.py
Normal file
168
basicswap/contrib/MoneroPy/base58.py
Normal file
168
basicswap/contrib/MoneroPy/base58.py
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
__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:
|
||||||
|
return "Hex string has invalid length!"
|
||||||
|
return [int(hex[i*2:i*2+2], 16) for i in range(len(hex)//2)]
|
||||||
|
|
||||||
|
def _binToHex(bin):
|
||||||
|
return "".join([("0" + hex(int(bin[i])).split('x')[1])[-2:] for i in range(len(bin))])
|
||||||
|
|
||||||
|
def _strToBin(a):
|
||||||
|
return [ord(s) for s in a]
|
||||||
|
|
||||||
|
def _binToStr(bin):
|
||||||
|
return ''.join([chr(bin[i]) for i in range(len(bin))])
|
||||||
|
|
||||||
|
def _uint8be_to_64(data):
|
||||||
|
l_data = len(data)
|
||||||
|
|
||||||
|
if l_data < 1 or l_data > 8:
|
||||||
|
return "Invalid input length"
|
||||||
|
|
||||||
|
res = 0
|
||||||
|
switch = 9 - l_data
|
||||||
|
for i in range(l_data):
|
||||||
|
if switch == 1:
|
||||||
|
res = res << 8 | data[i]
|
||||||
|
elif switch == 2:
|
||||||
|
res = res << 8 | data[i]
|
||||||
|
elif switch == 3:
|
||||||
|
res = res << 8 | data[i]
|
||||||
|
elif switch == 4:
|
||||||
|
res = res << 8 | data[i]
|
||||||
|
elif switch == 5:
|
||||||
|
res = res << 8 | data[i]
|
||||||
|
elif switch == 6:
|
||||||
|
res = res << 8 | data[i]
|
||||||
|
elif switch == 7:
|
||||||
|
res = res << 8 | data[i]
|
||||||
|
elif switch == 8:
|
||||||
|
res = res << 8 | data[i]
|
||||||
|
else:
|
||||||
|
return "Impossible condition"
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _uint64_to_8be(num, size):
|
||||||
|
res = [0] * size;
|
||||||
|
if size < 1 or size > 8:
|
||||||
|
return "Invalid input length"
|
||||||
|
|
||||||
|
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:
|
||||||
|
return "Invalid block length: " + str(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 = [0] * res_size
|
||||||
|
for i in range(res_size):
|
||||||
|
res[i] = __alphabet[0]
|
||||||
|
|
||||||
|
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 _binToStr(res)
|
||||||
|
|
||||||
|
def decode_block(data, buf, index):
|
||||||
|
l_data = len(data)
|
||||||
|
|
||||||
|
if l_data < 1 or l_data > __fullEncodedBlockSize:
|
||||||
|
return "Invalid block length: " + l_data
|
||||||
|
|
||||||
|
res_size = __encodedBlockSizes.index(l_data)
|
||||||
|
if res_size <= 0:
|
||||||
|
return "Invalid block size"
|
||||||
|
|
||||||
|
res_num = 0
|
||||||
|
order = 1
|
||||||
|
for i in range(l_data-1, -1, -1):
|
||||||
|
digit = __alphabet.index(data[i])
|
||||||
|
if digit < 0:
|
||||||
|
return "Invalid symbol"
|
||||||
|
|
||||||
|
product = order * digit + res_num
|
||||||
|
if product > __UINT64MAX:
|
||||||
|
return "Overflow"
|
||||||
|
|
||||||
|
res_num = product
|
||||||
|
order = order * __b58base
|
||||||
|
|
||||||
|
if res_size < __fullBlockSize and 2**(8 * res_size) <= res_num:
|
||||||
|
return "Overflow 2"
|
||||||
|
|
||||||
|
tmp_buf = _uint64_to_8be(res_num, res_size)
|
||||||
|
for i in range(len(tmp_buf)):
|
||||||
|
buf[i+index] = tmp_buf[i]
|
||||||
|
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def decode(enc):
|
||||||
|
'''Decode a base58 string (ex: a Monero address) into hexidecimal form.'''
|
||||||
|
enc = _strToBin(enc)
|
||||||
|
l_enc = len(enc)
|
||||||
|
|
||||||
|
if l_enc == 0:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
full_block_count = l_enc // __fullEncodedBlockSize
|
||||||
|
last_block_size = l_enc % __fullEncodedBlockSize
|
||||||
|
last_block_decoded_size = __encodedBlockSizes.index(last_block_size)
|
||||||
|
|
||||||
|
if last_block_decoded_size < 0:
|
||||||
|
return "Invalid encoded length"
|
||||||
|
|
||||||
|
data_size = full_block_count * __fullBlockSize + last_block_decoded_size
|
||||||
|
|
||||||
|
data = [0] * 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)
|
486
basicswap/contrib/ellipticcurve.py
Normal file
486
basicswap/contrib/ellipticcurve.py
Normal file
@ -0,0 +1,486 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#
|
||||||
|
# Implementation of elliptic curves, for cryptographic applications.
|
||||||
|
#
|
||||||
|
# This module doesn't provide any way to choose a random elliptic
|
||||||
|
# curve, nor to verify that an elliptic curve was chosen randomly,
|
||||||
|
# because one can simply use NIST's standard curves.
|
||||||
|
#
|
||||||
|
# Notes from X9.62-1998 (draft):
|
||||||
|
# Nomenclature:
|
||||||
|
# - Q is a public key.
|
||||||
|
# The "Elliptic Curve Domain Parameters" include:
|
||||||
|
# - q is the "field size", which in our case equals p.
|
||||||
|
# - p is a big prime.
|
||||||
|
# - G is a point of prime order (5.1.1.1).
|
||||||
|
# - n is the order of G (5.1.1.1).
|
||||||
|
# Public-key validation (5.2.2):
|
||||||
|
# - Verify that Q is not the point at infinity.
|
||||||
|
# - Verify that X_Q and Y_Q are in [0,p-1].
|
||||||
|
# - Verify that Q is on the curve.
|
||||||
|
# - Verify that nQ is the point at infinity.
|
||||||
|
# Signature generation (5.3):
|
||||||
|
# - Pick random k from [1,n-1].
|
||||||
|
# Signature checking (5.4.2):
|
||||||
|
# - Verify that r and s are in [1,n-1].
|
||||||
|
#
|
||||||
|
# Version of 2008.11.25.
|
||||||
|
#
|
||||||
|
# Revision history:
|
||||||
|
# 2005.12.31 - Initial version.
|
||||||
|
# 2008.11.25 - Change CurveFp.is_on to contains_point.
|
||||||
|
#
|
||||||
|
# Written in 2005 by Peter Pearson and placed in the public domain.
|
||||||
|
|
||||||
|
def inverse_mod(a, m):
|
||||||
|
"""Inverse of a mod m."""
|
||||||
|
|
||||||
|
if a < 0 or m <= a:
|
||||||
|
a = a % m
|
||||||
|
|
||||||
|
# From Ferguson and Schneier, roughly:
|
||||||
|
|
||||||
|
c, d = a, m
|
||||||
|
uc, vc, ud, vd = 1, 0, 0, 1
|
||||||
|
while c != 0:
|
||||||
|
q, c, d = divmod(d, c) + (c,)
|
||||||
|
uc, vc, ud, vd = ud - q * uc, vd - q * vc, uc, vc
|
||||||
|
|
||||||
|
# At this point, d is the GCD, and ud*a+vd*m = d.
|
||||||
|
# If d == 1, this means that ud is a inverse.
|
||||||
|
|
||||||
|
assert d == 1
|
||||||
|
if ud > 0:
|
||||||
|
return ud
|
||||||
|
else:
|
||||||
|
return ud + m
|
||||||
|
|
||||||
|
|
||||||
|
def modular_sqrt(a, p):
|
||||||
|
# from http://eli.thegreenplace.net/2009/03/07/computing-modular-square-roots-in-python/
|
||||||
|
""" Find a quadratic residue (mod p) of 'a'. p
|
||||||
|
must be an odd prime.
|
||||||
|
|
||||||
|
Solve the congruence of the form:
|
||||||
|
x^2 = a (mod p)
|
||||||
|
And returns x. Note that p - x is also a root.
|
||||||
|
|
||||||
|
0 is returned is no square root exists for
|
||||||
|
these a and p.
|
||||||
|
|
||||||
|
The Tonelli-Shanks algorithm is used (except
|
||||||
|
for some simple cases in which the solution
|
||||||
|
is known from an identity). This algorithm
|
||||||
|
runs in polynomial time (unless the
|
||||||
|
generalized Riemann hypothesis is false).
|
||||||
|
"""
|
||||||
|
# Simple cases
|
||||||
|
#
|
||||||
|
if legendre_symbol(a, p) != 1:
|
||||||
|
return 0
|
||||||
|
elif a == 0:
|
||||||
|
return 0
|
||||||
|
elif p == 2:
|
||||||
|
return p
|
||||||
|
elif p % 4 == 3:
|
||||||
|
return pow(a, (p + 1) // 4, p)
|
||||||
|
|
||||||
|
# Partition p-1 to s * 2^e for an odd s (i.e.
|
||||||
|
# reduce all the powers of 2 from p-1)
|
||||||
|
#
|
||||||
|
s = p - 1
|
||||||
|
e = 0
|
||||||
|
while s % 2 == 0:
|
||||||
|
s /= 2
|
||||||
|
e += 1
|
||||||
|
|
||||||
|
# Find some 'n' with a legendre symbol n|p = -1.
|
||||||
|
# Shouldn't take long.
|
||||||
|
#
|
||||||
|
n = 2
|
||||||
|
while legendre_symbol(n, p) != -1:
|
||||||
|
n += 1
|
||||||
|
|
||||||
|
# Here be dragons!
|
||||||
|
# Read the paper "Square roots from 1; 24, 51,
|
||||||
|
# 10 to Dan Shanks" by Ezra Brown for more
|
||||||
|
# information
|
||||||
|
#
|
||||||
|
|
||||||
|
# x is a guess of the square root that gets better
|
||||||
|
# with each iteration.
|
||||||
|
# b is the "fudge factor" - by how much we're off
|
||||||
|
# with the guess. The invariant x^2 = ab (mod p)
|
||||||
|
# is maintained throughout the loop.
|
||||||
|
# g is used for successive powers of n to update
|
||||||
|
# both a and b
|
||||||
|
# r is the exponent - decreases with each update
|
||||||
|
#
|
||||||
|
x = pow(a, (s + 1) // 2, p)
|
||||||
|
b = pow(a, s, p)
|
||||||
|
g = pow(n, s, p)
|
||||||
|
r = e
|
||||||
|
|
||||||
|
while True:
|
||||||
|
t = b
|
||||||
|
m = 0
|
||||||
|
for m in range(r):
|
||||||
|
if t == 1:
|
||||||
|
break
|
||||||
|
t = pow(t, 2, p)
|
||||||
|
|
||||||
|
if m == 0:
|
||||||
|
return x
|
||||||
|
|
||||||
|
gs = pow(g, 2 ** (r - m - 1), p)
|
||||||
|
g = (gs * gs) % p
|
||||||
|
x = (x * gs) % p
|
||||||
|
b = (b * g) % p
|
||||||
|
r = m
|
||||||
|
|
||||||
|
|
||||||
|
def legendre_symbol(a, p):
|
||||||
|
""" Compute the Legendre symbol a|p using
|
||||||
|
Euler's criterion. p is a prime, a is
|
||||||
|
relatively prime to p (if p divides
|
||||||
|
a, then a|p = 0)
|
||||||
|
|
||||||
|
Returns 1 if a has a square root modulo
|
||||||
|
p, -1 otherwise.
|
||||||
|
"""
|
||||||
|
ls = pow(a, (p - 1) // 2, p)
|
||||||
|
return -1 if ls == p - 1 else ls
|
||||||
|
|
||||||
|
|
||||||
|
def jacobi_symbol(n, k):
|
||||||
|
"""Compute the Jacobi symbol of n modulo k
|
||||||
|
|
||||||
|
See http://en.wikipedia.org/wiki/Jacobi_symbol
|
||||||
|
|
||||||
|
For our application k is always prime, so this is the same as the Legendre symbol."""
|
||||||
|
assert k > 0 and k & 1, "jacobi symbol is only defined for positive odd k"
|
||||||
|
n %= k
|
||||||
|
t = 0
|
||||||
|
while n != 0:
|
||||||
|
while n & 1 == 0:
|
||||||
|
n >>= 1
|
||||||
|
r = k & 7
|
||||||
|
t ^= (r == 3 or r == 5)
|
||||||
|
n, k = k, n
|
||||||
|
t ^= (n & k & 3 == 3)
|
||||||
|
n = n % k
|
||||||
|
if k == 1:
|
||||||
|
return -1 if t else 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class CurveFp(object):
|
||||||
|
"""Elliptic Curve over the field of integers modulo a prime."""
|
||||||
|
def __init__(self, p, a, b):
|
||||||
|
"""The curve of points satisfying y^2 = x^3 + a*x + b (mod p)."""
|
||||||
|
self.__p = p
|
||||||
|
self.__a = a
|
||||||
|
self.__b = b
|
||||||
|
|
||||||
|
def p(self):
|
||||||
|
return self.__p
|
||||||
|
|
||||||
|
def a(self):
|
||||||
|
return self.__a
|
||||||
|
|
||||||
|
def b(self):
|
||||||
|
return self.__b
|
||||||
|
|
||||||
|
def contains_point(self, x, y):
|
||||||
|
"""Is the point (x,y) on this curve?"""
|
||||||
|
return (y * y - (x * x * x + self.__a * x + self.__b)) % self.__p == 0
|
||||||
|
|
||||||
|
|
||||||
|
class Point(object):
|
||||||
|
""" A point on an elliptic curve. Altering x and y is forbidding,
|
||||||
|
but they can be read by the x() and y() methods."""
|
||||||
|
def __init__(self, curve, x, y, order=None):
|
||||||
|
"""curve, x, y, order; order (optional) is the order of this point."""
|
||||||
|
self.__curve = curve
|
||||||
|
self.__x = x
|
||||||
|
self.__y = y
|
||||||
|
self.__order = order
|
||||||
|
# self.curve is allowed to be None only for INFINITY:
|
||||||
|
if self.__curve:
|
||||||
|
assert self.__curve.contains_point(x, y)
|
||||||
|
if order:
|
||||||
|
assert self * order == INFINITY
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
"""Return 1 if the points are identical, 0 otherwise."""
|
||||||
|
if self.__curve == other.__curve \
|
||||||
|
and self.__x == other.__x \
|
||||||
|
and self.__y == other.__y:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
"""Add one point to another point."""
|
||||||
|
|
||||||
|
# X9.62 B.3:
|
||||||
|
if other == INFINITY:
|
||||||
|
return self
|
||||||
|
if self == INFINITY:
|
||||||
|
return other
|
||||||
|
assert self.__curve == other.__curve
|
||||||
|
if self.__x == other.__x:
|
||||||
|
if (self.__y + other.__y) % self.__curve.p() == 0:
|
||||||
|
return INFINITY
|
||||||
|
else:
|
||||||
|
return self.double()
|
||||||
|
|
||||||
|
p = self.__curve.p()
|
||||||
|
|
||||||
|
l = ((other.__y - self.__y) * inverse_mod(other.__x - self.__x, p)) % p
|
||||||
|
|
||||||
|
x3 = (l * l - self.__x - other.__x) % p
|
||||||
|
y3 = (l * (self.__x - x3) - self.__y) % p
|
||||||
|
|
||||||
|
return Point(self.__curve, x3, y3)
|
||||||
|
|
||||||
|
def __sub__(self, other):
|
||||||
|
#The inverse of a point P=(xP,yP) is its reflexion across the x-axis : P′=(xP,−yP).
|
||||||
|
#If you want to compute Q−P, just replace yP by −yP in the usual formula for point addition.
|
||||||
|
|
||||||
|
# X9.62 B.3:
|
||||||
|
if other == INFINITY:
|
||||||
|
return self
|
||||||
|
if self == INFINITY:
|
||||||
|
return other
|
||||||
|
assert self.__curve == other.__curve
|
||||||
|
|
||||||
|
p = self.__curve.p()
|
||||||
|
#opi = inverse_mod(other.__y, p)
|
||||||
|
opi = -other.__y % p
|
||||||
|
#print(opi)
|
||||||
|
#print(-other.__y % p)
|
||||||
|
|
||||||
|
if self.__x == other.__x:
|
||||||
|
if (self.__y + opi) % self.__curve.p() == 0:
|
||||||
|
return INFINITY
|
||||||
|
else:
|
||||||
|
return self.double
|
||||||
|
|
||||||
|
l = ((opi - self.__y) * inverse_mod(other.__x - self.__x, p)) % p
|
||||||
|
|
||||||
|
x3 = (l * l - self.__x - other.__x) % p
|
||||||
|
y3 = (l * (self.__x - x3) - self.__y) % p
|
||||||
|
|
||||||
|
return Point(self.__curve, x3, y3)
|
||||||
|
|
||||||
|
def __mul__(self, e):
|
||||||
|
if self.__order:
|
||||||
|
e %= self.__order
|
||||||
|
if e == 0 or self == INFINITY:
|
||||||
|
return INFINITY
|
||||||
|
result, q = INFINITY, self
|
||||||
|
while e:
|
||||||
|
if e & 1:
|
||||||
|
result += q
|
||||||
|
e, q = e >> 1, q.double()
|
||||||
|
return result
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __mul__(self, other):
|
||||||
|
#Multiply a point by an integer.
|
||||||
|
|
||||||
|
def leftmost_bit( x ):
|
||||||
|
assert x > 0
|
||||||
|
result = 1
|
||||||
|
while result <= x: result = 2 * result
|
||||||
|
return result // 2
|
||||||
|
|
||||||
|
e = other
|
||||||
|
if self.__order: e = e % self.__order
|
||||||
|
if e == 0: return INFINITY
|
||||||
|
if self == INFINITY: return INFINITY
|
||||||
|
assert e > 0
|
||||||
|
|
||||||
|
# From X9.62 D.3.2:
|
||||||
|
|
||||||
|
e3 = 3 * e
|
||||||
|
negative_self = Point( self.__curve, self.__x, -self.__y, self.__order )
|
||||||
|
i = leftmost_bit( e3 ) // 2
|
||||||
|
result = self
|
||||||
|
# print "Multiplying %s by %d (e3 = %d):" % ( self, other, e3 )
|
||||||
|
while i > 1:
|
||||||
|
result = result.double()
|
||||||
|
if ( e3 & i ) != 0 and ( e & i ) == 0: result = result + self
|
||||||
|
if ( e3 & i ) == 0 and ( e & i ) != 0: result = result + negative_self
|
||||||
|
# print ". . . i = %d, result = %s" % ( i, result )
|
||||||
|
i = i // 2
|
||||||
|
|
||||||
|
return result
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __rmul__(self, other):
|
||||||
|
"""Multiply a point by an integer."""
|
||||||
|
|
||||||
|
return self * other
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self == INFINITY:
|
||||||
|
return "infinity"
|
||||||
|
return "(%d, %d)" % (self.__x, self.__y)
|
||||||
|
|
||||||
|
def inverse(self):
|
||||||
|
return Point(self.__curve, self.__x, -self.__y % self.__curve.p())
|
||||||
|
|
||||||
|
def double(self):
|
||||||
|
"""Return a new point that is twice the old."""
|
||||||
|
|
||||||
|
if self == INFINITY:
|
||||||
|
return INFINITY
|
||||||
|
|
||||||
|
# X9.62 B.3:
|
||||||
|
|
||||||
|
p = self.__curve.p()
|
||||||
|
a = self.__curve.a()
|
||||||
|
|
||||||
|
l = ((3 * self.__x * self.__x + a) * inverse_mod(2 * self.__y, p)) % p
|
||||||
|
|
||||||
|
x3 = (l * l - 2 * self.__x) % p
|
||||||
|
y3 = (l * (self.__x - x3) - self.__y) % p
|
||||||
|
|
||||||
|
return Point(self.__curve, x3, y3)
|
||||||
|
|
||||||
|
def x(self):
|
||||||
|
return self.__x
|
||||||
|
|
||||||
|
def y(self):
|
||||||
|
return self.__y
|
||||||
|
|
||||||
|
def pair(self):
|
||||||
|
return (self.__x, self.__y)
|
||||||
|
|
||||||
|
def curve(self):
|
||||||
|
return self.__curve
|
||||||
|
|
||||||
|
def order(self):
|
||||||
|
return self.__order
|
||||||
|
|
||||||
|
|
||||||
|
# This one point is the Point At Infinity for all purposes:
|
||||||
|
INFINITY = Point(None, None, None)
|
||||||
|
|
||||||
|
|
||||||
|
def __main__():
|
||||||
|
|
||||||
|
class FailedTest(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_add(c, x1, y1, x2, y2, x3, y3):
|
||||||
|
"""We expect that on curve c, (x1,y1) + (x2, y2 ) = (x3, y3)."""
|
||||||
|
p1 = Point(c, x1, y1)
|
||||||
|
p2 = Point(c, x2, y2)
|
||||||
|
p3 = p1 + p2
|
||||||
|
print("%s + %s = %s" % (p1, p2, p3))
|
||||||
|
if p3.x() != x3 or p3.y() != y3:
|
||||||
|
raise FailedTest("Failure: should give (%d,%d)." % (x3, y3))
|
||||||
|
else:
|
||||||
|
print(" Good.")
|
||||||
|
|
||||||
|
def test_double(c, x1, y1, x3, y3):
|
||||||
|
"""We expect that on curve c, 2*(x1,y1) = (x3, y3)."""
|
||||||
|
p1 = Point(c, x1, y1)
|
||||||
|
p3 = p1.double()
|
||||||
|
print("%s doubled = %s" % (p1, p3))
|
||||||
|
if p3.x() != x3 or p3.y() != y3:
|
||||||
|
raise FailedTest("Failure: should give (%d,%d)." % (x3, y3))
|
||||||
|
else:
|
||||||
|
print(" Good.")
|
||||||
|
|
||||||
|
def test_double_infinity(c):
|
||||||
|
"""We expect that on curve c, 2*INFINITY = INFINITY."""
|
||||||
|
p1 = INFINITY
|
||||||
|
p3 = p1.double()
|
||||||
|
print("%s doubled = %s" % (p1, p3))
|
||||||
|
if p3.x() != INFINITY.x() or p3.y() != INFINITY.y():
|
||||||
|
raise FailedTest("Failure: should give (%d,%d)." % (INFINITY.x(), INFINITY.y()))
|
||||||
|
else:
|
||||||
|
print(" Good.")
|
||||||
|
|
||||||
|
def test_multiply(c, x1, y1, m, x3, y3):
|
||||||
|
"""We expect that on curve c, m*(x1,y1) = (x3,y3)."""
|
||||||
|
p1 = Point(c, x1, y1)
|
||||||
|
p3 = p1 * m
|
||||||
|
print("%s * %d = %s" % (p1, m, p3))
|
||||||
|
if p3.x() != x3 or p3.y() != y3:
|
||||||
|
raise FailedTest("Failure: should give (%d,%d)." % (x3, y3))
|
||||||
|
else:
|
||||||
|
print(" Good.")
|
||||||
|
|
||||||
|
# A few tests from X9.62 B.3:
|
||||||
|
|
||||||
|
c = CurveFp(23, 1, 1)
|
||||||
|
test_add(c, 3, 10, 9, 7, 17, 20)
|
||||||
|
test_double(c, 3, 10, 7, 12)
|
||||||
|
test_add(c, 3, 10, 3, 10, 7, 12) # (Should just invoke double.)
|
||||||
|
test_multiply(c, 3, 10, 2, 7, 12)
|
||||||
|
|
||||||
|
test_double_infinity(c)
|
||||||
|
|
||||||
|
# From X9.62 I.1 (p. 96):
|
||||||
|
|
||||||
|
g = Point(c, 13, 7, 7)
|
||||||
|
|
||||||
|
check = INFINITY
|
||||||
|
for i in range(7 + 1):
|
||||||
|
p = (i % 7) * g
|
||||||
|
print("%s * %d = %s, expected %s . . ." % (g, i, p, check))
|
||||||
|
if p == check:
|
||||||
|
print(" Good.")
|
||||||
|
else:
|
||||||
|
raise FailedTest("Bad.")
|
||||||
|
check = check + g
|
||||||
|
|
||||||
|
# NIST Curve P-192:
|
||||||
|
p = 6277101735386680763835789423207666416083908700390324961279
|
||||||
|
r = 6277101735386680763835789423176059013767194773182842284081
|
||||||
|
#s = 0x3045ae6fc8422f64ed579528d38120eae12196d5L
|
||||||
|
c = 0x3099d2bbbfcb2538542dcd5fb078b6ef5f3d6fe2c745de65
|
||||||
|
b = 0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1
|
||||||
|
Gx = 0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012
|
||||||
|
Gy = 0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811
|
||||||
|
|
||||||
|
c192 = CurveFp(p, -3, b)
|
||||||
|
p192 = Point(c192, Gx, Gy, r)
|
||||||
|
|
||||||
|
# Checking against some sample computations presented
|
||||||
|
# in X9.62:
|
||||||
|
|
||||||
|
d = 651056770906015076056810763456358567190100156695615665659
|
||||||
|
Q = d * p192
|
||||||
|
if Q.x() != 0x62B12D60690CDCF330BABAB6E69763B471F994DD702D16A5:
|
||||||
|
raise FailedTest("p192 * d came out wrong.")
|
||||||
|
else:
|
||||||
|
print("p192 * d came out right.")
|
||||||
|
|
||||||
|
k = 6140507067065001063065065565667405560006161556565665656654
|
||||||
|
R = k * p192
|
||||||
|
if R.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \
|
||||||
|
or R.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835:
|
||||||
|
raise FailedTest("k * p192 came out wrong.")
|
||||||
|
else:
|
||||||
|
print("k * p192 came out right.")
|
||||||
|
|
||||||
|
u1 = 2563697409189434185194736134579731015366492496392189760599
|
||||||
|
u2 = 6266643813348617967186477710235785849136406323338782220568
|
||||||
|
temp = u1 * p192 + u2 * Q
|
||||||
|
if temp.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \
|
||||||
|
or temp.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835:
|
||||||
|
raise FailedTest("u1 * p192 + u2 * Q came out wrong.")
|
||||||
|
else:
|
||||||
|
print("u1 * p192 + u2 * Q came out right.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
__main__()
|
0
basicswap/contrib/test_framework/__init__.py
Normal file
0
basicswap/contrib/test_framework/__init__.py
Normal file
158
basicswap/contrib/test_framework/address.py
Normal file
158
basicswap/contrib/test_framework/address.py
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2016-2020 The Bitcoin Core developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
"""Encode and decode BASE58, P2PKH and P2SH addresses."""
|
||||||
|
|
||||||
|
import enum
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from .script import hash256, hash160, sha256, CScript, OP_0
|
||||||
|
from .util import hex_str_to_bytes
|
||||||
|
|
||||||
|
from . import segwit_addr
|
||||||
|
|
||||||
|
from .util import assert_equal
|
||||||
|
|
||||||
|
ADDRESS_BCRT1_UNSPENDABLE = 'bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj'
|
||||||
|
ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR = 'addr(bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj)#juyq9d97'
|
||||||
|
# Coins sent to this address can be spent with a witness stack of just OP_TRUE
|
||||||
|
ADDRESS_BCRT1_P2WSH_OP_TRUE = 'bcrt1qft5p2uhsdcdc3l2ua4ap5qqfg4pjaqlp250x7us7a8qqhrxrxfsqseac85'
|
||||||
|
|
||||||
|
|
||||||
|
class AddressType(enum.Enum):
|
||||||
|
bech32 = 'bech32'
|
||||||
|
p2sh_segwit = 'p2sh-segwit'
|
||||||
|
legacy = 'legacy' # P2PKH
|
||||||
|
|
||||||
|
|
||||||
|
chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
||||||
|
|
||||||
|
|
||||||
|
def byte_to_base58(b, version):
|
||||||
|
result = ''
|
||||||
|
str = b.hex()
|
||||||
|
str = chr(version).encode('latin-1').hex() + str
|
||||||
|
checksum = hash256(hex_str_to_bytes(str)).hex()
|
||||||
|
str += checksum[:8]
|
||||||
|
value = int('0x'+str,0)
|
||||||
|
while value > 0:
|
||||||
|
result = chars[value % 58] + result
|
||||||
|
value //= 58
|
||||||
|
while (str[:2] == '00'):
|
||||||
|
result = chars[0] + result
|
||||||
|
str = str[2:]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def base58_to_byte(s, verify_checksum=True):
|
||||||
|
if not s:
|
||||||
|
return b''
|
||||||
|
n = 0
|
||||||
|
for c in s:
|
||||||
|
n *= 58
|
||||||
|
assert c in chars
|
||||||
|
digit = chars.index(c)
|
||||||
|
n += digit
|
||||||
|
h = '%x' % n
|
||||||
|
if len(h) % 2:
|
||||||
|
h = '0' + h
|
||||||
|
res = n.to_bytes((n.bit_length() + 7) // 8, 'big')
|
||||||
|
pad = 0
|
||||||
|
for c in s:
|
||||||
|
if c == chars[0]:
|
||||||
|
pad += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
res = b'\x00' * pad + res
|
||||||
|
if verify_checksum:
|
||||||
|
assert_equal(hash256(res[:-4])[:4], res[-4:])
|
||||||
|
|
||||||
|
return res[1:-4], int(res[0])
|
||||||
|
|
||||||
|
|
||||||
|
def keyhash_to_p2pkh(hash, main = False, btc = True):
|
||||||
|
assert (len(hash) == 20 or len(hash) == 32)
|
||||||
|
if len(hash) == 20:
|
||||||
|
if btc:
|
||||||
|
version = 0 if main else 111
|
||||||
|
else:
|
||||||
|
version = 56 if main else 118
|
||||||
|
return byte_to_base58(hash, version)
|
||||||
|
version = 57 if main else 119
|
||||||
|
return byte_to_base58(hash, version)
|
||||||
|
|
||||||
|
def scripthash_to_p2sh(hash, main = False, btc = True):
|
||||||
|
assert (len(hash) == 20)
|
||||||
|
if btc:
|
||||||
|
version = 5 if main else 196
|
||||||
|
else:
|
||||||
|
version = 60 if main else 122
|
||||||
|
return byte_to_base58(hash, version)
|
||||||
|
|
||||||
|
def key_to_p2pkh(key, main = False):
|
||||||
|
key = check_key(key)
|
||||||
|
return keyhash_to_p2pkh(hash160(key), main)
|
||||||
|
|
||||||
|
def script_to_p2sh(script, main = False, btc = True):
|
||||||
|
script = check_script(script)
|
||||||
|
return scripthash_to_p2sh(hash160(script), main, btc)
|
||||||
|
|
||||||
|
def key_to_p2sh_p2wpkh(key, main = False):
|
||||||
|
key = check_key(key)
|
||||||
|
p2shscript = CScript([OP_0, hash160(key)])
|
||||||
|
return script_to_p2sh(p2shscript, main)
|
||||||
|
|
||||||
|
def program_to_witness(version, program, main = False):
|
||||||
|
if (type(program) is str):
|
||||||
|
program = hex_str_to_bytes(program)
|
||||||
|
assert 0 <= version <= 16
|
||||||
|
assert 2 <= len(program) <= 40
|
||||||
|
assert version > 0 or len(program) in [20, 32]
|
||||||
|
return segwit_addr.encode("bc" if main else "bcrt", version, program)
|
||||||
|
|
||||||
|
def script_to_p2wsh(script, main = False):
|
||||||
|
script = check_script(script)
|
||||||
|
return program_to_witness(0, sha256(script), main)
|
||||||
|
|
||||||
|
def key_to_p2wpkh(key, main = False):
|
||||||
|
key = check_key(key)
|
||||||
|
return program_to_witness(0, hash160(key), main)
|
||||||
|
|
||||||
|
def script_to_p2sh_p2wsh(script, main = False):
|
||||||
|
script = check_script(script)
|
||||||
|
p2shscript = CScript([OP_0, sha256(script)])
|
||||||
|
return script_to_p2sh(p2shscript, main)
|
||||||
|
|
||||||
|
def check_key(key):
|
||||||
|
if (type(key) is str):
|
||||||
|
key = hex_str_to_bytes(key) # Assuming this is hex string
|
||||||
|
if (type(key) is bytes and (len(key) == 33 or len(key) == 65)):
|
||||||
|
return key
|
||||||
|
assert False
|
||||||
|
|
||||||
|
def check_script(script):
|
||||||
|
if (type(script) is str):
|
||||||
|
script = hex_str_to_bytes(script) # Assuming this is hex string
|
||||||
|
if (type(script) is bytes or type(script) is CScript):
|
||||||
|
return script
|
||||||
|
assert False
|
||||||
|
|
||||||
|
|
||||||
|
class TestFrameworkScript(unittest.TestCase):
|
||||||
|
def test_base58encodedecode(self):
|
||||||
|
def check_base58(data, version):
|
||||||
|
self.assertEqual(base58_to_byte(byte_to_base58(data, version)), (data, version))
|
||||||
|
|
||||||
|
check_base58(b'\x1f\x8e\xa1p*{\xd4\x94\x1b\xca\tA\xb8R\xc4\xbb\xfe\xdb.\x05', 111)
|
||||||
|
check_base58(b':\x0b\x05\xf4\xd7\xf6l;\xa7\x00\x9fE50)l\x84\\\xc9\xcf', 111)
|
||||||
|
check_base58(b'A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111)
|
||||||
|
check_base58(b'\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111)
|
||||||
|
check_base58(b'\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111)
|
||||||
|
check_base58(b'\0\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111)
|
||||||
|
check_base58(b'\x1f\x8e\xa1p*{\xd4\x94\x1b\xca\tA\xb8R\xc4\xbb\xfe\xdb.\x05', 0)
|
||||||
|
check_base58(b':\x0b\x05\xf4\xd7\xf6l;\xa7\x00\x9fE50)l\x84\\\xc9\xcf', 0)
|
||||||
|
check_base58(b'A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0)
|
||||||
|
check_base58(b'\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0)
|
||||||
|
check_base58(b'\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0)
|
||||||
|
check_base58(b'\0\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0)
|
204
basicswap/contrib/test_framework/authproxy.py
Normal file
204
basicswap/contrib/test_framework/authproxy.py
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
# Copyright (c) 2011 Jeff Garzik
|
||||||
|
#
|
||||||
|
# Previous copyright, from python-jsonrpc/jsonrpc/proxy.py:
|
||||||
|
#
|
||||||
|
# Copyright (c) 2007 Jan-Klaas Kollhof
|
||||||
|
#
|
||||||
|
# This file is part of jsonrpc.
|
||||||
|
#
|
||||||
|
# jsonrpc is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This software is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with this software; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
"""HTTP proxy for opening RPC connection to bitcoind.
|
||||||
|
|
||||||
|
AuthServiceProxy has the following improvements over python-jsonrpc's
|
||||||
|
ServiceProxy class:
|
||||||
|
|
||||||
|
- HTTP connections persist for the life of the AuthServiceProxy object
|
||||||
|
(if server supports HTTP/1.1)
|
||||||
|
- sends protocol 'version', per JSON-RPC 1.1
|
||||||
|
- sends proper, incrementing 'id'
|
||||||
|
- sends Basic HTTP authentication headers
|
||||||
|
- parses all JSON numbers that look like floats as Decimal
|
||||||
|
- uses standard Python json lib
|
||||||
|
"""
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import decimal
|
||||||
|
from http import HTTPStatus
|
||||||
|
import http.client
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import time
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
|
HTTP_TIMEOUT = 30
|
||||||
|
USER_AGENT = "AuthServiceProxy/0.1"
|
||||||
|
|
||||||
|
log = logging.getLogger("BitcoinRPC")
|
||||||
|
|
||||||
|
class JSONRPCException(Exception):
|
||||||
|
def __init__(self, rpc_error, http_status=None):
|
||||||
|
try:
|
||||||
|
errmsg = '%(message)s (%(code)i)' % rpc_error
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
errmsg = ''
|
||||||
|
super().__init__(errmsg)
|
||||||
|
self.error = rpc_error
|
||||||
|
self.http_status = http_status
|
||||||
|
|
||||||
|
|
||||||
|
def EncodeDecimal(o):
|
||||||
|
if isinstance(o, decimal.Decimal):
|
||||||
|
return str(o)
|
||||||
|
raise TypeError(repr(o) + " is not JSON serializable")
|
||||||
|
|
||||||
|
class AuthServiceProxy():
|
||||||
|
__id_count = 0
|
||||||
|
|
||||||
|
# ensure_ascii: escape unicode as \uXXXX, passed to json.dumps
|
||||||
|
def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connection=None, ensure_ascii=True):
|
||||||
|
self.__service_url = service_url
|
||||||
|
self._service_name = service_name
|
||||||
|
self.ensure_ascii = ensure_ascii # can be toggled on the fly by tests
|
||||||
|
self.__url = urllib.parse.urlparse(service_url)
|
||||||
|
user = None if self.__url.username is None else self.__url.username.encode('utf8')
|
||||||
|
passwd = None if self.__url.password is None else self.__url.password.encode('utf8')
|
||||||
|
authpair = user + b':' + passwd
|
||||||
|
self.__auth_header = b'Basic ' + base64.b64encode(authpair)
|
||||||
|
self.timeout = timeout
|
||||||
|
self._set_conn(connection)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
if name.startswith('__') and name.endswith('__'):
|
||||||
|
# Python internal stuff
|
||||||
|
raise AttributeError
|
||||||
|
if self._service_name is not None:
|
||||||
|
name = "%s.%s" % (self._service_name, name)
|
||||||
|
return AuthServiceProxy(self.__service_url, name, connection=self.__conn)
|
||||||
|
|
||||||
|
def _request(self, method, path, postdata):
|
||||||
|
'''
|
||||||
|
Do a HTTP request, with retry if we get disconnected (e.g. due to a timeout).
|
||||||
|
This is a workaround for https://bugs.python.org/issue3566 which is fixed in Python 3.5.
|
||||||
|
'''
|
||||||
|
headers = {'Host': self.__url.hostname,
|
||||||
|
'User-Agent': USER_AGENT,
|
||||||
|
'Authorization': self.__auth_header,
|
||||||
|
'Content-type': 'application/json'}
|
||||||
|
if os.name == 'nt':
|
||||||
|
# Windows somehow does not like to re-use connections
|
||||||
|
# TODO: Find out why the connection would disconnect occasionally and make it reusable on Windows
|
||||||
|
# Avoid "ConnectionAbortedError: [WinError 10053] An established connection was aborted by the software in your host machine"
|
||||||
|
self._set_conn()
|
||||||
|
try:
|
||||||
|
self.__conn.request(method, path, postdata, headers)
|
||||||
|
return self._get_response()
|
||||||
|
except (BrokenPipeError, ConnectionResetError):
|
||||||
|
# Python 3.5+ raises BrokenPipeError when the connection was reset
|
||||||
|
# ConnectionResetError happens on FreeBSD
|
||||||
|
self.__conn.close()
|
||||||
|
self.__conn.request(method, path, postdata, headers)
|
||||||
|
return self._get_response()
|
||||||
|
except OSError as e:
|
||||||
|
retry = (
|
||||||
|
'[WinError 10053] An established connection was aborted by the software in your host machine' in str(e))
|
||||||
|
if retry:
|
||||||
|
self.__conn.close()
|
||||||
|
self.__conn.request(method, path, postdata, headers)
|
||||||
|
return self._get_response()
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
def get_request(self, *args, **argsn):
|
||||||
|
AuthServiceProxy.__id_count += 1
|
||||||
|
|
||||||
|
log.debug("-{}-> {} {}".format(
|
||||||
|
AuthServiceProxy.__id_count,
|
||||||
|
self._service_name,
|
||||||
|
json.dumps(args or argsn, default=EncodeDecimal, ensure_ascii=self.ensure_ascii),
|
||||||
|
))
|
||||||
|
if args and argsn:
|
||||||
|
raise ValueError('Cannot handle both named and positional arguments')
|
||||||
|
return {'version': '1.1',
|
||||||
|
'method': self._service_name,
|
||||||
|
'params': args or argsn,
|
||||||
|
'id': AuthServiceProxy.__id_count}
|
||||||
|
|
||||||
|
def __call__(self, *args, **argsn):
|
||||||
|
postdata = json.dumps(self.get_request(*args, **argsn), default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
|
||||||
|
response, status = self._request('POST', self.__url.path, postdata.encode('utf-8'))
|
||||||
|
if response['error'] is not None:
|
||||||
|
raise JSONRPCException(response['error'], status)
|
||||||
|
elif 'result' not in response:
|
||||||
|
raise JSONRPCException({
|
||||||
|
'code': -343, 'message': 'missing JSON-RPC result'}, status)
|
||||||
|
elif status != HTTPStatus.OK:
|
||||||
|
raise JSONRPCException({
|
||||||
|
'code': -342, 'message': 'non-200 HTTP status code but no JSON-RPC error'}, status)
|
||||||
|
else:
|
||||||
|
return response['result']
|
||||||
|
|
||||||
|
def batch(self, rpc_call_list):
|
||||||
|
postdata = json.dumps(list(rpc_call_list), default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
|
||||||
|
log.debug("--> " + postdata)
|
||||||
|
response, status = self._request('POST', self.__url.path, postdata.encode('utf-8'))
|
||||||
|
if status != HTTPStatus.OK:
|
||||||
|
raise JSONRPCException({
|
||||||
|
'code': -342, 'message': 'non-200 HTTP status code but no JSON-RPC error'}, status)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def _get_response(self):
|
||||||
|
req_start_time = time.time()
|
||||||
|
try:
|
||||||
|
http_response = self.__conn.getresponse()
|
||||||
|
except socket.timeout:
|
||||||
|
raise JSONRPCException({
|
||||||
|
'code': -344,
|
||||||
|
'message': '%r RPC took longer than %f seconds. Consider '
|
||||||
|
'using larger timeout for calls that take '
|
||||||
|
'longer to return.' % (self._service_name,
|
||||||
|
self.__conn.timeout)})
|
||||||
|
if http_response is None:
|
||||||
|
raise JSONRPCException({
|
||||||
|
'code': -342, 'message': 'missing HTTP response from server'})
|
||||||
|
|
||||||
|
content_type = http_response.getheader('Content-Type')
|
||||||
|
if content_type != 'application/json':
|
||||||
|
raise JSONRPCException(
|
||||||
|
{'code': -342, 'message': 'non-JSON HTTP response with \'%i %s\' from server' % (http_response.status, http_response.reason)},
|
||||||
|
http_response.status)
|
||||||
|
|
||||||
|
responsedata = http_response.read().decode('utf8')
|
||||||
|
response = json.loads(responsedata, parse_float=decimal.Decimal)
|
||||||
|
elapsed = time.time() - req_start_time
|
||||||
|
if "error" in response and response["error"] is None:
|
||||||
|
log.debug("<-%s- [%.6f] %s" % (response["id"], elapsed, json.dumps(response["result"], default=EncodeDecimal, ensure_ascii=self.ensure_ascii)))
|
||||||
|
else:
|
||||||
|
log.debug("<-- [%.6f] %s" % (elapsed, responsedata))
|
||||||
|
return response, http_response.status
|
||||||
|
|
||||||
|
def __truediv__(self, relative_uri):
|
||||||
|
return AuthServiceProxy("{}/{}".format(self.__service_url, relative_uri), self._service_name, connection=self.__conn)
|
||||||
|
|
||||||
|
def _set_conn(self, connection=None):
|
||||||
|
port = 80 if self.__url.port is None else self.__url.port
|
||||||
|
if connection:
|
||||||
|
self.__conn = connection
|
||||||
|
self.timeout = connection.timeout
|
||||||
|
elif self.__url.scheme == 'https':
|
||||||
|
self.__conn = http.client.HTTPSConnection(self.__url.hostname, port, timeout=self.timeout)
|
||||||
|
else:
|
||||||
|
self.__conn = http.client.HTTPConnection(self.__url.hostname, port, timeout=self.timeout)
|
109
basicswap/contrib/test_framework/coverage.py
Normal file
109
basicswap/contrib/test_framework/coverage.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2015-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.
|
||||||
|
"""Utilities for doing coverage analysis on the RPC interface.
|
||||||
|
|
||||||
|
Provides a way to track which RPC commands are exercised during
|
||||||
|
testing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
REFERENCE_FILENAME = 'rpc_interface.txt'
|
||||||
|
|
||||||
|
|
||||||
|
class AuthServiceProxyWrapper():
|
||||||
|
"""
|
||||||
|
An object that wraps AuthServiceProxy to record specific RPC calls.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, auth_service_proxy_instance, coverage_logfile=None):
|
||||||
|
"""
|
||||||
|
Kwargs:
|
||||||
|
auth_service_proxy_instance (AuthServiceProxy): the instance
|
||||||
|
being wrapped.
|
||||||
|
coverage_logfile (str): if specified, write each service_name
|
||||||
|
out to a file when called.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.auth_service_proxy_instance = auth_service_proxy_instance
|
||||||
|
self.coverage_logfile = coverage_logfile
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return_val = getattr(self.auth_service_proxy_instance, name)
|
||||||
|
if not isinstance(return_val, type(self.auth_service_proxy_instance)):
|
||||||
|
# If proxy getattr returned an unwrapped value, do the same here.
|
||||||
|
return return_val
|
||||||
|
return AuthServiceProxyWrapper(return_val, self.coverage_logfile)
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Delegates to AuthServiceProxy, then writes the particular RPC method
|
||||||
|
called to a file.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return_val = self.auth_service_proxy_instance.__call__(*args, **kwargs)
|
||||||
|
self._log_call()
|
||||||
|
return return_val
|
||||||
|
|
||||||
|
def _log_call(self):
|
||||||
|
rpc_method = self.auth_service_proxy_instance._service_name
|
||||||
|
|
||||||
|
if self.coverage_logfile:
|
||||||
|
with open(self.coverage_logfile, 'a+', encoding='utf8') as f:
|
||||||
|
f.write("%s\n" % rpc_method)
|
||||||
|
|
||||||
|
def __truediv__(self, relative_uri):
|
||||||
|
return AuthServiceProxyWrapper(self.auth_service_proxy_instance / relative_uri,
|
||||||
|
self.coverage_logfile)
|
||||||
|
|
||||||
|
def get_request(self, *args, **kwargs):
|
||||||
|
self._log_call()
|
||||||
|
return self.auth_service_proxy_instance.get_request(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_filename(dirname, n_node):
|
||||||
|
"""
|
||||||
|
Get a filename unique to the test process ID and node.
|
||||||
|
|
||||||
|
This file will contain a list of RPC commands covered.
|
||||||
|
"""
|
||||||
|
pid = str(os.getpid())
|
||||||
|
return os.path.join(
|
||||||
|
dirname, "coverage.pid%s.node%s.txt" % (pid, str(n_node)))
|
||||||
|
|
||||||
|
|
||||||
|
def write_all_rpc_commands(dirname, node):
|
||||||
|
"""
|
||||||
|
Write out a list of all RPC functions available in `bitcoin-cli` for
|
||||||
|
coverage comparison. This will only happen once per coverage
|
||||||
|
directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dirname (str): temporary test dir
|
||||||
|
node (AuthServiceProxy): client
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool. if the RPC interface file was written.
|
||||||
|
|
||||||
|
"""
|
||||||
|
filename = os.path.join(dirname, REFERENCE_FILENAME)
|
||||||
|
|
||||||
|
if os.path.isfile(filename):
|
||||||
|
return False
|
||||||
|
|
||||||
|
help_output = node.help().split('\n')
|
||||||
|
commands = set()
|
||||||
|
|
||||||
|
for line in help_output:
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
# Ignore blanks and headers
|
||||||
|
if line and not line.startswith('='):
|
||||||
|
commands.add("%s\n" % line.split()[0])
|
||||||
|
|
||||||
|
with open(filename, 'w', encoding='utf8') as f:
|
||||||
|
f.writelines(list(commands))
|
||||||
|
|
||||||
|
return True
|
393
basicswap/contrib/test_framework/key.py
Normal file
393
basicswap/contrib/test_framework/key.py
Normal file
@ -0,0 +1,393 @@
|
|||||||
|
# Copyright (c) 2019 Pieter Wuille
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
"""Test-only secp256k1 elliptic curve implementation
|
||||||
|
|
||||||
|
WARNING: This code is slow, uses bad randomness, does not properly protect
|
||||||
|
keys, and is trivially vulnerable to side channel attacks. Do not use for
|
||||||
|
anything but tests."""
|
||||||
|
import random
|
||||||
|
|
||||||
|
def modinv(a, n):
|
||||||
|
"""Compute the modular inverse of a modulo n
|
||||||
|
|
||||||
|
See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers.
|
||||||
|
"""
|
||||||
|
t1, t2 = 0, 1
|
||||||
|
r1, r2 = n, a
|
||||||
|
while r2 != 0:
|
||||||
|
q = r1 // r2
|
||||||
|
t1, t2 = t2, t1 - q * t2
|
||||||
|
r1, r2 = r2, r1 - q * r2
|
||||||
|
if r1 > 1:
|
||||||
|
return None
|
||||||
|
if t1 < 0:
|
||||||
|
t1 += n
|
||||||
|
return t1
|
||||||
|
|
||||||
|
def jacobi_symbol(n, k):
|
||||||
|
"""Compute the Jacobi symbol of n modulo k
|
||||||
|
|
||||||
|
See http://en.wikipedia.org/wiki/Jacobi_symbol
|
||||||
|
|
||||||
|
For our application k is always prime, so this is the same as the Legendre symbol."""
|
||||||
|
assert k > 0 and k & 1, "jacobi symbol is only defined for positive odd k"
|
||||||
|
n %= k
|
||||||
|
t = 0
|
||||||
|
while n != 0:
|
||||||
|
while n & 1 == 0:
|
||||||
|
n >>= 1
|
||||||
|
r = k & 7
|
||||||
|
t ^= (r == 3 or r == 5)
|
||||||
|
n, k = k, n
|
||||||
|
t ^= (n & k & 3 == 3)
|
||||||
|
n = n % k
|
||||||
|
if k == 1:
|
||||||
|
return -1 if t else 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def modsqrt(a, p):
|
||||||
|
"""Compute the square root of a modulo p when p % 4 = 3.
|
||||||
|
|
||||||
|
The Tonelli-Shanks algorithm can be used. See https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm
|
||||||
|
|
||||||
|
Limiting this function to only work for p % 4 = 3 means we don't need to
|
||||||
|
iterate through the loop. The highest n such that p - 1 = 2^n Q with Q odd
|
||||||
|
is n = 1. Therefore Q = (p-1)/2 and sqrt = a^((Q+1)/2) = a^((p+1)/4)
|
||||||
|
|
||||||
|
secp256k1's is defined over field of size 2**256 - 2**32 - 977, which is 3 mod 4.
|
||||||
|
"""
|
||||||
|
if p % 4 != 3:
|
||||||
|
raise NotImplementedError("modsqrt only implemented for p % 4 = 3")
|
||||||
|
sqrt = pow(a, (p + 1)//4, p)
|
||||||
|
if pow(sqrt, 2, p) == a % p:
|
||||||
|
return sqrt
|
||||||
|
return None
|
||||||
|
|
||||||
|
class EllipticCurve:
|
||||||
|
def __init__(self, p, a, b):
|
||||||
|
"""Initialize elliptic curve y^2 = x^3 + a*x + b over GF(p)."""
|
||||||
|
self.p = p
|
||||||
|
self.a = a % p
|
||||||
|
self.b = b % p
|
||||||
|
|
||||||
|
def affine(self, p1):
|
||||||
|
"""Convert a Jacobian point tuple p1 to affine form, or None if at infinity.
|
||||||
|
|
||||||
|
An affine point is represented as the Jacobian (x, y, 1)"""
|
||||||
|
x1, y1, z1 = p1
|
||||||
|
if z1 == 0:
|
||||||
|
return None
|
||||||
|
inv = modinv(z1, self.p)
|
||||||
|
inv_2 = (inv**2) % self.p
|
||||||
|
inv_3 = (inv_2 * inv) % self.p
|
||||||
|
return ((inv_2 * x1) % self.p, (inv_3 * y1) % self.p, 1)
|
||||||
|
|
||||||
|
def negate(self, p1):
|
||||||
|
"""Negate a Jacobian point tuple p1."""
|
||||||
|
x1, y1, z1 = p1
|
||||||
|
return (x1, (self.p - y1) % self.p, z1)
|
||||||
|
|
||||||
|
def on_curve(self, p1):
|
||||||
|
"""Determine whether a Jacobian tuple p is on the curve (and not infinity)"""
|
||||||
|
x1, y1, z1 = p1
|
||||||
|
z2 = pow(z1, 2, self.p)
|
||||||
|
z4 = pow(z2, 2, self.p)
|
||||||
|
return z1 != 0 and (pow(x1, 3, self.p) + self.a * x1 * z4 + self.b * z2 * z4 - pow(y1, 2, self.p)) % self.p == 0
|
||||||
|
|
||||||
|
def is_x_coord(self, x):
|
||||||
|
"""Test whether x is a valid X coordinate on the curve."""
|
||||||
|
x_3 = pow(x, 3, self.p)
|
||||||
|
return jacobi_symbol(x_3 + self.a * x + self.b, self.p) != -1
|
||||||
|
|
||||||
|
def lift_x(self, x):
|
||||||
|
"""Given an X coordinate on the curve, return a corresponding affine point."""
|
||||||
|
x_3 = pow(x, 3, self.p)
|
||||||
|
v = x_3 + self.a * x + self.b
|
||||||
|
y = modsqrt(v, self.p)
|
||||||
|
if y is None:
|
||||||
|
return None
|
||||||
|
return (x, y, 1)
|
||||||
|
|
||||||
|
def double(self, p1):
|
||||||
|
"""Double a Jacobian tuple p1
|
||||||
|
|
||||||
|
See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Doubling"""
|
||||||
|
x1, y1, z1 = p1
|
||||||
|
if z1 == 0:
|
||||||
|
return (0, 1, 0)
|
||||||
|
y1_2 = (y1**2) % self.p
|
||||||
|
y1_4 = (y1_2**2) % self.p
|
||||||
|
x1_2 = (x1**2) % self.p
|
||||||
|
s = (4*x1*y1_2) % self.p
|
||||||
|
m = 3*x1_2
|
||||||
|
if self.a:
|
||||||
|
m += self.a * pow(z1, 4, self.p)
|
||||||
|
m = m % self.p
|
||||||
|
x2 = (m**2 - 2*s) % self.p
|
||||||
|
y2 = (m*(s - x2) - 8*y1_4) % self.p
|
||||||
|
z2 = (2*y1*z1) % self.p
|
||||||
|
return (x2, y2, z2)
|
||||||
|
|
||||||
|
def add_mixed(self, p1, p2):
|
||||||
|
"""Add a Jacobian tuple p1 and an affine tuple p2
|
||||||
|
|
||||||
|
See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition (with affine point)"""
|
||||||
|
x1, y1, z1 = p1
|
||||||
|
x2, y2, z2 = p2
|
||||||
|
assert(z2 == 1)
|
||||||
|
# Adding to the point at infinity is a no-op
|
||||||
|
if z1 == 0:
|
||||||
|
return p2
|
||||||
|
z1_2 = (z1**2) % self.p
|
||||||
|
z1_3 = (z1_2 * z1) % self.p
|
||||||
|
u2 = (x2 * z1_2) % self.p
|
||||||
|
s2 = (y2 * z1_3) % self.p
|
||||||
|
if x1 == u2:
|
||||||
|
if (y1 != s2):
|
||||||
|
# p1 and p2 are inverses. Return the point at infinity.
|
||||||
|
return (0, 1, 0)
|
||||||
|
# p1 == p2. The formulas below fail when the two points are equal.
|
||||||
|
return self.double(p1)
|
||||||
|
h = u2 - x1
|
||||||
|
r = s2 - y1
|
||||||
|
h_2 = (h**2) % self.p
|
||||||
|
h_3 = (h_2 * h) % self.p
|
||||||
|
u1_h_2 = (x1 * h_2) % self.p
|
||||||
|
x3 = (r**2 - h_3 - 2*u1_h_2) % self.p
|
||||||
|
y3 = (r*(u1_h_2 - x3) - y1*h_3) % self.p
|
||||||
|
z3 = (h*z1) % self.p
|
||||||
|
return (x3, y3, z3)
|
||||||
|
|
||||||
|
def add(self, p1, p2):
|
||||||
|
"""Add two Jacobian tuples p1 and p2
|
||||||
|
|
||||||
|
See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition"""
|
||||||
|
x1, y1, z1 = p1
|
||||||
|
x2, y2, z2 = p2
|
||||||
|
# Adding the point at infinity is a no-op
|
||||||
|
if z1 == 0:
|
||||||
|
return p2
|
||||||
|
if z2 == 0:
|
||||||
|
return p1
|
||||||
|
# Adding an Affine to a Jacobian is more efficient since we save field multiplications and squarings when z = 1
|
||||||
|
if z1 == 1:
|
||||||
|
return self.add_mixed(p2, p1)
|
||||||
|
if z2 == 1:
|
||||||
|
return self.add_mixed(p1, p2)
|
||||||
|
z1_2 = (z1**2) % self.p
|
||||||
|
z1_3 = (z1_2 * z1) % self.p
|
||||||
|
z2_2 = (z2**2) % self.p
|
||||||
|
z2_3 = (z2_2 * z2) % self.p
|
||||||
|
u1 = (x1 * z2_2) % self.p
|
||||||
|
u2 = (x2 * z1_2) % self.p
|
||||||
|
s1 = (y1 * z2_3) % self.p
|
||||||
|
s2 = (y2 * z1_3) % self.p
|
||||||
|
if u1 == u2:
|
||||||
|
if (s1 != s2):
|
||||||
|
# p1 and p2 are inverses. Return the point at infinity.
|
||||||
|
return (0, 1, 0)
|
||||||
|
# p1 == p2. The formulas below fail when the two points are equal.
|
||||||
|
return self.double(p1)
|
||||||
|
h = u2 - u1
|
||||||
|
r = s2 - s1
|
||||||
|
h_2 = (h**2) % self.p
|
||||||
|
h_3 = (h_2 * h) % self.p
|
||||||
|
u1_h_2 = (u1 * h_2) % self.p
|
||||||
|
x3 = (r**2 - h_3 - 2*u1_h_2) % self.p
|
||||||
|
y3 = (r*(u1_h_2 - x3) - s1*h_3) % self.p
|
||||||
|
z3 = (h*z1*z2) % self.p
|
||||||
|
return (x3, y3, z3)
|
||||||
|
|
||||||
|
def mul(self, ps):
|
||||||
|
"""Compute a (multi) point multiplication
|
||||||
|
|
||||||
|
ps is a list of (Jacobian tuple, scalar) pairs.
|
||||||
|
"""
|
||||||
|
r = (0, 1, 0)
|
||||||
|
for i in range(255, -1, -1):
|
||||||
|
r = self.double(r)
|
||||||
|
for (p, n) in ps:
|
||||||
|
if ((n >> i) & 1):
|
||||||
|
r = self.add(r, p)
|
||||||
|
return r
|
||||||
|
|
||||||
|
SECP256K1 = EllipticCurve(2**256 - 2**32 - 977, 0, 7)
|
||||||
|
SECP256K1_G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, 1)
|
||||||
|
SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
|
||||||
|
SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2
|
||||||
|
|
||||||
|
class ECPubKey():
|
||||||
|
"""A secp256k1 public key"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Construct an uninitialized public key"""
|
||||||
|
self.valid = False
|
||||||
|
|
||||||
|
def set_int(self, x, y):
|
||||||
|
p = (x, y, 1)
|
||||||
|
self.valid = SECP256K1.on_curve(p)
|
||||||
|
if self.valid:
|
||||||
|
self.p = p
|
||||||
|
self.compressed = False
|
||||||
|
|
||||||
|
def set(self, data):
|
||||||
|
"""Construct a public key from a serialization in compressed or uncompressed format"""
|
||||||
|
if (len(data) == 65 and data[0] == 0x04):
|
||||||
|
p = (int.from_bytes(data[1:33], 'big'), int.from_bytes(data[33:65], 'big'), 1)
|
||||||
|
self.valid = SECP256K1.on_curve(p)
|
||||||
|
if self.valid:
|
||||||
|
self.p = p
|
||||||
|
self.compressed = False
|
||||||
|
elif (len(data) == 33 and (data[0] == 0x02 or data[0] == 0x03)):
|
||||||
|
x = int.from_bytes(data[1:33], 'big')
|
||||||
|
if SECP256K1.is_x_coord(x):
|
||||||
|
p = SECP256K1.lift_x(x)
|
||||||
|
# if the oddness of the y co-ord isn't correct, find the other
|
||||||
|
# valid y
|
||||||
|
if (p[1] & 1) != (data[0] & 1):
|
||||||
|
p = SECP256K1.negate(p)
|
||||||
|
self.p = p
|
||||||
|
self.valid = True
|
||||||
|
self.compressed = True
|
||||||
|
else:
|
||||||
|
self.valid = False
|
||||||
|
else:
|
||||||
|
self.valid = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_compressed(self):
|
||||||
|
return self.compressed
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_valid(self):
|
||||||
|
return self.valid
|
||||||
|
|
||||||
|
def get_bytes(self):
|
||||||
|
assert(self.valid)
|
||||||
|
p = SECP256K1.affine(self.p)
|
||||||
|
if p is None:
|
||||||
|
return None
|
||||||
|
if self.compressed:
|
||||||
|
return bytes([0x02 + (p[1] & 1)]) + p[0].to_bytes(32, 'big')
|
||||||
|
else:
|
||||||
|
return bytes([0x04]) + p[0].to_bytes(32, 'big') + p[1].to_bytes(32, 'big')
|
||||||
|
|
||||||
|
def verify_ecdsa(self, sig, msg, low_s=True):
|
||||||
|
"""Verify a strictly DER-encoded ECDSA signature against this pubkey.
|
||||||
|
|
||||||
|
See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the
|
||||||
|
ECDSA verifier algorithm"""
|
||||||
|
assert(self.valid)
|
||||||
|
|
||||||
|
# Extract r and s from the DER formatted signature. Return false for
|
||||||
|
# any DER encoding errors.
|
||||||
|
if (sig[1] + 2 != len(sig)):
|
||||||
|
return False
|
||||||
|
if (len(sig) < 4):
|
||||||
|
return False
|
||||||
|
if (sig[0] != 0x30):
|
||||||
|
return False
|
||||||
|
if (sig[2] != 0x02):
|
||||||
|
return False
|
||||||
|
rlen = sig[3]
|
||||||
|
if (len(sig) < 6 + rlen):
|
||||||
|
return False
|
||||||
|
if rlen < 1 or rlen > 33:
|
||||||
|
return False
|
||||||
|
if sig[4] >= 0x80:
|
||||||
|
return False
|
||||||
|
if (rlen > 1 and (sig[4] == 0) and not (sig[5] & 0x80)):
|
||||||
|
return False
|
||||||
|
r = int.from_bytes(sig[4:4+rlen], 'big')
|
||||||
|
if (sig[4+rlen] != 0x02):
|
||||||
|
return False
|
||||||
|
slen = sig[5+rlen]
|
||||||
|
if slen < 1 or slen > 33:
|
||||||
|
return False
|
||||||
|
if (len(sig) != 6 + rlen + slen):
|
||||||
|
return False
|
||||||
|
if sig[6+rlen] >= 0x80:
|
||||||
|
return False
|
||||||
|
if (slen > 1 and (sig[6+rlen] == 0) and not (sig[7+rlen] & 0x80)):
|
||||||
|
return False
|
||||||
|
s = int.from_bytes(sig[6+rlen:6+rlen+slen], 'big')
|
||||||
|
|
||||||
|
# Verify that r and s are within the group order
|
||||||
|
if r < 1 or s < 1 or r >= SECP256K1_ORDER or s >= SECP256K1_ORDER:
|
||||||
|
return False
|
||||||
|
if low_s and s >= SECP256K1_ORDER_HALF:
|
||||||
|
return False
|
||||||
|
z = int.from_bytes(msg, 'big')
|
||||||
|
|
||||||
|
# Run verifier algorithm on r, s
|
||||||
|
w = modinv(s, SECP256K1_ORDER)
|
||||||
|
u1 = z*w % SECP256K1_ORDER
|
||||||
|
u2 = r*w % SECP256K1_ORDER
|
||||||
|
R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, u1), (self.p, u2)]))
|
||||||
|
if R is None or R[0] != r:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
class ECKey():
|
||||||
|
"""A secp256k1 private key"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.valid = False
|
||||||
|
|
||||||
|
def set(self, secret, compressed):
|
||||||
|
"""Construct a private key object with given 32-byte secret and compressed flag."""
|
||||||
|
assert(len(secret) == 32)
|
||||||
|
secret = int.from_bytes(secret, 'big')
|
||||||
|
self.valid = (secret > 0 and secret < SECP256K1_ORDER)
|
||||||
|
if self.valid:
|
||||||
|
self.secret = secret
|
||||||
|
self.compressed = compressed
|
||||||
|
|
||||||
|
def generate(self, compressed=True):
|
||||||
|
"""Generate a random private key (compressed or uncompressed)."""
|
||||||
|
self.set(random.randrange(1, SECP256K1_ORDER).to_bytes(32, 'big'), compressed)
|
||||||
|
|
||||||
|
def get_bytes(self):
|
||||||
|
"""Retrieve the 32-byte representation of this key."""
|
||||||
|
assert(self.valid)
|
||||||
|
return self.secret.to_bytes(32, 'big')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_valid(self):
|
||||||
|
return self.valid
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_compressed(self):
|
||||||
|
return self.compressed
|
||||||
|
|
||||||
|
def get_pubkey(self):
|
||||||
|
"""Compute an ECPubKey object for this secret key."""
|
||||||
|
assert(self.valid)
|
||||||
|
ret = ECPubKey()
|
||||||
|
p = SECP256K1.mul([(SECP256K1_G, self.secret)])
|
||||||
|
ret.p = p
|
||||||
|
ret.valid = True
|
||||||
|
ret.compressed = self.compressed
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def sign_ecdsa(self, msg, low_s=True):
|
||||||
|
"""Construct a DER-encoded ECDSA signature with this key.
|
||||||
|
|
||||||
|
See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the
|
||||||
|
ECDSA signer algorithm."""
|
||||||
|
assert(self.valid)
|
||||||
|
z = int.from_bytes(msg, 'big')
|
||||||
|
# Note: no RFC6979, but a simple random nonce (some tests rely on distinct transactions for the same operation)
|
||||||
|
k = random.randrange(1, SECP256K1_ORDER)
|
||||||
|
R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, k)]))
|
||||||
|
r = R[0] % SECP256K1_ORDER
|
||||||
|
s = (modinv(k, SECP256K1_ORDER) * (z + self.secret * r)) % SECP256K1_ORDER
|
||||||
|
if low_s and s > SECP256K1_ORDER_HALF:
|
||||||
|
s = SECP256K1_ORDER - s
|
||||||
|
# Represent in DER format. The byte representations of r and s have
|
||||||
|
# length rounded up (255 bits becomes 32 bytes and 256 bits becomes 33
|
||||||
|
# bytes).
|
||||||
|
rb = r.to_bytes((r.bit_length() + 8) // 8, 'big')
|
||||||
|
sb = s.to_bytes((s.bit_length() + 8) // 8, 'big')
|
||||||
|
return b'\x30' + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + rb + bytes([2, len(sb)]) + sb
|
1756
basicswap/contrib/test_framework/messages.py
Executable file
1756
basicswap/contrib/test_framework/messages.py
Executable file
File diff suppressed because it is too large
Load Diff
740
basicswap/contrib/test_framework/script.py
Normal file
740
basicswap/contrib/test_framework/script.py
Normal file
@ -0,0 +1,740 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2015-2020 The Bitcoin Core developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
"""Functionality to build scripts, as well as signature hash functions.
|
||||||
|
|
||||||
|
This file is modified from python-bitcoinlib.
|
||||||
|
"""
|
||||||
|
import hashlib
|
||||||
|
import struct
|
||||||
|
import unittest
|
||||||
|
from typing import List, Dict
|
||||||
|
|
||||||
|
from .messages import (
|
||||||
|
CTransaction,
|
||||||
|
CTxOut,
|
||||||
|
hash256,
|
||||||
|
ser_string,
|
||||||
|
ser_uint256,
|
||||||
|
sha256,
|
||||||
|
uint256_from_str,
|
||||||
|
)
|
||||||
|
|
||||||
|
MAX_SCRIPT_ELEMENT_SIZE = 520
|
||||||
|
OPCODE_NAMES = {} # type: Dict[CScriptOp, str]
|
||||||
|
|
||||||
|
def hash160(s):
|
||||||
|
return hashlib.new('ripemd160', sha256(s)).digest()
|
||||||
|
|
||||||
|
def bn2vch(v):
|
||||||
|
"""Convert number to bitcoin-specific little endian format."""
|
||||||
|
# We need v.bit_length() bits, plus a sign bit for every nonzero number.
|
||||||
|
n_bits = v.bit_length() + (v != 0)
|
||||||
|
# The number of bytes for that is:
|
||||||
|
n_bytes = (n_bits + 7) // 8
|
||||||
|
# Convert number to absolute value + sign in top bit.
|
||||||
|
encoded_v = 0 if v == 0 else abs(v) | ((v < 0) << (n_bytes * 8 - 1))
|
||||||
|
# Serialize to bytes
|
||||||
|
return encoded_v.to_bytes(n_bytes, 'little')
|
||||||
|
|
||||||
|
_opcode_instances = [] # type: List[CScriptOp]
|
||||||
|
class CScriptOp(int):
|
||||||
|
"""A single script opcode"""
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encode_op_pushdata(d):
|
||||||
|
"""Encode a PUSHDATA op, returning bytes"""
|
||||||
|
if len(d) < 0x4c:
|
||||||
|
return b'' + bytes([len(d)]) + d # OP_PUSHDATA
|
||||||
|
elif len(d) <= 0xff:
|
||||||
|
return b'\x4c' + bytes([len(d)]) + d # OP_PUSHDATA1
|
||||||
|
elif len(d) <= 0xffff:
|
||||||
|
return b'\x4d' + struct.pack(b'<H', len(d)) + d # OP_PUSHDATA2
|
||||||
|
elif len(d) <= 0xffffffff:
|
||||||
|
return b'\x4e' + struct.pack(b'<I', len(d)) + d # OP_PUSHDATA4
|
||||||
|
else:
|
||||||
|
raise ValueError("Data too long to encode in a PUSHDATA op")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encode_op_n(n):
|
||||||
|
"""Encode a small integer op, returning an opcode"""
|
||||||
|
if not (0 <= n <= 16):
|
||||||
|
raise ValueError('Integer must be in range 0 <= n <= 16, got %d' % n)
|
||||||
|
|
||||||
|
if n == 0:
|
||||||
|
return OP_0
|
||||||
|
else:
|
||||||
|
return CScriptOp(OP_1 + n - 1)
|
||||||
|
|
||||||
|
def decode_op_n(self):
|
||||||
|
"""Decode a small integer opcode, returning an integer"""
|
||||||
|
if self == OP_0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if not (self == OP_0 or OP_1 <= self <= OP_16):
|
||||||
|
raise ValueError('op %r is not an OP_N' % self)
|
||||||
|
|
||||||
|
return int(self - OP_1 + 1)
|
||||||
|
|
||||||
|
def is_small_int(self):
|
||||||
|
"""Return true if the op pushes a small integer to the stack"""
|
||||||
|
if 0x51 <= self <= 0x60 or self == 0:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return repr(self)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self in OPCODE_NAMES:
|
||||||
|
return OPCODE_NAMES[self]
|
||||||
|
else:
|
||||||
|
return 'CScriptOp(0x%x)' % self
|
||||||
|
|
||||||
|
def __new__(cls, n):
|
||||||
|
try:
|
||||||
|
return _opcode_instances[n]
|
||||||
|
except IndexError:
|
||||||
|
assert len(_opcode_instances) == n
|
||||||
|
_opcode_instances.append(super().__new__(cls, n))
|
||||||
|
return _opcode_instances[n]
|
||||||
|
|
||||||
|
# Populate opcode instance table
|
||||||
|
for n in range(0xff + 1):
|
||||||
|
CScriptOp(n)
|
||||||
|
|
||||||
|
|
||||||
|
# push value
|
||||||
|
OP_0 = CScriptOp(0x00)
|
||||||
|
OP_FALSE = OP_0
|
||||||
|
OP_PUSHDATA1 = CScriptOp(0x4c)
|
||||||
|
OP_PUSHDATA2 = CScriptOp(0x4d)
|
||||||
|
OP_PUSHDATA4 = CScriptOp(0x4e)
|
||||||
|
OP_1NEGATE = CScriptOp(0x4f)
|
||||||
|
OP_RESERVED = CScriptOp(0x50)
|
||||||
|
OP_1 = CScriptOp(0x51)
|
||||||
|
OP_TRUE = OP_1
|
||||||
|
OP_2 = CScriptOp(0x52)
|
||||||
|
OP_3 = CScriptOp(0x53)
|
||||||
|
OP_4 = CScriptOp(0x54)
|
||||||
|
OP_5 = CScriptOp(0x55)
|
||||||
|
OP_6 = CScriptOp(0x56)
|
||||||
|
OP_7 = CScriptOp(0x57)
|
||||||
|
OP_8 = CScriptOp(0x58)
|
||||||
|
OP_9 = CScriptOp(0x59)
|
||||||
|
OP_10 = CScriptOp(0x5a)
|
||||||
|
OP_11 = CScriptOp(0x5b)
|
||||||
|
OP_12 = CScriptOp(0x5c)
|
||||||
|
OP_13 = CScriptOp(0x5d)
|
||||||
|
OP_14 = CScriptOp(0x5e)
|
||||||
|
OP_15 = CScriptOp(0x5f)
|
||||||
|
OP_16 = CScriptOp(0x60)
|
||||||
|
|
||||||
|
# control
|
||||||
|
OP_NOP = CScriptOp(0x61)
|
||||||
|
OP_VER = CScriptOp(0x62)
|
||||||
|
OP_IF = CScriptOp(0x63)
|
||||||
|
OP_NOTIF = CScriptOp(0x64)
|
||||||
|
OP_VERIF = CScriptOp(0x65)
|
||||||
|
OP_VERNOTIF = CScriptOp(0x66)
|
||||||
|
OP_ELSE = CScriptOp(0x67)
|
||||||
|
OP_ENDIF = CScriptOp(0x68)
|
||||||
|
OP_VERIFY = CScriptOp(0x69)
|
||||||
|
OP_RETURN = CScriptOp(0x6a)
|
||||||
|
|
||||||
|
# stack ops
|
||||||
|
OP_TOALTSTACK = CScriptOp(0x6b)
|
||||||
|
OP_FROMALTSTACK = CScriptOp(0x6c)
|
||||||
|
OP_2DROP = CScriptOp(0x6d)
|
||||||
|
OP_2DUP = CScriptOp(0x6e)
|
||||||
|
OP_3DUP = CScriptOp(0x6f)
|
||||||
|
OP_2OVER = CScriptOp(0x70)
|
||||||
|
OP_2ROT = CScriptOp(0x71)
|
||||||
|
OP_2SWAP = CScriptOp(0x72)
|
||||||
|
OP_IFDUP = CScriptOp(0x73)
|
||||||
|
OP_DEPTH = CScriptOp(0x74)
|
||||||
|
OP_DROP = CScriptOp(0x75)
|
||||||
|
OP_DUP = CScriptOp(0x76)
|
||||||
|
OP_NIP = CScriptOp(0x77)
|
||||||
|
OP_OVER = CScriptOp(0x78)
|
||||||
|
OP_PICK = CScriptOp(0x79)
|
||||||
|
OP_ROLL = CScriptOp(0x7a)
|
||||||
|
OP_ROT = CScriptOp(0x7b)
|
||||||
|
OP_SWAP = CScriptOp(0x7c)
|
||||||
|
OP_TUCK = CScriptOp(0x7d)
|
||||||
|
|
||||||
|
# splice ops
|
||||||
|
OP_CAT = CScriptOp(0x7e)
|
||||||
|
OP_SUBSTR = CScriptOp(0x7f)
|
||||||
|
OP_LEFT = CScriptOp(0x80)
|
||||||
|
OP_RIGHT = CScriptOp(0x81)
|
||||||
|
OP_SIZE = CScriptOp(0x82)
|
||||||
|
|
||||||
|
# bit logic
|
||||||
|
OP_INVERT = CScriptOp(0x83)
|
||||||
|
OP_AND = CScriptOp(0x84)
|
||||||
|
OP_OR = CScriptOp(0x85)
|
||||||
|
OP_XOR = CScriptOp(0x86)
|
||||||
|
OP_EQUAL = CScriptOp(0x87)
|
||||||
|
OP_EQUALVERIFY = CScriptOp(0x88)
|
||||||
|
OP_RESERVED1 = CScriptOp(0x89)
|
||||||
|
OP_RESERVED2 = CScriptOp(0x8a)
|
||||||
|
|
||||||
|
# numeric
|
||||||
|
OP_1ADD = CScriptOp(0x8b)
|
||||||
|
OP_1SUB = CScriptOp(0x8c)
|
||||||
|
OP_2MUL = CScriptOp(0x8d)
|
||||||
|
OP_2DIV = CScriptOp(0x8e)
|
||||||
|
OP_NEGATE = CScriptOp(0x8f)
|
||||||
|
OP_ABS = CScriptOp(0x90)
|
||||||
|
OP_NOT = CScriptOp(0x91)
|
||||||
|
OP_0NOTEQUAL = CScriptOp(0x92)
|
||||||
|
|
||||||
|
OP_ADD = CScriptOp(0x93)
|
||||||
|
OP_SUB = CScriptOp(0x94)
|
||||||
|
OP_MUL = CScriptOp(0x95)
|
||||||
|
OP_DIV = CScriptOp(0x96)
|
||||||
|
OP_MOD = CScriptOp(0x97)
|
||||||
|
OP_LSHIFT = CScriptOp(0x98)
|
||||||
|
OP_RSHIFT = CScriptOp(0x99)
|
||||||
|
|
||||||
|
OP_BOOLAND = CScriptOp(0x9a)
|
||||||
|
OP_BOOLOR = CScriptOp(0x9b)
|
||||||
|
OP_NUMEQUAL = CScriptOp(0x9c)
|
||||||
|
OP_NUMEQUALVERIFY = CScriptOp(0x9d)
|
||||||
|
OP_NUMNOTEQUAL = CScriptOp(0x9e)
|
||||||
|
OP_LESSTHAN = CScriptOp(0x9f)
|
||||||
|
OP_GREATERTHAN = CScriptOp(0xa0)
|
||||||
|
OP_LESSTHANOREQUAL = CScriptOp(0xa1)
|
||||||
|
OP_GREATERTHANOREQUAL = CScriptOp(0xa2)
|
||||||
|
OP_MIN = CScriptOp(0xa3)
|
||||||
|
OP_MAX = CScriptOp(0xa4)
|
||||||
|
|
||||||
|
OP_WITHIN = CScriptOp(0xa5)
|
||||||
|
|
||||||
|
# crypto
|
||||||
|
OP_RIPEMD160 = CScriptOp(0xa6)
|
||||||
|
OP_SHA1 = CScriptOp(0xa7)
|
||||||
|
OP_SHA256 = CScriptOp(0xa8)
|
||||||
|
OP_HASH160 = CScriptOp(0xa9)
|
||||||
|
OP_HASH256 = CScriptOp(0xaa)
|
||||||
|
OP_CODESEPARATOR = CScriptOp(0xab)
|
||||||
|
OP_CHECKSIG = CScriptOp(0xac)
|
||||||
|
OP_CHECKSIGVERIFY = CScriptOp(0xad)
|
||||||
|
OP_CHECKMULTISIG = CScriptOp(0xae)
|
||||||
|
OP_CHECKMULTISIGVERIFY = CScriptOp(0xaf)
|
||||||
|
|
||||||
|
# expansion
|
||||||
|
OP_NOP1 = CScriptOp(0xb0)
|
||||||
|
OP_CHECKLOCKTIMEVERIFY = CScriptOp(0xb1)
|
||||||
|
OP_CHECKSEQUENCEVERIFY = CScriptOp(0xb2)
|
||||||
|
OP_NOP4 = CScriptOp(0xb3)
|
||||||
|
OP_NOP5 = CScriptOp(0xb4)
|
||||||
|
OP_NOP6 = CScriptOp(0xb5)
|
||||||
|
OP_NOP7 = CScriptOp(0xb6)
|
||||||
|
OP_NOP8 = CScriptOp(0xb7)
|
||||||
|
OP_NOP9 = CScriptOp(0xb8)
|
||||||
|
OP_NOP10 = CScriptOp(0xb9)
|
||||||
|
|
||||||
|
# template matching params
|
||||||
|
OP_SMALLINTEGER = CScriptOp(0xfa)
|
||||||
|
OP_PUBKEYS = CScriptOp(0xfb)
|
||||||
|
OP_PUBKEYHASH = CScriptOp(0xfd)
|
||||||
|
OP_PUBKEY = CScriptOp(0xfe)
|
||||||
|
|
||||||
|
OP_INVALIDOPCODE = CScriptOp(0xff)
|
||||||
|
|
||||||
|
OPCODE_NAMES.update({
|
||||||
|
OP_0: 'OP_0',
|
||||||
|
OP_PUSHDATA1: 'OP_PUSHDATA1',
|
||||||
|
OP_PUSHDATA2: 'OP_PUSHDATA2',
|
||||||
|
OP_PUSHDATA4: 'OP_PUSHDATA4',
|
||||||
|
OP_1NEGATE: 'OP_1NEGATE',
|
||||||
|
OP_RESERVED: 'OP_RESERVED',
|
||||||
|
OP_1: 'OP_1',
|
||||||
|
OP_2: 'OP_2',
|
||||||
|
OP_3: 'OP_3',
|
||||||
|
OP_4: 'OP_4',
|
||||||
|
OP_5: 'OP_5',
|
||||||
|
OP_6: 'OP_6',
|
||||||
|
OP_7: 'OP_7',
|
||||||
|
OP_8: 'OP_8',
|
||||||
|
OP_9: 'OP_9',
|
||||||
|
OP_10: 'OP_10',
|
||||||
|
OP_11: 'OP_11',
|
||||||
|
OP_12: 'OP_12',
|
||||||
|
OP_13: 'OP_13',
|
||||||
|
OP_14: 'OP_14',
|
||||||
|
OP_15: 'OP_15',
|
||||||
|
OP_16: 'OP_16',
|
||||||
|
OP_NOP: 'OP_NOP',
|
||||||
|
OP_VER: 'OP_VER',
|
||||||
|
OP_IF: 'OP_IF',
|
||||||
|
OP_NOTIF: 'OP_NOTIF',
|
||||||
|
OP_VERIF: 'OP_VERIF',
|
||||||
|
OP_VERNOTIF: 'OP_VERNOTIF',
|
||||||
|
OP_ELSE: 'OP_ELSE',
|
||||||
|
OP_ENDIF: 'OP_ENDIF',
|
||||||
|
OP_VERIFY: 'OP_VERIFY',
|
||||||
|
OP_RETURN: 'OP_RETURN',
|
||||||
|
OP_TOALTSTACK: 'OP_TOALTSTACK',
|
||||||
|
OP_FROMALTSTACK: 'OP_FROMALTSTACK',
|
||||||
|
OP_2DROP: 'OP_2DROP',
|
||||||
|
OP_2DUP: 'OP_2DUP',
|
||||||
|
OP_3DUP: 'OP_3DUP',
|
||||||
|
OP_2OVER: 'OP_2OVER',
|
||||||
|
OP_2ROT: 'OP_2ROT',
|
||||||
|
OP_2SWAP: 'OP_2SWAP',
|
||||||
|
OP_IFDUP: 'OP_IFDUP',
|
||||||
|
OP_DEPTH: 'OP_DEPTH',
|
||||||
|
OP_DROP: 'OP_DROP',
|
||||||
|
OP_DUP: 'OP_DUP',
|
||||||
|
OP_NIP: 'OP_NIP',
|
||||||
|
OP_OVER: 'OP_OVER',
|
||||||
|
OP_PICK: 'OP_PICK',
|
||||||
|
OP_ROLL: 'OP_ROLL',
|
||||||
|
OP_ROT: 'OP_ROT',
|
||||||
|
OP_SWAP: 'OP_SWAP',
|
||||||
|
OP_TUCK: 'OP_TUCK',
|
||||||
|
OP_CAT: 'OP_CAT',
|
||||||
|
OP_SUBSTR: 'OP_SUBSTR',
|
||||||
|
OP_LEFT: 'OP_LEFT',
|
||||||
|
OP_RIGHT: 'OP_RIGHT',
|
||||||
|
OP_SIZE: 'OP_SIZE',
|
||||||
|
OP_INVERT: 'OP_INVERT',
|
||||||
|
OP_AND: 'OP_AND',
|
||||||
|
OP_OR: 'OP_OR',
|
||||||
|
OP_XOR: 'OP_XOR',
|
||||||
|
OP_EQUAL: 'OP_EQUAL',
|
||||||
|
OP_EQUALVERIFY: 'OP_EQUALVERIFY',
|
||||||
|
OP_RESERVED1: 'OP_RESERVED1',
|
||||||
|
OP_RESERVED2: 'OP_RESERVED2',
|
||||||
|
OP_1ADD: 'OP_1ADD',
|
||||||
|
OP_1SUB: 'OP_1SUB',
|
||||||
|
OP_2MUL: 'OP_2MUL',
|
||||||
|
OP_2DIV: 'OP_2DIV',
|
||||||
|
OP_NEGATE: 'OP_NEGATE',
|
||||||
|
OP_ABS: 'OP_ABS',
|
||||||
|
OP_NOT: 'OP_NOT',
|
||||||
|
OP_0NOTEQUAL: 'OP_0NOTEQUAL',
|
||||||
|
OP_ADD: 'OP_ADD',
|
||||||
|
OP_SUB: 'OP_SUB',
|
||||||
|
OP_MUL: 'OP_MUL',
|
||||||
|
OP_DIV: 'OP_DIV',
|
||||||
|
OP_MOD: 'OP_MOD',
|
||||||
|
OP_LSHIFT: 'OP_LSHIFT',
|
||||||
|
OP_RSHIFT: 'OP_RSHIFT',
|
||||||
|
OP_BOOLAND: 'OP_BOOLAND',
|
||||||
|
OP_BOOLOR: 'OP_BOOLOR',
|
||||||
|
OP_NUMEQUAL: 'OP_NUMEQUAL',
|
||||||
|
OP_NUMEQUALVERIFY: 'OP_NUMEQUALVERIFY',
|
||||||
|
OP_NUMNOTEQUAL: 'OP_NUMNOTEQUAL',
|
||||||
|
OP_LESSTHAN: 'OP_LESSTHAN',
|
||||||
|
OP_GREATERTHAN: 'OP_GREATERTHAN',
|
||||||
|
OP_LESSTHANOREQUAL: 'OP_LESSTHANOREQUAL',
|
||||||
|
OP_GREATERTHANOREQUAL: 'OP_GREATERTHANOREQUAL',
|
||||||
|
OP_MIN: 'OP_MIN',
|
||||||
|
OP_MAX: 'OP_MAX',
|
||||||
|
OP_WITHIN: 'OP_WITHIN',
|
||||||
|
OP_RIPEMD160: 'OP_RIPEMD160',
|
||||||
|
OP_SHA1: 'OP_SHA1',
|
||||||
|
OP_SHA256: 'OP_SHA256',
|
||||||
|
OP_HASH160: 'OP_HASH160',
|
||||||
|
OP_HASH256: 'OP_HASH256',
|
||||||
|
OP_CODESEPARATOR: 'OP_CODESEPARATOR',
|
||||||
|
OP_CHECKSIG: 'OP_CHECKSIG',
|
||||||
|
OP_CHECKSIGVERIFY: 'OP_CHECKSIGVERIFY',
|
||||||
|
OP_CHECKMULTISIG: 'OP_CHECKMULTISIG',
|
||||||
|
OP_CHECKMULTISIGVERIFY: 'OP_CHECKMULTISIGVERIFY',
|
||||||
|
OP_NOP1: 'OP_NOP1',
|
||||||
|
OP_CHECKLOCKTIMEVERIFY: 'OP_CHECKLOCKTIMEVERIFY',
|
||||||
|
OP_CHECKSEQUENCEVERIFY: 'OP_CHECKSEQUENCEVERIFY',
|
||||||
|
OP_NOP4: 'OP_NOP4',
|
||||||
|
OP_NOP5: 'OP_NOP5',
|
||||||
|
OP_NOP6: 'OP_NOP6',
|
||||||
|
OP_NOP7: 'OP_NOP7',
|
||||||
|
OP_NOP8: 'OP_NOP8',
|
||||||
|
OP_NOP9: 'OP_NOP9',
|
||||||
|
OP_NOP10: 'OP_NOP10',
|
||||||
|
OP_SMALLINTEGER: 'OP_SMALLINTEGER',
|
||||||
|
OP_PUBKEYS: 'OP_PUBKEYS',
|
||||||
|
OP_PUBKEYHASH: 'OP_PUBKEYHASH',
|
||||||
|
OP_PUBKEY: 'OP_PUBKEY',
|
||||||
|
OP_INVALIDOPCODE: 'OP_INVALIDOPCODE',
|
||||||
|
})
|
||||||
|
|
||||||
|
class CScriptInvalidError(Exception):
|
||||||
|
"""Base class for CScript exceptions"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class CScriptTruncatedPushDataError(CScriptInvalidError):
|
||||||
|
"""Invalid pushdata due to truncation"""
|
||||||
|
def __init__(self, msg, data):
|
||||||
|
self.data = data
|
||||||
|
super().__init__(msg)
|
||||||
|
|
||||||
|
|
||||||
|
# This is used, eg, for blockchain heights in coinbase scripts (bip34)
|
||||||
|
class CScriptNum:
|
||||||
|
__slots__ = ("value",)
|
||||||
|
|
||||||
|
def __init__(self, d=0):
|
||||||
|
self.value = d
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encode(obj):
|
||||||
|
r = bytearray(0)
|
||||||
|
if obj.value == 0:
|
||||||
|
return bytes(r)
|
||||||
|
neg = obj.value < 0
|
||||||
|
absvalue = -obj.value if neg else obj.value
|
||||||
|
while (absvalue):
|
||||||
|
r.append(absvalue & 0xff)
|
||||||
|
absvalue >>= 8
|
||||||
|
if r[-1] & 0x80:
|
||||||
|
r.append(0x80 if neg else 0)
|
||||||
|
elif neg:
|
||||||
|
r[-1] |= 0x80
|
||||||
|
return bytes([len(r)]) + r
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def decode(vch):
|
||||||
|
result = 0
|
||||||
|
# We assume valid push_size and minimal encoding
|
||||||
|
value = vch[1:]
|
||||||
|
if len(value) == 0:
|
||||||
|
return result
|
||||||
|
for i, byte in enumerate(value):
|
||||||
|
result |= int(byte) << 8 * i
|
||||||
|
if value[-1] >= 0x80:
|
||||||
|
# Mask for all but the highest result bit
|
||||||
|
num_mask = (2**(len(value) * 8) - 1) >> 1
|
||||||
|
result &= num_mask
|
||||||
|
result *= -1
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class CScript(bytes):
|
||||||
|
"""Serialized script
|
||||||
|
|
||||||
|
A bytes subclass, so you can use this directly whenever bytes are accepted.
|
||||||
|
Note that this means that indexing does *not* work - you'll get an index by
|
||||||
|
byte rather than opcode. This format was chosen for efficiency so that the
|
||||||
|
general case would not require creating a lot of little CScriptOP objects.
|
||||||
|
|
||||||
|
iter(script) however does iterate by opcode.
|
||||||
|
"""
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __coerce_instance(cls, other):
|
||||||
|
# Coerce other into bytes
|
||||||
|
if isinstance(other, CScriptOp):
|
||||||
|
other = bytes([other])
|
||||||
|
elif isinstance(other, CScriptNum):
|
||||||
|
if (other.value == 0):
|
||||||
|
other = bytes([CScriptOp(OP_0)])
|
||||||
|
else:
|
||||||
|
other = CScriptNum.encode(other)
|
||||||
|
elif isinstance(other, int):
|
||||||
|
if 0 <= other <= 16:
|
||||||
|
other = bytes([CScriptOp.encode_op_n(other)])
|
||||||
|
elif other == -1:
|
||||||
|
other = bytes([OP_1NEGATE])
|
||||||
|
else:
|
||||||
|
other = CScriptOp.encode_op_pushdata(bn2vch(other))
|
||||||
|
elif isinstance(other, (bytes, bytearray)):
|
||||||
|
other = CScriptOp.encode_op_pushdata(other)
|
||||||
|
return other
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
# add makes no sense for a CScript()
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def join(self, iterable):
|
||||||
|
# join makes no sense for a CScript()
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __new__(cls, value=b''):
|
||||||
|
if isinstance(value, bytes) or isinstance(value, bytearray):
|
||||||
|
return super().__new__(cls, value)
|
||||||
|
else:
|
||||||
|
def coerce_iterable(iterable):
|
||||||
|
for instance in iterable:
|
||||||
|
yield cls.__coerce_instance(instance)
|
||||||
|
# Annoyingly on both python2 and python3 bytes.join() always
|
||||||
|
# returns a bytes instance even when subclassed.
|
||||||
|
return super().__new__(cls, b''.join(coerce_iterable(value)))
|
||||||
|
|
||||||
|
def raw_iter(self):
|
||||||
|
"""Raw iteration
|
||||||
|
|
||||||
|
Yields tuples of (opcode, data, sop_idx) so that the different possible
|
||||||
|
PUSHDATA encodings can be accurately distinguished, as well as
|
||||||
|
determining the exact opcode byte indexes. (sop_idx)
|
||||||
|
"""
|
||||||
|
i = 0
|
||||||
|
while i < len(self):
|
||||||
|
sop_idx = i
|
||||||
|
opcode = self[i]
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if opcode > OP_PUSHDATA4:
|
||||||
|
yield (opcode, None, sop_idx)
|
||||||
|
else:
|
||||||
|
datasize = None
|
||||||
|
pushdata_type = None
|
||||||
|
if opcode < OP_PUSHDATA1:
|
||||||
|
pushdata_type = 'PUSHDATA(%d)' % opcode
|
||||||
|
datasize = opcode
|
||||||
|
|
||||||
|
elif opcode == OP_PUSHDATA1:
|
||||||
|
pushdata_type = 'PUSHDATA1'
|
||||||
|
if i >= len(self):
|
||||||
|
raise CScriptInvalidError('PUSHDATA1: missing data length')
|
||||||
|
datasize = self[i]
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
elif opcode == OP_PUSHDATA2:
|
||||||
|
pushdata_type = 'PUSHDATA2'
|
||||||
|
if i + 1 >= len(self):
|
||||||
|
raise CScriptInvalidError('PUSHDATA2: missing data length')
|
||||||
|
datasize = self[i] + (self[i + 1] << 8)
|
||||||
|
i += 2
|
||||||
|
|
||||||
|
elif opcode == OP_PUSHDATA4:
|
||||||
|
pushdata_type = 'PUSHDATA4'
|
||||||
|
if i + 3 >= len(self):
|
||||||
|
raise CScriptInvalidError('PUSHDATA4: missing data length')
|
||||||
|
datasize = self[i] + (self[i + 1] << 8) + (self[i + 2] << 16) + (self[i + 3] << 24)
|
||||||
|
i += 4
|
||||||
|
|
||||||
|
else:
|
||||||
|
assert False # shouldn't happen
|
||||||
|
|
||||||
|
data = bytes(self[i:i + datasize])
|
||||||
|
|
||||||
|
# Check for truncation
|
||||||
|
if len(data) < datasize:
|
||||||
|
raise CScriptTruncatedPushDataError('%s: truncated data' % pushdata_type, data)
|
||||||
|
|
||||||
|
i += datasize
|
||||||
|
|
||||||
|
yield (opcode, data, sop_idx)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
"""'Cooked' iteration
|
||||||
|
|
||||||
|
Returns either a CScriptOP instance, an integer, or bytes, as
|
||||||
|
appropriate.
|
||||||
|
|
||||||
|
See raw_iter() if you need to distinguish the different possible
|
||||||
|
PUSHDATA encodings.
|
||||||
|
"""
|
||||||
|
for (opcode, data, sop_idx) in self.raw_iter():
|
||||||
|
if data is not None:
|
||||||
|
yield data
|
||||||
|
else:
|
||||||
|
opcode = CScriptOp(opcode)
|
||||||
|
|
||||||
|
if opcode.is_small_int():
|
||||||
|
yield opcode.decode_op_n()
|
||||||
|
else:
|
||||||
|
yield CScriptOp(opcode)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
def _repr(o):
|
||||||
|
if isinstance(o, bytes):
|
||||||
|
return "x('%s')" % o.hex()
|
||||||
|
else:
|
||||||
|
return repr(o)
|
||||||
|
|
||||||
|
ops = []
|
||||||
|
i = iter(self)
|
||||||
|
while True:
|
||||||
|
op = None
|
||||||
|
try:
|
||||||
|
op = _repr(next(i))
|
||||||
|
except CScriptTruncatedPushDataError as err:
|
||||||
|
op = '%s...<ERROR: %s>' % (_repr(err.data), err)
|
||||||
|
break
|
||||||
|
except CScriptInvalidError as err:
|
||||||
|
op = '<ERROR: %s>' % err
|
||||||
|
break
|
||||||
|
except StopIteration:
|
||||||
|
break
|
||||||
|
finally:
|
||||||
|
if op is not None:
|
||||||
|
ops.append(op)
|
||||||
|
|
||||||
|
return "CScript([%s])" % ', '.join(ops)
|
||||||
|
|
||||||
|
def GetSigOpCount(self, fAccurate):
|
||||||
|
"""Get the SigOp count.
|
||||||
|
|
||||||
|
fAccurate - Accurately count CHECKMULTISIG, see BIP16 for details.
|
||||||
|
|
||||||
|
Note that this is consensus-critical.
|
||||||
|
"""
|
||||||
|
n = 0
|
||||||
|
lastOpcode = OP_INVALIDOPCODE
|
||||||
|
for (opcode, data, sop_idx) in self.raw_iter():
|
||||||
|
if opcode in (OP_CHECKSIG, OP_CHECKSIGVERIFY):
|
||||||
|
n += 1
|
||||||
|
elif opcode in (OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY):
|
||||||
|
if fAccurate and (OP_1 <= lastOpcode <= OP_16):
|
||||||
|
n += opcode.decode_op_n()
|
||||||
|
else:
|
||||||
|
n += 20
|
||||||
|
lastOpcode = opcode
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
|
SIGHASH_ALL = 1
|
||||||
|
SIGHASH_NONE = 2
|
||||||
|
SIGHASH_SINGLE = 3
|
||||||
|
SIGHASH_ANYONECANPAY = 0x80
|
||||||
|
|
||||||
|
def FindAndDelete(script, sig):
|
||||||
|
"""Consensus critical, see FindAndDelete() in Satoshi codebase"""
|
||||||
|
r = b''
|
||||||
|
last_sop_idx = sop_idx = 0
|
||||||
|
skip = True
|
||||||
|
for (opcode, data, sop_idx) in script.raw_iter():
|
||||||
|
if not skip:
|
||||||
|
r += script[last_sop_idx:sop_idx]
|
||||||
|
last_sop_idx = sop_idx
|
||||||
|
if script[sop_idx:sop_idx + len(sig)] == sig:
|
||||||
|
skip = True
|
||||||
|
else:
|
||||||
|
skip = False
|
||||||
|
if not skip:
|
||||||
|
r += script[last_sop_idx:]
|
||||||
|
return CScript(r)
|
||||||
|
|
||||||
|
|
||||||
|
def LegacySignatureHash(script, txTo, inIdx, hashtype):
|
||||||
|
"""Consensus-correct SignatureHash
|
||||||
|
|
||||||
|
Returns (hash, err) to precisely match the consensus-critical behavior of
|
||||||
|
the SIGHASH_SINGLE bug. (inIdx is *not* checked for validity)
|
||||||
|
"""
|
||||||
|
HASH_ONE = b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||||
|
|
||||||
|
if inIdx >= len(txTo.vin):
|
||||||
|
return (HASH_ONE, "inIdx %d out of range (%d)" % (inIdx, len(txTo.vin)))
|
||||||
|
txtmp = CTransaction(txTo)
|
||||||
|
|
||||||
|
for txin in txtmp.vin:
|
||||||
|
txin.scriptSig = b''
|
||||||
|
txtmp.vin[inIdx].scriptSig = FindAndDelete(script, CScript([OP_CODESEPARATOR]))
|
||||||
|
|
||||||
|
if (hashtype & 0x1f) == SIGHASH_NONE:
|
||||||
|
txtmp.vout = []
|
||||||
|
|
||||||
|
for i in range(len(txtmp.vin)):
|
||||||
|
if i != inIdx:
|
||||||
|
txtmp.vin[i].nSequence = 0
|
||||||
|
|
||||||
|
elif (hashtype & 0x1f) == SIGHASH_SINGLE:
|
||||||
|
outIdx = inIdx
|
||||||
|
if outIdx >= len(txtmp.vout):
|
||||||
|
return (HASH_ONE, "outIdx %d out of range (%d)" % (outIdx, len(txtmp.vout)))
|
||||||
|
|
||||||
|
tmp = txtmp.vout[outIdx]
|
||||||
|
txtmp.vout = []
|
||||||
|
for i in range(outIdx):
|
||||||
|
txtmp.vout.append(CTxOut(-1))
|
||||||
|
txtmp.vout.append(tmp)
|
||||||
|
|
||||||
|
for i in range(len(txtmp.vin)):
|
||||||
|
if i != inIdx:
|
||||||
|
txtmp.vin[i].nSequence = 0
|
||||||
|
|
||||||
|
if hashtype & SIGHASH_ANYONECANPAY:
|
||||||
|
tmp = txtmp.vin[inIdx]
|
||||||
|
txtmp.vin = []
|
||||||
|
txtmp.vin.append(tmp)
|
||||||
|
|
||||||
|
s = txtmp.serialize_without_witness()
|
||||||
|
s += struct.pack(b"<I", hashtype)
|
||||||
|
|
||||||
|
hash = hash256(s)
|
||||||
|
|
||||||
|
return (hash, None)
|
||||||
|
|
||||||
|
# TODO: Allow cached hashPrevouts/hashSequence/hashOutputs to be provided.
|
||||||
|
# Performance optimization probably not necessary for python tests, however.
|
||||||
|
# Note that this corresponds to sigversion == 1 in EvalScript, which is used
|
||||||
|
# for version 0 witnesses.
|
||||||
|
def SegwitV0SignatureHash(script, txTo, inIdx, hashtype, amount):
|
||||||
|
|
||||||
|
hashPrevouts = 0
|
||||||
|
hashSequence = 0
|
||||||
|
hashOutputs = 0
|
||||||
|
|
||||||
|
if not (hashtype & SIGHASH_ANYONECANPAY):
|
||||||
|
serialize_prevouts = bytes()
|
||||||
|
for i in txTo.vin:
|
||||||
|
serialize_prevouts += i.prevout.serialize()
|
||||||
|
hashPrevouts = uint256_from_str(hash256(serialize_prevouts))
|
||||||
|
|
||||||
|
if (not (hashtype & SIGHASH_ANYONECANPAY) and (hashtype & 0x1f) != SIGHASH_SINGLE and (hashtype & 0x1f) != SIGHASH_NONE):
|
||||||
|
serialize_sequence = bytes()
|
||||||
|
for i in txTo.vin:
|
||||||
|
serialize_sequence += struct.pack("<I", i.nSequence)
|
||||||
|
hashSequence = uint256_from_str(hash256(serialize_sequence))
|
||||||
|
|
||||||
|
if ((hashtype & 0x1f) != SIGHASH_SINGLE and (hashtype & 0x1f) != SIGHASH_NONE):
|
||||||
|
serialize_outputs = bytes()
|
||||||
|
for o in txTo.vout:
|
||||||
|
serialize_outputs += o.serialize()
|
||||||
|
hashOutputs = uint256_from_str(hash256(serialize_outputs))
|
||||||
|
elif ((hashtype & 0x1f) == SIGHASH_SINGLE and inIdx < len(txTo.vout)):
|
||||||
|
serialize_outputs = txTo.vout[inIdx].serialize()
|
||||||
|
hashOutputs = uint256_from_str(hash256(serialize_outputs))
|
||||||
|
|
||||||
|
ss = bytes()
|
||||||
|
ss += struct.pack("<i", txTo.nVersion)
|
||||||
|
ss += ser_uint256(hashPrevouts)
|
||||||
|
ss += ser_uint256(hashSequence)
|
||||||
|
ss += txTo.vin[inIdx].prevout.serialize()
|
||||||
|
ss += ser_string(script)
|
||||||
|
ss += struct.pack("<q", amount)
|
||||||
|
ss += struct.pack("<I", txTo.vin[inIdx].nSequence)
|
||||||
|
ss += ser_uint256(hashOutputs)
|
||||||
|
ss += struct.pack("<i", txTo.nLockTime)
|
||||||
|
ss += struct.pack("<I", hashtype)
|
||||||
|
|
||||||
|
return hash256(ss)
|
||||||
|
|
||||||
|
class TestFrameworkScript(unittest.TestCase):
|
||||||
|
def test_bn2vch(self):
|
||||||
|
self.assertEqual(bn2vch(0), bytes([]))
|
||||||
|
self.assertEqual(bn2vch(1), bytes([0x01]))
|
||||||
|
self.assertEqual(bn2vch(-1), bytes([0x81]))
|
||||||
|
self.assertEqual(bn2vch(0x7F), bytes([0x7F]))
|
||||||
|
self.assertEqual(bn2vch(-0x7F), bytes([0xFF]))
|
||||||
|
self.assertEqual(bn2vch(0x80), bytes([0x80, 0x00]))
|
||||||
|
self.assertEqual(bn2vch(-0x80), bytes([0x80, 0x80]))
|
||||||
|
self.assertEqual(bn2vch(0xFF), bytes([0xFF, 0x00]))
|
||||||
|
self.assertEqual(bn2vch(-0xFF), bytes([0xFF, 0x80]))
|
||||||
|
self.assertEqual(bn2vch(0x100), bytes([0x00, 0x01]))
|
||||||
|
self.assertEqual(bn2vch(-0x100), bytes([0x00, 0x81]))
|
||||||
|
self.assertEqual(bn2vch(0x7FFF), bytes([0xFF, 0x7F]))
|
||||||
|
self.assertEqual(bn2vch(-0x8000), bytes([0x00, 0x80, 0x80]))
|
||||||
|
self.assertEqual(bn2vch(-0x7FFFFF), bytes([0xFF, 0xFF, 0xFF]))
|
||||||
|
self.assertEqual(bn2vch(0x80000000), bytes([0x00, 0x00, 0x00, 0x80, 0x00]))
|
||||||
|
self.assertEqual(bn2vch(-0x80000000), bytes([0x00, 0x00, 0x00, 0x80, 0x80]))
|
||||||
|
self.assertEqual(bn2vch(0xFFFFFFFF), bytes([0xFF, 0xFF, 0xFF, 0xFF, 0x00]))
|
||||||
|
self.assertEqual(bn2vch(123456789), bytes([0x15, 0xCD, 0x5B, 0x07]))
|
||||||
|
self.assertEqual(bn2vch(-54321), bytes([0x31, 0xD4, 0x80]))
|
||||||
|
|
||||||
|
def test_cscriptnum_encoding(self):
|
||||||
|
# round-trip negative and multi-byte CScriptNums
|
||||||
|
values = [0, 1, -1, -2, 127, 128, -255, 256, (1 << 15) - 1, -(1 << 16), (1 << 24) - 1, (1 << 31), 1 - (1 << 32), 1 << 40, 1500, -1500]
|
||||||
|
for value in values:
|
||||||
|
self.assertEqual(CScriptNum.decode(CScriptNum.encode(CScriptNum(value))), value)
|
107
basicswap/contrib/test_framework/segwit_addr.py
Normal file
107
basicswap/contrib/test_framework/segwit_addr.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2017 Pieter Wuille
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
"""Reference implementation for Bech32 and segwit addresses."""
|
||||||
|
|
||||||
|
|
||||||
|
CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
|
||||||
|
|
||||||
|
|
||||||
|
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."""
|
||||||
|
return bech32_polymod(bech32_hrp_expand(hrp) + data) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def bech32_create_checksum(hrp, data):
|
||||||
|
"""Compute the checksum values given HRP and data."""
|
||||||
|
values = bech32_hrp_expand(hrp) + data
|
||||||
|
polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ 1
|
||||||
|
return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
|
||||||
|
|
||||||
|
|
||||||
|
def bech32_encode(hrp, data):
|
||||||
|
"""Compute a Bech32 string given HRP and data values."""
|
||||||
|
combined = data + bech32_create_checksum(hrp, data)
|
||||||
|
return hrp + '1' + ''.join([CHARSET[d] for d in combined])
|
||||||
|
|
||||||
|
|
||||||
|
def bech32_decode(bech):
|
||||||
|
"""Validate a Bech32 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)
|
||||||
|
bech = bech.lower()
|
||||||
|
pos = bech.rfind('1')
|
||||||
|
if pos < 1 or pos + 7 > len(bech) or len(bech) > 90:
|
||||||
|
return (None, None)
|
||||||
|
if not all(x in CHARSET for x in bech[pos+1:]):
|
||||||
|
return (None, None)
|
||||||
|
hrp = bech[:pos]
|
||||||
|
data = [CHARSET.find(x) for x in bech[pos+1:]]
|
||||||
|
if not bech32_verify_checksum(hrp, data):
|
||||||
|
return (None, None)
|
||||||
|
return (hrp, data[:-6])
|
||||||
|
|
||||||
|
|
||||||
|
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 = 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)
|
||||||
|
return (data[0], decoded)
|
||||||
|
|
||||||
|
|
||||||
|
def encode(hrp, witver, witprog):
|
||||||
|
"""Encode a segwit address."""
|
||||||
|
ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5))
|
||||||
|
if decode(hrp, ret) == (None, None):
|
||||||
|
return None
|
||||||
|
return ret
|
63
basicswap/contrib/test_framework/siphash.py
Normal file
63
basicswap/contrib/test_framework/siphash.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2016-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.
|
||||||
|
"""Specialized SipHash-2-4 implementations.
|
||||||
|
|
||||||
|
This implements SipHash-2-4 for 256-bit integers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def rotl64(n, b):
|
||||||
|
return n >> (64 - b) | (n & ((1 << (64 - b)) - 1)) << b
|
||||||
|
|
||||||
|
def siphash_round(v0, v1, v2, v3):
|
||||||
|
v0 = (v0 + v1) & ((1 << 64) - 1)
|
||||||
|
v1 = rotl64(v1, 13)
|
||||||
|
v1 ^= v0
|
||||||
|
v0 = rotl64(v0, 32)
|
||||||
|
v2 = (v2 + v3) & ((1 << 64) - 1)
|
||||||
|
v3 = rotl64(v3, 16)
|
||||||
|
v3 ^= v2
|
||||||
|
v0 = (v0 + v3) & ((1 << 64) - 1)
|
||||||
|
v3 = rotl64(v3, 21)
|
||||||
|
v3 ^= v0
|
||||||
|
v2 = (v2 + v1) & ((1 << 64) - 1)
|
||||||
|
v1 = rotl64(v1, 17)
|
||||||
|
v1 ^= v2
|
||||||
|
v2 = rotl64(v2, 32)
|
||||||
|
return (v0, v1, v2, v3)
|
||||||
|
|
||||||
|
def siphash256(k0, k1, h):
|
||||||
|
n0 = h & ((1 << 64) - 1)
|
||||||
|
n1 = (h >> 64) & ((1 << 64) - 1)
|
||||||
|
n2 = (h >> 128) & ((1 << 64) - 1)
|
||||||
|
n3 = (h >> 192) & ((1 << 64) - 1)
|
||||||
|
v0 = 0x736f6d6570736575 ^ k0
|
||||||
|
v1 = 0x646f72616e646f6d ^ k1
|
||||||
|
v2 = 0x6c7967656e657261 ^ k0
|
||||||
|
v3 = 0x7465646279746573 ^ k1 ^ n0
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0 ^= n0
|
||||||
|
v3 ^= n1
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0 ^= n1
|
||||||
|
v3 ^= n2
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0 ^= n2
|
||||||
|
v3 ^= n3
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0 ^= n3
|
||||||
|
v3 ^= 0x2000000000000000
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0 ^= 0x2000000000000000
|
||||||
|
v2 ^= 0xFF
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
return v0 ^ v1 ^ v2 ^ v3
|
619
basicswap/contrib/test_framework/util.py
Normal file
619
basicswap/contrib/test_framework/util.py
Normal file
@ -0,0 +1,619 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2014-2020 The Bitcoin Core developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
"""Helpful routines for regression testing."""
|
||||||
|
|
||||||
|
from base64 import b64encode
|
||||||
|
from binascii import unhexlify
|
||||||
|
from decimal import Decimal, ROUND_DOWN
|
||||||
|
from subprocess import CalledProcessError
|
||||||
|
import inspect
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
|
from . import coverage
|
||||||
|
from .authproxy import AuthServiceProxy, JSONRPCException
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
logger = logging.getLogger("TestFramework.utils")
|
||||||
|
|
||||||
|
# Assert functions
|
||||||
|
##################
|
||||||
|
|
||||||
|
|
||||||
|
def assert_approx(v, vexp, vspan=0.00001):
|
||||||
|
"""Assert that `v` is within `vspan` of `vexp`"""
|
||||||
|
if v < vexp - vspan:
|
||||||
|
raise AssertionError("%s < [%s..%s]" % (str(v), str(vexp - vspan), str(vexp + vspan)))
|
||||||
|
if v > vexp + vspan:
|
||||||
|
raise AssertionError("%s > [%s..%s]" % (str(v), str(vexp - vspan), str(vexp + vspan)))
|
||||||
|
|
||||||
|
|
||||||
|
def assert_fee_amount(fee, tx_size, fee_per_kB):
|
||||||
|
"""Assert the fee was in range"""
|
||||||
|
target_fee = round(tx_size * fee_per_kB / 1000, 8)
|
||||||
|
if fee < target_fee:
|
||||||
|
raise AssertionError("Fee of %s BTC too low! (Should be %s BTC)" % (str(fee), str(target_fee)))
|
||||||
|
# allow the wallet's estimation to be at most 2 bytes off
|
||||||
|
if fee > (tx_size + 2) * fee_per_kB / 1000:
|
||||||
|
raise AssertionError("Fee of %s BTC too high! (Should be %s BTC)" % (str(fee), str(target_fee)))
|
||||||
|
|
||||||
|
|
||||||
|
def assert_equal(thing1, thing2, *args):
|
||||||
|
if thing1 != thing2 or any(thing1 != arg for arg in args):
|
||||||
|
raise AssertionError("not(%s)" % " == ".join(str(arg) for arg in (thing1, thing2) + args))
|
||||||
|
|
||||||
|
|
||||||
|
def assert_greater_than(thing1, thing2):
|
||||||
|
if thing1 <= thing2:
|
||||||
|
raise AssertionError("%s <= %s" % (str(thing1), str(thing2)))
|
||||||
|
|
||||||
|
|
||||||
|
def assert_greater_than_or_equal(thing1, thing2):
|
||||||
|
if thing1 < thing2:
|
||||||
|
raise AssertionError("%s < %s" % (str(thing1), str(thing2)))
|
||||||
|
|
||||||
|
|
||||||
|
def assert_raises(exc, fun, *args, **kwds):
|
||||||
|
assert_raises_message(exc, None, fun, *args, **kwds)
|
||||||
|
|
||||||
|
|
||||||
|
def assert_raises_message(exc, message, fun, *args, **kwds):
|
||||||
|
try:
|
||||||
|
fun(*args, **kwds)
|
||||||
|
except JSONRPCException:
|
||||||
|
raise AssertionError("Use assert_raises_rpc_error() to test RPC failures")
|
||||||
|
except exc as e:
|
||||||
|
if message is not None and message not in e.error['message']:
|
||||||
|
raise AssertionError(
|
||||||
|
"Expected substring not found in error message:\nsubstring: '{}'\nerror message: '{}'.".format(
|
||||||
|
message, e.error['message']))
|
||||||
|
except Exception as e:
|
||||||
|
raise AssertionError("Unexpected exception raised: " + type(e).__name__)
|
||||||
|
else:
|
||||||
|
raise AssertionError("No exception raised")
|
||||||
|
|
||||||
|
|
||||||
|
def assert_raises_process_error(returncode, output, fun, *args, **kwds):
|
||||||
|
"""Execute a process and asserts the process return code and output.
|
||||||
|
|
||||||
|
Calls function `fun` with arguments `args` and `kwds`. Catches a CalledProcessError
|
||||||
|
and verifies that the return code and output are as expected. Throws AssertionError if
|
||||||
|
no CalledProcessError was raised or if the return code and output are not as expected.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
returncode (int): the process return code.
|
||||||
|
output (string): [a substring of] the process output.
|
||||||
|
fun (function): the function to call. This should execute a process.
|
||||||
|
args*: positional arguments for the function.
|
||||||
|
kwds**: named arguments for the function.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
fun(*args, **kwds)
|
||||||
|
except CalledProcessError as e:
|
||||||
|
if returncode != e.returncode:
|
||||||
|
raise AssertionError("Unexpected returncode %i" % e.returncode)
|
||||||
|
if output not in e.output:
|
||||||
|
raise AssertionError("Expected substring not found:" + e.output)
|
||||||
|
else:
|
||||||
|
raise AssertionError("No exception raised")
|
||||||
|
|
||||||
|
|
||||||
|
def assert_raises_rpc_error(code, message, fun, *args, **kwds):
|
||||||
|
"""Run an RPC and verify that a specific JSONRPC exception code and message is raised.
|
||||||
|
|
||||||
|
Calls function `fun` with arguments `args` and `kwds`. Catches a JSONRPCException
|
||||||
|
and verifies that the error code and message are as expected. Throws AssertionError if
|
||||||
|
no JSONRPCException was raised or if the error code/message are not as expected.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
code (int), optional: the error code returned by the RPC call (defined
|
||||||
|
in src/rpc/protocol.h). Set to None if checking the error code is not required.
|
||||||
|
message (string), optional: [a substring of] the error string returned by the
|
||||||
|
RPC call. Set to None if checking the error string is not required.
|
||||||
|
fun (function): the function to call. This should be the name of an RPC.
|
||||||
|
args*: positional arguments for the function.
|
||||||
|
kwds**: named arguments for the function.
|
||||||
|
"""
|
||||||
|
assert try_rpc(code, message, fun, *args, **kwds), "No exception raised"
|
||||||
|
|
||||||
|
|
||||||
|
def try_rpc(code, message, fun, *args, **kwds):
|
||||||
|
"""Tries to run an rpc command.
|
||||||
|
|
||||||
|
Test against error code and message if the rpc fails.
|
||||||
|
Returns whether a JSONRPCException was raised."""
|
||||||
|
try:
|
||||||
|
fun(*args, **kwds)
|
||||||
|
except JSONRPCException as e:
|
||||||
|
# JSONRPCException was thrown as expected. Check the code and message values are correct.
|
||||||
|
if (code is not None) and (code != e.error["code"]):
|
||||||
|
raise AssertionError("Unexpected JSONRPC error code %i" % e.error["code"])
|
||||||
|
if (message is not None) and (message not in e.error['message']):
|
||||||
|
raise AssertionError(
|
||||||
|
"Expected substring not found in error message:\nsubstring: '{}'\nerror message: '{}'.".format(
|
||||||
|
message, e.error['message']))
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
raise AssertionError("Unexpected exception raised: " + type(e).__name__)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def assert_is_hex_string(string):
|
||||||
|
try:
|
||||||
|
int(string, 16)
|
||||||
|
except Exception as e:
|
||||||
|
raise AssertionError("Couldn't interpret %r as hexadecimal; raised: %s" % (string, e))
|
||||||
|
|
||||||
|
|
||||||
|
def assert_is_hash_string(string, length=64):
|
||||||
|
if not isinstance(string, str):
|
||||||
|
raise AssertionError("Expected a string, got type %r" % type(string))
|
||||||
|
elif length and len(string) != length:
|
||||||
|
raise AssertionError("String of length %d expected; got %d" % (length, len(string)))
|
||||||
|
elif not re.match('[abcdef0-9]+$', string):
|
||||||
|
raise AssertionError("String %r contains invalid characters for a hash." % string)
|
||||||
|
|
||||||
|
|
||||||
|
def assert_array_result(object_array, to_match, expected, should_not_find=False):
|
||||||
|
"""
|
||||||
|
Pass in array of JSON objects, a dictionary with key/value pairs
|
||||||
|
to match against, and another dictionary with expected key/value
|
||||||
|
pairs.
|
||||||
|
If the should_not_find flag is true, to_match should not be found
|
||||||
|
in object_array
|
||||||
|
"""
|
||||||
|
if should_not_find:
|
||||||
|
assert_equal(expected, {})
|
||||||
|
num_matched = 0
|
||||||
|
for item in object_array:
|
||||||
|
all_match = True
|
||||||
|
for key, value in to_match.items():
|
||||||
|
if item[key] != value:
|
||||||
|
all_match = False
|
||||||
|
if not all_match:
|
||||||
|
continue
|
||||||
|
elif should_not_find:
|
||||||
|
num_matched = num_matched + 1
|
||||||
|
for key, value in expected.items():
|
||||||
|
if item[key] != value:
|
||||||
|
raise AssertionError("%s : expected %s=%s" % (str(item), str(key), str(value)))
|
||||||
|
num_matched = num_matched + 1
|
||||||
|
if num_matched == 0 and not should_not_find:
|
||||||
|
raise AssertionError("No objects matched %s" % (str(to_match)))
|
||||||
|
if num_matched > 0 and should_not_find:
|
||||||
|
raise AssertionError("Objects were found %s" % (str(to_match)))
|
||||||
|
|
||||||
|
|
||||||
|
# Utility functions
|
||||||
|
###################
|
||||||
|
|
||||||
|
|
||||||
|
def check_json_precision():
|
||||||
|
"""Make sure json library being used does not lose precision converting BTC values"""
|
||||||
|
n = Decimal("20000000.00000003")
|
||||||
|
satoshis = int(json.loads(json.dumps(float(n))) * 1.0e8)
|
||||||
|
if satoshis != 2000000000000003:
|
||||||
|
raise RuntimeError("JSON encode/decode loses precision")
|
||||||
|
|
||||||
|
|
||||||
|
def EncodeDecimal(o):
|
||||||
|
if isinstance(o, Decimal):
|
||||||
|
return str(o)
|
||||||
|
raise TypeError(repr(o) + " is not JSON serializable")
|
||||||
|
|
||||||
|
|
||||||
|
def count_bytes(hex_string):
|
||||||
|
return len(bytearray.fromhex(hex_string))
|
||||||
|
|
||||||
|
|
||||||
|
def hex_str_to_bytes(hex_str):
|
||||||
|
return unhexlify(hex_str.encode('ascii'))
|
||||||
|
|
||||||
|
|
||||||
|
def str_to_b64str(string):
|
||||||
|
return b64encode(string.encode('utf-8')).decode('ascii')
|
||||||
|
|
||||||
|
|
||||||
|
def satoshi_round(amount):
|
||||||
|
return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
|
||||||
|
|
||||||
|
|
||||||
|
def wait_until(predicate, *, attempts=float('inf'), timeout=float('inf'), lock=None, timeout_factor=1.0):
|
||||||
|
if attempts == float('inf') and timeout == float('inf'):
|
||||||
|
timeout = 60
|
||||||
|
timeout = timeout * timeout_factor
|
||||||
|
attempt = 0
|
||||||
|
time_end = time.time() + timeout
|
||||||
|
|
||||||
|
while attempt < attempts and time.time() < time_end:
|
||||||
|
if lock:
|
||||||
|
with lock:
|
||||||
|
if predicate():
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
if predicate():
|
||||||
|
return
|
||||||
|
attempt += 1
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
# Print the cause of the timeout
|
||||||
|
predicate_source = "''''\n" + inspect.getsource(predicate) + "'''"
|
||||||
|
logger.error("wait_until() failed. Predicate: {}".format(predicate_source))
|
||||||
|
if attempt >= attempts:
|
||||||
|
raise AssertionError("Predicate {} not true after {} attempts".format(predicate_source, attempts))
|
||||||
|
elif time.time() >= time_end:
|
||||||
|
raise AssertionError("Predicate {} not true after {} seconds".format(predicate_source, timeout))
|
||||||
|
raise RuntimeError('Unreachable')
|
||||||
|
|
||||||
|
|
||||||
|
# RPC/P2P connection constants and functions
|
||||||
|
############################################
|
||||||
|
|
||||||
|
# The maximum number of nodes a single test can spawn
|
||||||
|
MAX_NODES = 12
|
||||||
|
# Don't assign rpc or p2p ports lower than this
|
||||||
|
PORT_MIN = int(os.getenv('TEST_RUNNER_PORT_MIN', default=11000))
|
||||||
|
# The number of ports to "reserve" for p2p and rpc, each
|
||||||
|
PORT_RANGE = 5000
|
||||||
|
|
||||||
|
|
||||||
|
class PortSeed:
|
||||||
|
# Must be initialized with a unique integer for each process
|
||||||
|
n = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_rpc_proxy(url, node_number, *, timeout=None, coveragedir=None):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
url (str): URL of the RPC server to call
|
||||||
|
node_number (int): the node number (or id) that this calls to
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
timeout (int): HTTP timeout in seconds
|
||||||
|
coveragedir (str): Directory
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
AuthServiceProxy. convenience object for making RPC calls.
|
||||||
|
|
||||||
|
"""
|
||||||
|
proxy_kwargs = {}
|
||||||
|
if timeout is not None:
|
||||||
|
proxy_kwargs['timeout'] = int(timeout)
|
||||||
|
|
||||||
|
proxy = AuthServiceProxy(url, **proxy_kwargs)
|
||||||
|
proxy.url = url # store URL on proxy for info
|
||||||
|
|
||||||
|
coverage_logfile = coverage.get_filename(coveragedir, node_number) if coveragedir else None
|
||||||
|
|
||||||
|
return coverage.AuthServiceProxyWrapper(proxy, coverage_logfile)
|
||||||
|
|
||||||
|
|
||||||
|
def p2p_port(n):
|
||||||
|
assert n <= MAX_NODES
|
||||||
|
return PORT_MIN + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
|
||||||
|
|
||||||
|
|
||||||
|
def rpc_port(n):
|
||||||
|
return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
|
||||||
|
|
||||||
|
|
||||||
|
def rpc_url(datadir, i, chain, rpchost):
|
||||||
|
rpc_u, rpc_p = get_auth_cookie(datadir, chain)
|
||||||
|
host = '127.0.0.1'
|
||||||
|
port = rpc_port(i)
|
||||||
|
if rpchost:
|
||||||
|
parts = rpchost.split(':')
|
||||||
|
if len(parts) == 2:
|
||||||
|
host, port = parts
|
||||||
|
else:
|
||||||
|
host = rpchost
|
||||||
|
return "http://%s:%s@%s:%d" % (rpc_u, rpc_p, host, int(port))
|
||||||
|
|
||||||
|
|
||||||
|
# Node functions
|
||||||
|
################
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_datadir(dirname, n, chain):
|
||||||
|
datadir = get_datadir_path(dirname, n)
|
||||||
|
if not os.path.isdir(datadir):
|
||||||
|
os.makedirs(datadir)
|
||||||
|
# Translate chain name to config name
|
||||||
|
if chain == 'testnet3':
|
||||||
|
chain_name_conf_arg = 'testnet'
|
||||||
|
chain_name_conf_section = 'test'
|
||||||
|
else:
|
||||||
|
chain_name_conf_arg = chain
|
||||||
|
chain_name_conf_section = chain
|
||||||
|
with open(os.path.join(datadir, "particl.conf"), 'w', encoding='utf8') as f:
|
||||||
|
f.write("{}=1\n".format(chain_name_conf_arg))
|
||||||
|
f.write("[{}]\n".format(chain_name_conf_section))
|
||||||
|
f.write("port=" + str(p2p_port(n)) + "\n")
|
||||||
|
f.write("rpcport=" + str(rpc_port(n)) + "\n")
|
||||||
|
f.write("fallbackfee=0.0002\n")
|
||||||
|
f.write("server=1\n")
|
||||||
|
f.write("keypool=1\n")
|
||||||
|
f.write("discover=0\n")
|
||||||
|
f.write("dnsseed=0\n")
|
||||||
|
f.write("listenonion=0\n")
|
||||||
|
f.write("printtoconsole=0\n")
|
||||||
|
f.write("upnp=0\n")
|
||||||
|
f.write("shrinkdebugfile=0\n")
|
||||||
|
os.makedirs(os.path.join(datadir, 'stderr'), exist_ok=True)
|
||||||
|
os.makedirs(os.path.join(datadir, 'stdout'), exist_ok=True)
|
||||||
|
return datadir
|
||||||
|
|
||||||
|
|
||||||
|
def get_datadir_path(dirname, n):
|
||||||
|
return os.path.join(dirname, "node" + str(n))
|
||||||
|
|
||||||
|
|
||||||
|
def append_config(datadir, options):
|
||||||
|
with open(os.path.join(datadir, "particl.conf"), 'a', encoding='utf8') as f:
|
||||||
|
for option in options:
|
||||||
|
f.write(option + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def get_auth_cookie(datadir, chain):
|
||||||
|
user = None
|
||||||
|
password = None
|
||||||
|
if os.path.isfile(os.path.join(datadir, "particl.conf")):
|
||||||
|
with open(os.path.join(datadir, "particl.conf"), 'r', encoding='utf8') as f:
|
||||||
|
for line in f:
|
||||||
|
if line.startswith("rpcuser="):
|
||||||
|
assert user is None # Ensure that there is only one rpcuser line
|
||||||
|
user = line.split("=")[1].strip("\n")
|
||||||
|
if line.startswith("rpcpassword="):
|
||||||
|
assert password is None # Ensure that there is only one rpcpassword line
|
||||||
|
password = line.split("=")[1].strip("\n")
|
||||||
|
try:
|
||||||
|
with open(os.path.join(datadir, chain, ".cookie"), 'r', encoding="ascii") as f:
|
||||||
|
userpass = f.read()
|
||||||
|
split_userpass = userpass.split(':')
|
||||||
|
user = split_userpass[0]
|
||||||
|
password = split_userpass[1]
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
if user is None or password is None:
|
||||||
|
raise ValueError("No RPC credentials")
|
||||||
|
return user, password
|
||||||
|
|
||||||
|
|
||||||
|
# If a cookie file exists in the given datadir, delete it.
|
||||||
|
def delete_cookie_file(datadir, chain):
|
||||||
|
if os.path.isfile(os.path.join(datadir, chain, ".cookie")):
|
||||||
|
logger.debug("Deleting leftover cookie file")
|
||||||
|
os.remove(os.path.join(datadir, chain, ".cookie"))
|
||||||
|
|
||||||
|
|
||||||
|
def softfork_active(node, key):
|
||||||
|
"""Return whether a softfork is active."""
|
||||||
|
return node.getblockchaininfo()['softforks'][key]['active']
|
||||||
|
|
||||||
|
|
||||||
|
def set_node_times(nodes, t):
|
||||||
|
for node in nodes:
|
||||||
|
node.setmocktime(t)
|
||||||
|
|
||||||
|
|
||||||
|
def disconnect_nodes(from_connection, node_num):
|
||||||
|
def get_peer_ids():
|
||||||
|
result = []
|
||||||
|
for peer in from_connection.getpeerinfo():
|
||||||
|
if "testnode{}".format(node_num) in peer['subver']:
|
||||||
|
result.append(peer['id'])
|
||||||
|
return result
|
||||||
|
|
||||||
|
peer_ids = get_peer_ids()
|
||||||
|
if not peer_ids:
|
||||||
|
logger.warning("disconnect_nodes: {} and {} were not connected".format(
|
||||||
|
from_connection.index,
|
||||||
|
node_num,
|
||||||
|
))
|
||||||
|
return
|
||||||
|
for peer_id in peer_ids:
|
||||||
|
try:
|
||||||
|
from_connection.disconnectnode(nodeid=peer_id)
|
||||||
|
except JSONRPCException as e:
|
||||||
|
# If this node is disconnected between calculating the peer id
|
||||||
|
# and issuing the disconnect, don't worry about it.
|
||||||
|
# This avoids a race condition if we're mass-disconnecting peers.
|
||||||
|
if e.error['code'] != -29: # RPC_CLIENT_NODE_NOT_CONNECTED
|
||||||
|
raise
|
||||||
|
|
||||||
|
# wait to disconnect
|
||||||
|
wait_until(lambda: not get_peer_ids(), timeout=5)
|
||||||
|
|
||||||
|
|
||||||
|
def connect_nodes(from_connection, node_num):
|
||||||
|
ip_port = "127.0.0.1:" + str(p2p_port(node_num))
|
||||||
|
from_connection.addnode(ip_port, "onetry")
|
||||||
|
# poll until version handshake complete to avoid race conditions
|
||||||
|
# with transaction relaying
|
||||||
|
# See comments in net_processing:
|
||||||
|
# * Must have a version message before anything else
|
||||||
|
# * Must have a verack message before anything else
|
||||||
|
wait_until(lambda: all(peer['version'] != 0 for peer in from_connection.getpeerinfo()))
|
||||||
|
wait_until(lambda: all(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in from_connection.getpeerinfo()))
|
||||||
|
|
||||||
|
|
||||||
|
# Transaction/Block functions
|
||||||
|
#############################
|
||||||
|
|
||||||
|
|
||||||
|
def find_output(node, txid, amount, *, blockhash=None):
|
||||||
|
"""
|
||||||
|
Return index to output of txid with value amount
|
||||||
|
Raises exception if there is none.
|
||||||
|
"""
|
||||||
|
txdata = node.getrawtransaction(txid, 1, blockhash)
|
||||||
|
for i in range(len(txdata["vout"])):
|
||||||
|
if txdata["vout"][i]["value"] == amount:
|
||||||
|
return i
|
||||||
|
raise RuntimeError("find_output txid %s : %s not found" % (txid, str(amount)))
|
||||||
|
|
||||||
|
|
||||||
|
def gather_inputs(from_node, amount_needed, confirmations_required=1):
|
||||||
|
"""
|
||||||
|
Return a random set of unspent txouts that are enough to pay amount_needed
|
||||||
|
"""
|
||||||
|
assert confirmations_required >= 0
|
||||||
|
utxo = from_node.listunspent(confirmations_required)
|
||||||
|
random.shuffle(utxo)
|
||||||
|
inputs = []
|
||||||
|
total_in = Decimal("0.00000000")
|
||||||
|
while total_in < amount_needed and len(utxo) > 0:
|
||||||
|
t = utxo.pop()
|
||||||
|
total_in += t["amount"]
|
||||||
|
inputs.append({"txid": t["txid"], "vout": t["vout"], "address": t["address"]})
|
||||||
|
if total_in < amount_needed:
|
||||||
|
raise RuntimeError("Insufficient funds: need %d, have %d" % (amount_needed, total_in))
|
||||||
|
return (total_in, inputs)
|
||||||
|
|
||||||
|
|
||||||
|
def make_change(from_node, amount_in, amount_out, fee):
|
||||||
|
"""
|
||||||
|
Create change output(s), return them
|
||||||
|
"""
|
||||||
|
outputs = {}
|
||||||
|
amount = amount_out + fee
|
||||||
|
change = amount_in - amount
|
||||||
|
if change > amount * 2:
|
||||||
|
# Create an extra change output to break up big inputs
|
||||||
|
change_address = from_node.getnewaddress()
|
||||||
|
# Split change in two, being careful of rounding:
|
||||||
|
outputs[change_address] = Decimal(change / 2).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
|
||||||
|
change = amount_in - amount - outputs[change_address]
|
||||||
|
if change > 0:
|
||||||
|
outputs[from_node.getnewaddress()] = change
|
||||||
|
return outputs
|
||||||
|
|
||||||
|
|
||||||
|
def random_transaction(nodes, amount, min_fee, fee_increment, fee_variants):
|
||||||
|
"""
|
||||||
|
Create a random transaction.
|
||||||
|
Returns (txid, hex-encoded-transaction-data, fee)
|
||||||
|
"""
|
||||||
|
from_node = random.choice(nodes)
|
||||||
|
to_node = random.choice(nodes)
|
||||||
|
fee = min_fee + fee_increment * random.randint(0, fee_variants)
|
||||||
|
|
||||||
|
(total_in, inputs) = gather_inputs(from_node, amount + fee)
|
||||||
|
outputs = make_change(from_node, total_in, amount, fee)
|
||||||
|
outputs[to_node.getnewaddress()] = float(amount)
|
||||||
|
|
||||||
|
rawtx = from_node.createrawtransaction(inputs, outputs)
|
||||||
|
signresult = from_node.signrawtransactionwithwallet(rawtx)
|
||||||
|
txid = from_node.sendrawtransaction(signresult["hex"], 0)
|
||||||
|
|
||||||
|
return (txid, signresult["hex"], fee)
|
||||||
|
|
||||||
|
|
||||||
|
# Helper to create at least "count" utxos
|
||||||
|
# Pass in a fee that is sufficient for relay and mining new transactions.
|
||||||
|
def create_confirmed_utxos(fee, node, count):
|
||||||
|
to_generate = int(0.5 * count) + 101
|
||||||
|
while to_generate > 0:
|
||||||
|
node.generate(min(25, to_generate))
|
||||||
|
to_generate -= 25
|
||||||
|
utxos = node.listunspent()
|
||||||
|
iterations = count - len(utxos)
|
||||||
|
addr1 = node.getnewaddress()
|
||||||
|
addr2 = node.getnewaddress()
|
||||||
|
if iterations <= 0:
|
||||||
|
return utxos
|
||||||
|
for i in range(iterations):
|
||||||
|
t = utxos.pop()
|
||||||
|
inputs = []
|
||||||
|
inputs.append({"txid": t["txid"], "vout": t["vout"]})
|
||||||
|
outputs = {}
|
||||||
|
send_value = t['amount'] - fee
|
||||||
|
outputs[addr1] = satoshi_round(send_value / 2)
|
||||||
|
outputs[addr2] = satoshi_round(send_value / 2)
|
||||||
|
raw_tx = node.createrawtransaction(inputs, outputs)
|
||||||
|
signed_tx = node.signrawtransactionwithwallet(raw_tx)["hex"]
|
||||||
|
node.sendrawtransaction(signed_tx)
|
||||||
|
|
||||||
|
while (node.getmempoolinfo()['size'] > 0):
|
||||||
|
node.generate(1)
|
||||||
|
|
||||||
|
utxos = node.listunspent()
|
||||||
|
assert len(utxos) >= count
|
||||||
|
return utxos
|
||||||
|
|
||||||
|
|
||||||
|
# Create large OP_RETURN txouts that can be appended to a transaction
|
||||||
|
# to make it large (helper for constructing large transactions).
|
||||||
|
def gen_return_txouts():
|
||||||
|
# Some pre-processing to create a bunch of OP_RETURN txouts to insert into transactions we create
|
||||||
|
# So we have big transactions (and therefore can't fit very many into each block)
|
||||||
|
# create one script_pubkey
|
||||||
|
script_pubkey = "6a4d0200" # OP_RETURN OP_PUSH2 512 bytes
|
||||||
|
for i in range(512):
|
||||||
|
script_pubkey = script_pubkey + "01"
|
||||||
|
# concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change
|
||||||
|
txouts = []
|
||||||
|
from .messages import CTxOut
|
||||||
|
txout = CTxOut()
|
||||||
|
txout.nValue = 0
|
||||||
|
txout.scriptPubKey = hex_str_to_bytes(script_pubkey)
|
||||||
|
for k in range(128):
|
||||||
|
txouts.append(txout)
|
||||||
|
return txouts
|
||||||
|
|
||||||
|
|
||||||
|
# Create a spend of each passed-in utxo, splicing in "txouts" to each raw
|
||||||
|
# transaction to make it large. See gen_return_txouts() above.
|
||||||
|
def create_lots_of_big_transactions(node, txouts, utxos, num, fee):
|
||||||
|
addr = node.getnewaddress()
|
||||||
|
txids = []
|
||||||
|
from .messages import CTransaction
|
||||||
|
for _ in range(num):
|
||||||
|
t = utxos.pop()
|
||||||
|
inputs = [{"txid": t["txid"], "vout": t["vout"]}]
|
||||||
|
outputs = {}
|
||||||
|
change = t['amount'] - fee
|
||||||
|
outputs[addr] = satoshi_round(change)
|
||||||
|
rawtx = node.createrawtransaction(inputs, outputs)
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.deserialize(BytesIO(hex_str_to_bytes(rawtx)))
|
||||||
|
for txout in txouts:
|
||||||
|
tx.vout.append(txout)
|
||||||
|
newtx = tx.serialize().hex()
|
||||||
|
signresult = node.signrawtransactionwithwallet(newtx, None, "NONE")
|
||||||
|
txid = node.sendrawtransaction(signresult["hex"], 0)
|
||||||
|
txids.append(txid)
|
||||||
|
return txids
|
||||||
|
|
||||||
|
|
||||||
|
def mine_large_block(node, utxos=None):
|
||||||
|
# generate a 66k transaction,
|
||||||
|
# and 14 of them is close to the 1MB block limit
|
||||||
|
num = 14
|
||||||
|
txouts = gen_return_txouts()
|
||||||
|
utxos = utxos if utxos is not None else []
|
||||||
|
if len(utxos) < num:
|
||||||
|
utxos.clear()
|
||||||
|
utxos.extend(node.listunspent())
|
||||||
|
fee = 100 * node.getnetworkinfo()["relayfee"]
|
||||||
|
create_lots_of_big_transactions(node, txouts, utxos, num, fee=fee)
|
||||||
|
node.generate(1)
|
||||||
|
|
||||||
|
|
||||||
|
def find_vout_for_address(node, txid, addr):
|
||||||
|
"""
|
||||||
|
Locate the vout index of the given transaction sending to the
|
||||||
|
given address. Raises runtime error exception if not found.
|
||||||
|
"""
|
||||||
|
tx = node.getrawtransaction(txid, True)
|
||||||
|
for i in range(len(tx["vout"])):
|
||||||
|
if any([addr == a for a in tx["vout"][i]["scriptPubKey"]["addresses"]]):
|
||||||
|
return i
|
||||||
|
raise RuntimeError("Vout not found for address: txid=%s, addr=%s" % (txid, addr))
|
131
basicswap/contrib/test_framework/wallet_util.py
Executable file
131
basicswap/contrib/test_framework/wallet_util.py
Executable file
@ -0,0 +1,131 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2018-2020 The Bitcoin Core developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
"""Useful util functions for testing the wallet"""
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
from .address import (
|
||||||
|
byte_to_base58,
|
||||||
|
key_to_p2pkh,
|
||||||
|
key_to_p2sh_p2wpkh,
|
||||||
|
key_to_p2wpkh,
|
||||||
|
script_to_p2sh,
|
||||||
|
script_to_p2sh_p2wsh,
|
||||||
|
script_to_p2wsh,
|
||||||
|
)
|
||||||
|
from .key import ECKey
|
||||||
|
from .script import (
|
||||||
|
CScript,
|
||||||
|
OP_0,
|
||||||
|
OP_2,
|
||||||
|
OP_3,
|
||||||
|
OP_CHECKMULTISIG,
|
||||||
|
OP_CHECKSIG,
|
||||||
|
OP_DUP,
|
||||||
|
OP_EQUAL,
|
||||||
|
OP_EQUALVERIFY,
|
||||||
|
OP_HASH160,
|
||||||
|
hash160,
|
||||||
|
sha256,
|
||||||
|
)
|
||||||
|
from .util import hex_str_to_bytes
|
||||||
|
|
||||||
|
Key = namedtuple('Key', ['privkey',
|
||||||
|
'pubkey',
|
||||||
|
'p2pkh_script',
|
||||||
|
'p2pkh_addr',
|
||||||
|
'p2wpkh_script',
|
||||||
|
'p2wpkh_addr',
|
||||||
|
'p2sh_p2wpkh_script',
|
||||||
|
'p2sh_p2wpkh_redeem_script',
|
||||||
|
'p2sh_p2wpkh_addr'])
|
||||||
|
|
||||||
|
Multisig = namedtuple('Multisig', ['privkeys',
|
||||||
|
'pubkeys',
|
||||||
|
'p2sh_script',
|
||||||
|
'p2sh_addr',
|
||||||
|
'redeem_script',
|
||||||
|
'p2wsh_script',
|
||||||
|
'p2wsh_addr',
|
||||||
|
'p2sh_p2wsh_script',
|
||||||
|
'p2sh_p2wsh_addr'])
|
||||||
|
|
||||||
|
def get_key(node):
|
||||||
|
"""Generate a fresh key on node
|
||||||
|
|
||||||
|
Returns a named tuple of privkey, pubkey and all address and scripts."""
|
||||||
|
addr = node.getnewaddress()
|
||||||
|
pubkey = node.getaddressinfo(addr)['pubkey']
|
||||||
|
pkh = hash160(hex_str_to_bytes(pubkey))
|
||||||
|
return Key(privkey=node.dumpprivkey(addr),
|
||||||
|
pubkey=pubkey,
|
||||||
|
p2pkh_script=CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG]).hex(),
|
||||||
|
p2pkh_addr=key_to_p2pkh(pubkey),
|
||||||
|
p2wpkh_script=CScript([OP_0, pkh]).hex(),
|
||||||
|
p2wpkh_addr=key_to_p2wpkh(pubkey),
|
||||||
|
p2sh_p2wpkh_script=CScript([OP_HASH160, hash160(CScript([OP_0, pkh])), OP_EQUAL]).hex(),
|
||||||
|
p2sh_p2wpkh_redeem_script=CScript([OP_0, pkh]).hex(),
|
||||||
|
p2sh_p2wpkh_addr=key_to_p2sh_p2wpkh(pubkey))
|
||||||
|
|
||||||
|
def get_generate_key():
|
||||||
|
"""Generate a fresh key
|
||||||
|
|
||||||
|
Returns a named tuple of privkey, pubkey and all address and scripts."""
|
||||||
|
eckey = ECKey()
|
||||||
|
eckey.generate()
|
||||||
|
privkey = bytes_to_wif(eckey.get_bytes())
|
||||||
|
pubkey = eckey.get_pubkey().get_bytes().hex()
|
||||||
|
pkh = hash160(hex_str_to_bytes(pubkey))
|
||||||
|
return Key(privkey=privkey,
|
||||||
|
pubkey=pubkey,
|
||||||
|
p2pkh_script=CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG]).hex(),
|
||||||
|
p2pkh_addr=key_to_p2pkh(pubkey),
|
||||||
|
p2wpkh_script=CScript([OP_0, pkh]).hex(),
|
||||||
|
p2wpkh_addr=key_to_p2wpkh(pubkey),
|
||||||
|
p2sh_p2wpkh_script=CScript([OP_HASH160, hash160(CScript([OP_0, pkh])), OP_EQUAL]).hex(),
|
||||||
|
p2sh_p2wpkh_redeem_script=CScript([OP_0, pkh]).hex(),
|
||||||
|
p2sh_p2wpkh_addr=key_to_p2sh_p2wpkh(pubkey))
|
||||||
|
|
||||||
|
def get_multisig(node):
|
||||||
|
"""Generate a fresh 2-of-3 multisig on node
|
||||||
|
|
||||||
|
Returns a named tuple of privkeys, pubkeys and all address and scripts."""
|
||||||
|
addrs = []
|
||||||
|
pubkeys = []
|
||||||
|
for _ in range(3):
|
||||||
|
addr = node.getaddressinfo(node.getnewaddress())
|
||||||
|
addrs.append(addr['address'])
|
||||||
|
pubkeys.append(addr['pubkey'])
|
||||||
|
script_code = CScript([OP_2] + [hex_str_to_bytes(pubkey) for pubkey in pubkeys] + [OP_3, OP_CHECKMULTISIG])
|
||||||
|
witness_script = CScript([OP_0, sha256(script_code)])
|
||||||
|
return Multisig(privkeys=[node.dumpprivkey(addr) for addr in addrs],
|
||||||
|
pubkeys=pubkeys,
|
||||||
|
p2sh_script=CScript([OP_HASH160, hash160(script_code), OP_EQUAL]).hex(),
|
||||||
|
p2sh_addr=script_to_p2sh(script_code),
|
||||||
|
redeem_script=script_code.hex(),
|
||||||
|
p2wsh_script=witness_script.hex(),
|
||||||
|
p2wsh_addr=script_to_p2wsh(script_code),
|
||||||
|
p2sh_p2wsh_script=CScript([OP_HASH160, witness_script, OP_EQUAL]).hex(),
|
||||||
|
p2sh_p2wsh_addr=script_to_p2sh_p2wsh(script_code))
|
||||||
|
|
||||||
|
def test_address(node, address, **kwargs):
|
||||||
|
"""Get address info for `address` and test whether the returned values are as expected."""
|
||||||
|
addr_info = node.getaddressinfo(address)
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
if value is None:
|
||||||
|
if key in addr_info.keys():
|
||||||
|
raise AssertionError("key {} unexpectedly returned in getaddressinfo.".format(key))
|
||||||
|
elif addr_info[key] != value:
|
||||||
|
raise AssertionError("key {} value {} did not match expected value {}".format(key, addr_info[key], value))
|
||||||
|
|
||||||
|
def bytes_to_wif(b, compressed=True, prefix=239):
|
||||||
|
if compressed:
|
||||||
|
b += b'\x01'
|
||||||
|
return byte_to_base58(b, prefix)
|
||||||
|
|
||||||
|
def generate_wif_key():
|
||||||
|
# Makes a WIF privkey for imports
|
||||||
|
k = ECKey()
|
||||||
|
k.generate()
|
||||||
|
return bytes_to_wif(k.get_bytes(), k.is_compressed)
|
@ -97,7 +97,7 @@ class Bid(Base):
|
|||||||
participate_txn_refund = sa.Column(sa.LargeBinary)
|
participate_txn_refund = sa.Column(sa.LargeBinary)
|
||||||
|
|
||||||
state = sa.Column(sa.Integer)
|
state = sa.Column(sa.Integer)
|
||||||
state_time = sa.Column(sa.BigInteger) # timestamp of last state change
|
state_time = sa.Column(sa.BigInteger) # Timestamp of last state change
|
||||||
states = sa.Column(sa.LargeBinary) # Packed states and times
|
states = sa.Column(sa.LargeBinary) # Packed states and times
|
||||||
|
|
||||||
state_note = sa.Column(sa.String)
|
state_note = sa.Column(sa.String)
|
||||||
|
222
basicswap/ecc_util.py
Normal file
222
basicswap/ecc_util.py
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import codecs
|
||||||
|
import hashlib
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
from .contrib.ellipticcurve import CurveFp, Point, INFINITY, jacobi_symbol
|
||||||
|
|
||||||
|
|
||||||
|
class ECCParameters():
|
||||||
|
def __init__(self, p, a, b, Gx, Gy, o):
|
||||||
|
self.p = p
|
||||||
|
self.a = a
|
||||||
|
self.b = b
|
||||||
|
self.Gx = Gx
|
||||||
|
self.Gy = Gy
|
||||||
|
self.o = o
|
||||||
|
|
||||||
|
|
||||||
|
ep = ECCParameters( \
|
||||||
|
p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f, \
|
||||||
|
a = 0x0, \
|
||||||
|
b = 0x7, \
|
||||||
|
Gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, \
|
||||||
|
Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8, \
|
||||||
|
o = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141) # noqa: E221,E251,E502
|
||||||
|
|
||||||
|
curve_secp256k1 = CurveFp(ep.p, ep.a, ep.b)
|
||||||
|
G = Point(curve_secp256k1, ep.Gx, ep.Gy, ep.o)
|
||||||
|
SECP256K1_ORDER_HALF = ep.o // 2
|
||||||
|
|
||||||
|
|
||||||
|
def ToDER(P):
|
||||||
|
return bytes((4, )) + int(P.x()).to_bytes(32, byteorder='big') + int(P.y()).to_bytes(32, byteorder='big')
|
||||||
|
|
||||||
|
|
||||||
|
def bytes32ToInt(b):
|
||||||
|
return int.from_bytes(b, byteorder='big')
|
||||||
|
|
||||||
|
|
||||||
|
def intToBytes32(i):
|
||||||
|
return i.to_bytes(32, byteorder='big')
|
||||||
|
|
||||||
|
|
||||||
|
def intToBytes32_le(i):
|
||||||
|
return i.to_bytes(32, byteorder='little')
|
||||||
|
|
||||||
|
|
||||||
|
def bytesToHexStr(b):
|
||||||
|
return codecs.encode(b, 'hex').decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
def hexStrToBytes(h):
|
||||||
|
if h.startswith('0x'):
|
||||||
|
h = h[2:]
|
||||||
|
return bytes.fromhex(h)
|
||||||
|
|
||||||
|
|
||||||
|
def getSecretBytes():
|
||||||
|
i = 1 + secrets.randbelow(ep.o - 1)
|
||||||
|
return intToBytes32(i)
|
||||||
|
|
||||||
|
|
||||||
|
def getSecretInt():
|
||||||
|
return 1 + secrets.randbelow(ep.o - 1)
|
||||||
|
|
||||||
|
|
||||||
|
def getInsecureBytes():
|
||||||
|
while True:
|
||||||
|
s = os.urandom(32)
|
||||||
|
|
||||||
|
s_test = int.from_bytes(s, byteorder='big')
|
||||||
|
if s_test > 1 and s_test < ep.o:
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def getInsecureInt():
|
||||||
|
while True:
|
||||||
|
s = os.urandom(32)
|
||||||
|
|
||||||
|
s_test = int.from_bytes(s, byteorder='big')
|
||||||
|
if s_test > 1 and s_test < ep.o:
|
||||||
|
return s_test
|
||||||
|
|
||||||
|
|
||||||
|
def powMod(x, y, z):
|
||||||
|
# Calculate (x ** y) % z efficiently.
|
||||||
|
number = 1
|
||||||
|
while y:
|
||||||
|
if y & 1:
|
||||||
|
number = number * x % z
|
||||||
|
y >>= 1 # y //= 2
|
||||||
|
|
||||||
|
x = x * x % z
|
||||||
|
return number
|
||||||
|
|
||||||
|
|
||||||
|
def ExpandPoint(xb, sign):
|
||||||
|
x = int.from_bytes(xb, byteorder='big')
|
||||||
|
a = (powMod(x, 3, ep.p) + 7) % ep.p
|
||||||
|
y = powMod(a, (ep.p + 1) // 4, ep.p)
|
||||||
|
|
||||||
|
if sign:
|
||||||
|
y = ep.p - y
|
||||||
|
return Point(curve_secp256k1, x, y, ep.o)
|
||||||
|
|
||||||
|
|
||||||
|
def CPKToPoint(cpk):
|
||||||
|
y_parity = cpk[0] - 2
|
||||||
|
|
||||||
|
x = int.from_bytes(cpk[1:], byteorder='big')
|
||||||
|
a = (powMod(x, 3, ep.p) + 7) % ep.p
|
||||||
|
y = powMod(a, (ep.p + 1) // 4, ep.p)
|
||||||
|
|
||||||
|
if y % 2 != y_parity:
|
||||||
|
y = ep.p - y
|
||||||
|
|
||||||
|
return Point(curve_secp256k1, x, y, ep.o)
|
||||||
|
|
||||||
|
|
||||||
|
def pointToCPK2(point, ind=0x09):
|
||||||
|
# The function is_square(x), where x is an integer, returns whether or not x is a quadratic residue modulo p. Since p is prime, it is equivalent to the Legendre symbol (x / p) = x(p-1)/2 mod p being equal to 1[8].
|
||||||
|
ind = bytes((ind ^ (1 if jacobi_symbol(point.y(), ep.p) == 1 else 0),))
|
||||||
|
return ind + point.x().to_bytes(32, byteorder='big')
|
||||||
|
|
||||||
|
|
||||||
|
def pointToCPK(point):
|
||||||
|
|
||||||
|
y = point.y().to_bytes(32, byteorder='big')
|
||||||
|
ind = bytes((0x03,)) if y[31] % 2 else bytes((0x02,))
|
||||||
|
|
||||||
|
cpk = ind + point.x().to_bytes(32, byteorder='big')
|
||||||
|
return cpk
|
||||||
|
|
||||||
|
|
||||||
|
def secretToCPK(secret):
|
||||||
|
secretInt = secret if isinstance(secret, int) \
|
||||||
|
else int.from_bytes(secret, byteorder='big')
|
||||||
|
|
||||||
|
R = G * secretInt
|
||||||
|
|
||||||
|
Y = R.y().to_bytes(32, byteorder='big')
|
||||||
|
ind = bytes((0x03,)) if Y[31] % 2 else bytes((0x02,))
|
||||||
|
|
||||||
|
pubkey = ind + R.x().to_bytes(32, byteorder='big')
|
||||||
|
|
||||||
|
return pubkey
|
||||||
|
|
||||||
|
|
||||||
|
def getKeypair():
|
||||||
|
secretBytes = getSecretBytes()
|
||||||
|
return secretBytes, secretToCPK(secretBytes)
|
||||||
|
|
||||||
|
|
||||||
|
def hashToCurve(pubkey):
|
||||||
|
|
||||||
|
xBytes = hashlib.sha256(pubkey).digest()
|
||||||
|
x = int.from_bytes(xBytes, byteorder='big')
|
||||||
|
|
||||||
|
for k in range(0, 100):
|
||||||
|
# get matching y element for point
|
||||||
|
y_parity = 0 # always pick 0,
|
||||||
|
a = (powMod(x, 3, ep.p) + 7) % ep.p
|
||||||
|
y = powMod(a, (ep.p + 1) // 4, ep.p)
|
||||||
|
|
||||||
|
# print("before parity %x" % (y))
|
||||||
|
if y % 2 != y_parity:
|
||||||
|
y = ep.p - y
|
||||||
|
|
||||||
|
# If x is always mod P, can R ever not be on the curve?
|
||||||
|
try:
|
||||||
|
R = Point(curve_secp256k1, x, y, ep.o)
|
||||||
|
except Exception:
|
||||||
|
x = (x + 1) % ep.p # % P?
|
||||||
|
continue
|
||||||
|
|
||||||
|
if R == INFINITY or R * ep.o != INFINITY: # is R * O != INFINITY check necessary? Validation of Elliptic Curve Public Keys says no if cofactor = 1
|
||||||
|
x = (x + 1) % ep.p # % P?
|
||||||
|
continue
|
||||||
|
return R
|
||||||
|
|
||||||
|
raise ValueError('hashToCurve failed for 100 tries')
|
||||||
|
|
||||||
|
|
||||||
|
def hash256(inb):
|
||||||
|
return hashlib.sha256(inb).digest()
|
||||||
|
|
||||||
|
|
||||||
|
i2b = intToBytes32
|
||||||
|
b2i = bytes32ToInt
|
||||||
|
b2h = bytesToHexStr
|
||||||
|
h2b = hexStrToBytes
|
||||||
|
|
||||||
|
|
||||||
|
def i2h(x):
|
||||||
|
return b2h(i2b(x))
|
||||||
|
|
||||||
|
|
||||||
|
def testEccUtils():
|
||||||
|
print('testEccUtils()')
|
||||||
|
|
||||||
|
G_enc = ToDER(G)
|
||||||
|
assert(G_enc.hex() == '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8')
|
||||||
|
|
||||||
|
G_enc = pointToCPK(G)
|
||||||
|
assert(G_enc.hex() == '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')
|
||||||
|
G_dec = CPKToPoint(G_enc)
|
||||||
|
assert(G_dec == G)
|
||||||
|
|
||||||
|
G_enc = pointToCPK2(G)
|
||||||
|
assert(G_enc.hex() == '0879be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')
|
||||||
|
|
||||||
|
H = hashToCurve(ToDER(G))
|
||||||
|
assert(pointToCPK(H).hex() == '0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0')
|
||||||
|
|
||||||
|
print('Passed.')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
testEccUtils()
|
@ -19,7 +19,7 @@ from . import __version__
|
|||||||
from .util import (
|
from .util import (
|
||||||
COIN,
|
COIN,
|
||||||
format8,
|
format8,
|
||||||
makeInt,
|
make_int,
|
||||||
dumpj,
|
dumpj,
|
||||||
)
|
)
|
||||||
from .chainparams import (
|
from .chainparams import (
|
||||||
@ -129,7 +129,7 @@ def validateAmountString(amount):
|
|||||||
|
|
||||||
def inputAmount(amount_str):
|
def inputAmount(amount_str):
|
||||||
validateAmountString(amount_str)
|
validateAmountString(amount_str)
|
||||||
return makeInt(amount_str)
|
return make_int(amount_str)
|
||||||
|
|
||||||
|
|
||||||
def setCoinFilter(form_data, field_name):
|
def setCoinFilter(form_data, field_name):
|
||||||
|
805
basicswap/interface_btc.py
Normal file
805
basicswap/interface_btc.py
Normal file
@ -0,0 +1,805 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2020 tecnovert
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
import time
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
from .util import (
|
||||||
|
decodeScriptNum,
|
||||||
|
getCompactSizeLen,
|
||||||
|
dumpj,
|
||||||
|
format_amount,
|
||||||
|
make_int
|
||||||
|
)
|
||||||
|
|
||||||
|
from .ecc_util import (
|
||||||
|
G, ep,
|
||||||
|
pointToCPK, CPKToPoint,
|
||||||
|
getSecretInt,
|
||||||
|
b2h, i2b, b2i, i2h)
|
||||||
|
|
||||||
|
from .contrib.test_framework.messages import (
|
||||||
|
COIN,
|
||||||
|
COutPoint,
|
||||||
|
CTransaction,
|
||||||
|
CTxIn,
|
||||||
|
CTxInWitness,
|
||||||
|
CTxOut,
|
||||||
|
FromHex,
|
||||||
|
ToHex)
|
||||||
|
|
||||||
|
from .contrib.test_framework.script import (
|
||||||
|
CScript,
|
||||||
|
CScriptOp,
|
||||||
|
CScriptNum,
|
||||||
|
OP_IF, OP_ELSE, OP_ENDIF,
|
||||||
|
OP_0,
|
||||||
|
OP_2,
|
||||||
|
OP_16,
|
||||||
|
OP_EQUALVERIFY,
|
||||||
|
OP_CHECKSIG,
|
||||||
|
OP_SIZE,
|
||||||
|
OP_SHA256,
|
||||||
|
OP_CHECKMULTISIG,
|
||||||
|
OP_CHECKSEQUENCEVERIFY,
|
||||||
|
OP_DROP,
|
||||||
|
SIGHASH_ALL,
|
||||||
|
SegwitV0SignatureHash,
|
||||||
|
hash160)
|
||||||
|
|
||||||
|
from .contrib.test_framework.key import ECKey, ECPubKey
|
||||||
|
|
||||||
|
from .chainparams import CoinInterface
|
||||||
|
from .rpc import make_rpc_func
|
||||||
|
from .util import assert_cond
|
||||||
|
|
||||||
|
|
||||||
|
def findOutput(tx, script_pk):
|
||||||
|
for i in range(len(tx.vout)):
|
||||||
|
if tx.vout[i].scriptPubKey == script_pk:
|
||||||
|
return i
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class BTCInterface(CoinInterface):
|
||||||
|
@staticmethod
|
||||||
|
def exp():
|
||||||
|
return 8
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def nbk():
|
||||||
|
return 32
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def nbK(): # No. of bytes requires to encode a public key
|
||||||
|
return 33
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def witnessScaleFactor():
|
||||||
|
return 4
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def txVersion():
|
||||||
|
return 2
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getTxOutputValue(tx):
|
||||||
|
rv = 0
|
||||||
|
for output in tx.vout:
|
||||||
|
rv += output.nValue
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def compareFeeRates(self, a, b):
|
||||||
|
return abs(a - b) < 20
|
||||||
|
|
||||||
|
def __init__(self, coin_settings):
|
||||||
|
self.rpc_callback = make_rpc_func(coin_settings['rpcport'], coin_settings['rpcauth'])
|
||||||
|
self.txoType = CTxOut
|
||||||
|
|
||||||
|
def getNewSecretKey(self):
|
||||||
|
return getSecretInt()
|
||||||
|
|
||||||
|
def pubkey(self, key):
|
||||||
|
return G * key
|
||||||
|
|
||||||
|
def encodePubkey(self, pk):
|
||||||
|
return pointToCPK(pk)
|
||||||
|
|
||||||
|
def decodePubkey(self, pke):
|
||||||
|
return CPKToPoint(pke)
|
||||||
|
|
||||||
|
def decodeKey(self, k):
|
||||||
|
i = b2i(k)
|
||||||
|
assert(i < ep.o)
|
||||||
|
return i
|
||||||
|
|
||||||
|
def sumKeys(self, ka, kb):
|
||||||
|
return (ka + kb) % ep.o
|
||||||
|
|
||||||
|
def sumPubkeys(self, Ka, Kb):
|
||||||
|
return Ka + Kb
|
||||||
|
|
||||||
|
def extractScriptLockScriptValues(self, script_bytes):
|
||||||
|
script_len = len(script_bytes)
|
||||||
|
assert_cond(script_len > 112, 'Bad script length')
|
||||||
|
assert_cond(script_bytes[0] == OP_IF)
|
||||||
|
assert_cond(script_bytes[1] == OP_SIZE)
|
||||||
|
assert_cond(script_bytes[2:4] == bytes((1, 32))) # 0120, CScriptNum length, then data
|
||||||
|
assert_cond(script_bytes[4] == OP_EQUALVERIFY)
|
||||||
|
assert_cond(script_bytes[5] == OP_SHA256)
|
||||||
|
assert_cond(script_bytes[6] == 32)
|
||||||
|
secret_hash = script_bytes[7: 7 + 32]
|
||||||
|
assert_cond(script_bytes[39] == OP_EQUALVERIFY)
|
||||||
|
assert_cond(script_bytes[40] == OP_2)
|
||||||
|
assert_cond(script_bytes[41] == 33)
|
||||||
|
pk1 = script_bytes[42: 42 + 33]
|
||||||
|
assert_cond(script_bytes[75] == 33)
|
||||||
|
pk2 = script_bytes[76: 76 + 33]
|
||||||
|
assert_cond(script_bytes[109] == OP_2)
|
||||||
|
assert_cond(script_bytes[110] == OP_CHECKMULTISIG)
|
||||||
|
assert_cond(script_bytes[111] == OP_ELSE)
|
||||||
|
o = 112
|
||||||
|
|
||||||
|
# Decode script num
|
||||||
|
csv_val, nb = decodeScriptNum(script_bytes, o)
|
||||||
|
o += nb
|
||||||
|
|
||||||
|
assert_cond(script_len == o + 8 + 66, 'Bad script length') # Fails if script too long
|
||||||
|
assert_cond(script_bytes[o] == OP_CHECKSEQUENCEVERIFY)
|
||||||
|
o += 1
|
||||||
|
assert_cond(script_bytes[o] == OP_DROP)
|
||||||
|
o += 1
|
||||||
|
assert_cond(script_bytes[o] == OP_2)
|
||||||
|
o += 1
|
||||||
|
assert_cond(script_bytes[o] == 33)
|
||||||
|
o += 1
|
||||||
|
pk3 = script_bytes[o: o + 33]
|
||||||
|
o += 33
|
||||||
|
assert_cond(script_bytes[o] == 33)
|
||||||
|
o += 1
|
||||||
|
pk4 = script_bytes[o: o + 33]
|
||||||
|
o += 33
|
||||||
|
assert_cond(script_bytes[o] == OP_2)
|
||||||
|
o += 1
|
||||||
|
assert_cond(script_bytes[o] == OP_CHECKMULTISIG)
|
||||||
|
o += 1
|
||||||
|
assert_cond(script_bytes[o] == OP_ENDIF)
|
||||||
|
|
||||||
|
return secret_hash, pk1, pk2, csv_val, pk3, pk4
|
||||||
|
|
||||||
|
def genScriptLockTxScript(self, sh, Kal, Kaf, lock_blocks, Karl, Karf):
|
||||||
|
return CScript([
|
||||||
|
CScriptOp(OP_IF),
|
||||||
|
CScriptOp(OP_SIZE), 32, CScriptOp(OP_EQUALVERIFY),
|
||||||
|
CScriptOp(OP_SHA256), sh, CScriptOp(OP_EQUALVERIFY),
|
||||||
|
2, self.encodePubkey(Kal), self.encodePubkey(Kaf), 2, CScriptOp(OP_CHECKMULTISIG),
|
||||||
|
CScriptOp(OP_ELSE),
|
||||||
|
lock_blocks, CScriptOp(OP_CHECKSEQUENCEVERIFY), CScriptOp(OP_DROP),
|
||||||
|
2, self.encodePubkey(Karl), self.encodePubkey(Karf), 2, CScriptOp(OP_CHECKMULTISIG),
|
||||||
|
CScriptOp(OP_ENDIF)])
|
||||||
|
|
||||||
|
def createScriptLockTx(self, value, sh, Kal, Kaf, lock_blocks, Karl, Karf):
|
||||||
|
|
||||||
|
script = self.genScriptLockTxScript(sh, Kal, Kaf, lock_blocks, Karl, Karf)
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.nVersion = self.txVersion()
|
||||||
|
tx.vout.append(self.txoType(value, CScript([OP_0, hashlib.sha256(script).digest()])))
|
||||||
|
|
||||||
|
return tx, script
|
||||||
|
|
||||||
|
def extractScriptLockRefundScriptValues(self, script_bytes):
|
||||||
|
script_len = len(script_bytes)
|
||||||
|
assert_cond(script_len > 73, 'Bad script length')
|
||||||
|
assert_cond(script_bytes[0] == OP_IF)
|
||||||
|
assert_cond(script_bytes[1] == OP_2)
|
||||||
|
assert_cond(script_bytes[2] == 33)
|
||||||
|
pk1 = script_bytes[3: 3 + 33]
|
||||||
|
assert_cond(script_bytes[36] == 33)
|
||||||
|
pk2 = script_bytes[37: 37 + 33]
|
||||||
|
assert_cond(script_bytes[70] == OP_2)
|
||||||
|
assert_cond(script_bytes[71] == OP_CHECKMULTISIG)
|
||||||
|
assert_cond(script_bytes[72] == OP_ELSE)
|
||||||
|
o = 73
|
||||||
|
csv_val, nb = decodeScriptNum(script_bytes, o)
|
||||||
|
o += nb
|
||||||
|
|
||||||
|
assert_cond(script_len == o + 5 + 33, 'Bad script length') # Fails if script too long
|
||||||
|
assert_cond(script_bytes[o] == OP_CHECKSEQUENCEVERIFY)
|
||||||
|
o += 1
|
||||||
|
assert_cond(script_bytes[o] == OP_DROP)
|
||||||
|
o += 1
|
||||||
|
assert_cond(script_bytes[o] == 33)
|
||||||
|
o += 1
|
||||||
|
pk3 = script_bytes[o: o + 33]
|
||||||
|
o += 33
|
||||||
|
assert_cond(script_bytes[o] == OP_CHECKSIG)
|
||||||
|
o += 1
|
||||||
|
assert_cond(script_bytes[o] == OP_ENDIF)
|
||||||
|
|
||||||
|
return pk1, pk2, csv_val, pk3
|
||||||
|
|
||||||
|
def genScriptLockRefundTxScript(self, Karl, Karf, csv_val, Kaf):
|
||||||
|
return CScript([
|
||||||
|
CScriptOp(OP_IF),
|
||||||
|
2, self.encodePubkey(Karl), self.encodePubkey(Karf), 2, CScriptOp(OP_CHECKMULTISIG),
|
||||||
|
CScriptOp(OP_ELSE),
|
||||||
|
csv_val, CScriptOp(OP_CHECKSEQUENCEVERIFY), CScriptOp(OP_DROP),
|
||||||
|
self.encodePubkey(Kaf), CScriptOp(OP_CHECKSIG),
|
||||||
|
CScriptOp(OP_ENDIF)])
|
||||||
|
|
||||||
|
def createScriptLockRefundTx(self, tx_lock, script_lock, Karl, Karf, csv_val, Kaf, tx_fee_rate):
|
||||||
|
|
||||||
|
output_script = CScript([OP_0, hashlib.sha256(script_lock).digest()])
|
||||||
|
locked_n = findOutput(tx_lock, output_script)
|
||||||
|
assert_cond(locked_n is not None, 'Output not found in tx')
|
||||||
|
locked_coin = tx_lock.vout[locked_n].nValue
|
||||||
|
|
||||||
|
tx_lock.rehash()
|
||||||
|
tx_lock_hash_int = tx_lock.sha256
|
||||||
|
|
||||||
|
sh, A, B, lock1_value, C, D = self.extractScriptLockScriptValues(script_lock)
|
||||||
|
|
||||||
|
refund_script = self.genScriptLockRefundTxScript(Karl, Karf, csv_val, Kaf)
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.nVersion = self.txVersion()
|
||||||
|
tx.vin.append(CTxIn(COutPoint(tx_lock_hash_int, locked_n), nSequence=lock1_value))
|
||||||
|
tx.vout.append(self.txoType(locked_coin, CScript([OP_0, hashlib.sha256(refund_script).digest()])))
|
||||||
|
|
||||||
|
witness_bytes = len(script_lock)
|
||||||
|
witness_bytes += 73 * 2 # 2 signatures (72 + 1 byts size)
|
||||||
|
witness_bytes += 2 # 2 empty witness stack values
|
||||||
|
witness_bytes += getCompactSizeLen(witness_bytes)
|
||||||
|
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||||
|
pay_fee = int(tx_fee_rate * vsize / 1000)
|
||||||
|
tx.vout[0].nValue = locked_coin - pay_fee
|
||||||
|
|
||||||
|
tx.rehash()
|
||||||
|
logging.info('createScriptLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
|
||||||
|
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
|
||||||
|
|
||||||
|
return tx, refund_script, tx.vout[0].nValue
|
||||||
|
|
||||||
|
def createScriptLockRefundSpendTx(self, tx_lock_refund, script_lock_refund, Kal, tx_fee_rate):
|
||||||
|
# Returns the coinA locked coin to the leader
|
||||||
|
# The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey
|
||||||
|
# When the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower
|
||||||
|
|
||||||
|
output_script = CScript([OP_0, hashlib.sha256(script_lock_refund).digest()])
|
||||||
|
locked_n = findOutput(tx_lock_refund, output_script)
|
||||||
|
assert_cond(locked_n is not None, 'Output not found in tx')
|
||||||
|
locked_coin = tx_lock_refund.vout[locked_n].nValue
|
||||||
|
|
||||||
|
tx_lock_refund.rehash()
|
||||||
|
tx_lock_refund_hash_int = tx_lock_refund.sha256
|
||||||
|
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.nVersion = self.txVersion()
|
||||||
|
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), nSequence=0))
|
||||||
|
|
||||||
|
pubkeyhash = hash160(self.encodePubkey(Kal))
|
||||||
|
tx.vout.append(self.txoType(locked_coin, CScript([OP_0, pubkeyhash])))
|
||||||
|
|
||||||
|
witness_bytes = len(script_lock_refund)
|
||||||
|
witness_bytes += 73 * 2 # 2 signatures (72 + 1 byte size)
|
||||||
|
witness_bytes += 4 # 1 empty, 1 true witness stack values
|
||||||
|
witness_bytes += getCompactSizeLen(witness_bytes)
|
||||||
|
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||||
|
pay_fee = int(tx_fee_rate * vsize / 1000)
|
||||||
|
tx.vout[0].nValue = locked_coin - pay_fee
|
||||||
|
|
||||||
|
tx.rehash()
|
||||||
|
logging.info('createScriptLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
|
||||||
|
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
|
||||||
|
|
||||||
|
return tx
|
||||||
|
|
||||||
|
def createScriptLockRefundSpendToFTx(self, tx_lock_refund, script_lock_refund, pkh_dest, tx_fee_rate):
|
||||||
|
# Sends the coinA locked coin to the follower
|
||||||
|
output_script = CScript([OP_0, hashlib.sha256(script_lock_refund).digest()])
|
||||||
|
locked_n = findOutput(tx_lock_refund, output_script)
|
||||||
|
assert_cond(locked_n is not None, 'Output not found in tx')
|
||||||
|
locked_coin = tx_lock_refund.vout[locked_n].nValue
|
||||||
|
|
||||||
|
A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund)
|
||||||
|
|
||||||
|
tx_lock_refund.rehash()
|
||||||
|
tx_lock_refund_hash_int = tx_lock_refund.sha256
|
||||||
|
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.nVersion = self.txVersion()
|
||||||
|
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), nSequence=lock2_value))
|
||||||
|
|
||||||
|
tx.vout.append(self.txoType(locked_coin, CScript([OP_0, pkh_dest])))
|
||||||
|
|
||||||
|
witness_bytes = len(script_lock_refund)
|
||||||
|
witness_bytes += 73 # signature (72 + 1 byte size)
|
||||||
|
witness_bytes += 1 # 1 empty stack value
|
||||||
|
witness_bytes += getCompactSizeLen(witness_bytes)
|
||||||
|
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||||
|
pay_fee = int(tx_fee_rate * vsize / 1000)
|
||||||
|
tx.vout[0].nValue = locked_coin - pay_fee
|
||||||
|
|
||||||
|
tx.rehash()
|
||||||
|
logging.info('createScriptLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
|
||||||
|
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
|
||||||
|
|
||||||
|
return tx
|
||||||
|
|
||||||
|
def createScriptLockSpendTx(self, tx_lock, script_lock, pkh_dest, tx_fee_rate):
|
||||||
|
|
||||||
|
output_script = CScript([OP_0, hashlib.sha256(script_lock).digest()])
|
||||||
|
locked_n = findOutput(tx_lock, output_script)
|
||||||
|
assert_cond(locked_n is not None, 'Output not found in tx')
|
||||||
|
locked_coin = tx_lock.vout[locked_n].nValue
|
||||||
|
|
||||||
|
tx_lock.rehash()
|
||||||
|
tx_lock_hash_int = tx_lock.sha256
|
||||||
|
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.nVersion = self.txVersion()
|
||||||
|
tx.vin.append(CTxIn(COutPoint(tx_lock_hash_int, locked_n)))
|
||||||
|
|
||||||
|
p2wpkh = CScript([OP_0, pkh_dest])
|
||||||
|
tx.vout.append(self.txoType(locked_coin, p2wpkh))
|
||||||
|
|
||||||
|
witness_bytes = len(script_lock)
|
||||||
|
witness_bytes += 33 # sv, size
|
||||||
|
witness_bytes += 73 * 2 # 2 signatures (72 + 1 byts size)
|
||||||
|
witness_bytes += 4 # 1 empty, 1 true witness stack values
|
||||||
|
witness_bytes += getCompactSizeLen(witness_bytes)
|
||||||
|
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||||
|
pay_fee = int(tx_fee_rate * vsize / 1000)
|
||||||
|
tx.vout[0].nValue = locked_coin - pay_fee
|
||||||
|
|
||||||
|
tx.rehash()
|
||||||
|
logging.info('createScriptLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
|
||||||
|
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
|
||||||
|
|
||||||
|
return tx
|
||||||
|
|
||||||
|
def verifyLockTx(self, tx, script_out,
|
||||||
|
swap_value,
|
||||||
|
sh,
|
||||||
|
Kal, Kaf,
|
||||||
|
lock_value, feerate,
|
||||||
|
Karl, Karf,
|
||||||
|
check_lock_tx_inputs):
|
||||||
|
# Verify:
|
||||||
|
#
|
||||||
|
|
||||||
|
# Not necessary to check the lock txn is mineable, as protocol will wait for it to confirm
|
||||||
|
# However by checking early we can avoid wasting time processing unmineable txns
|
||||||
|
# Check fee is reasonable
|
||||||
|
|
||||||
|
tx_hash = self.getTxHash(tx)
|
||||||
|
logging.info('Verifying lock tx: {}.'.format(b2h(tx_hash)))
|
||||||
|
|
||||||
|
assert_cond(tx.nVersion == self.txVersion(), 'Bad version')
|
||||||
|
assert_cond(tx.nLockTime == 0, 'Bad nLockTime')
|
||||||
|
|
||||||
|
script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()])
|
||||||
|
locked_n = findOutput(tx, script_pk)
|
||||||
|
assert_cond(locked_n is not None, 'Output not found in tx')
|
||||||
|
locked_coin = tx.vout[locked_n].nValue
|
||||||
|
|
||||||
|
assert_cond(locked_coin == swap_value, 'Bad locked value')
|
||||||
|
|
||||||
|
# Check script and values
|
||||||
|
shv, A, B, csv_val, C, D = self.extractScriptLockScriptValues(script_out)
|
||||||
|
assert_cond(shv == sh, 'Bad hash lock')
|
||||||
|
assert_cond(A == self.encodePubkey(Kal), 'Bad script pubkey')
|
||||||
|
assert_cond(B == self.encodePubkey(Kaf), 'Bad script pubkey')
|
||||||
|
assert_cond(csv_val == lock_value, 'Bad script csv value')
|
||||||
|
assert_cond(C == self.encodePubkey(Karl), 'Bad script pubkey')
|
||||||
|
assert_cond(D == self.encodePubkey(Karf), 'Bad script pubkey')
|
||||||
|
|
||||||
|
if check_lock_tx_inputs:
|
||||||
|
# Check that inputs are unspent and verify fee rate
|
||||||
|
inputs_value = 0
|
||||||
|
add_bytes = 0
|
||||||
|
add_witness_bytes = getCompactSizeLen(len(tx.vin))
|
||||||
|
for pi in tx.vin:
|
||||||
|
ptx = self.rpc_callback('getrawtransaction', [i2h(pi.prevout.hash), True])
|
||||||
|
print('ptx', dumpj(ptx))
|
||||||
|
prevout = ptx['vout'][pi.prevout.n]
|
||||||
|
inputs_value += make_int(prevout['value'])
|
||||||
|
|
||||||
|
prevout_type = prevout['scriptPubKey']['type']
|
||||||
|
if prevout_type == 'witness_v0_keyhash':
|
||||||
|
add_witness_bytes += 107 # sig 72, pk 33 and 2 size bytes
|
||||||
|
add_witness_bytes += getCompactSizeLen(107)
|
||||||
|
else:
|
||||||
|
# Assume P2PKH, TODO more types
|
||||||
|
add_bytes += 107 # OP_PUSH72 <ecdsa_signature> OP_PUSH33 <public_key>
|
||||||
|
|
||||||
|
outputs_value = 0
|
||||||
|
for txo in tx.vout:
|
||||||
|
outputs_value += txo.nValue
|
||||||
|
fee_paid = inputs_value - outputs_value
|
||||||
|
assert(fee_paid > 0)
|
||||||
|
|
||||||
|
vsize = self.getTxVSize(tx, add_bytes, add_witness_bytes)
|
||||||
|
fee_rate_paid = fee_paid * 1000 / vsize
|
||||||
|
|
||||||
|
logging.info('tx amount, vsize, feerate: %ld, %ld, %ld', locked_coin, vsize, fee_rate_paid)
|
||||||
|
|
||||||
|
if not self.compareFeeRates(fee_rate_paid, feerate):
|
||||||
|
logging.warning('feerate paid doesn\'t match expected: %ld, %ld', fee_rate_paid, feerate)
|
||||||
|
# TODO: Display warning to user
|
||||||
|
|
||||||
|
return tx_hash, locked_n
|
||||||
|
|
||||||
|
def verifyLockRefundTx(self, tx, script_out,
|
||||||
|
prevout_id, prevout_n, prevout_seq, prevout_script,
|
||||||
|
Karl, Karf, csv_val_expect, Kaf, swap_value, feerate):
|
||||||
|
# Verify:
|
||||||
|
# Must have only one input with correct prevout and sequence
|
||||||
|
# Must have only one output to the p2wsh of the lock refund script
|
||||||
|
# Output value must be locked_coin - lock tx fee
|
||||||
|
|
||||||
|
tx_hash = self.getTxHash(tx)
|
||||||
|
logging.info('Verifying lock refund tx: {}.'.format(b2h(tx_hash)))
|
||||||
|
|
||||||
|
assert_cond(tx.nVersion == self.txVersion(), 'Bad version')
|
||||||
|
assert_cond(tx.nLockTime == 0, 'nLockTime not 0')
|
||||||
|
assert_cond(len(tx.vin) == 1, 'tx doesn\'t have one input')
|
||||||
|
|
||||||
|
assert_cond(tx.vin[0].nSequence == prevout_seq, 'Bad input nSequence')
|
||||||
|
assert_cond(len(tx.vin[0].scriptSig) == 0, 'Input scriptsig not empty')
|
||||||
|
assert_cond(tx.vin[0].prevout.hash == b2i(prevout_id) and tx.vin[0].prevout.n == prevout_n, 'Input prevout mismatch')
|
||||||
|
|
||||||
|
assert_cond(len(tx.vout) == 1, 'tx doesn\'t have one output')
|
||||||
|
|
||||||
|
script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()])
|
||||||
|
locked_n = findOutput(tx, script_pk)
|
||||||
|
assert_cond(locked_n is not None, 'Output not found in tx')
|
||||||
|
locked_coin = tx.vout[locked_n].nValue
|
||||||
|
|
||||||
|
# Check script and values
|
||||||
|
A, B, csv_val, C = self.extractScriptLockRefundScriptValues(script_out)
|
||||||
|
assert_cond(A == self.encodePubkey(Karl), 'Bad script pubkey')
|
||||||
|
assert_cond(B == self.encodePubkey(Karf), 'Bad script pubkey')
|
||||||
|
assert_cond(csv_val == csv_val_expect, 'Bad script csv value')
|
||||||
|
assert_cond(C == self.encodePubkey(Kaf), 'Bad script pubkey')
|
||||||
|
|
||||||
|
fee_paid = swap_value - locked_coin
|
||||||
|
assert(fee_paid > 0)
|
||||||
|
|
||||||
|
witness_bytes = len(prevout_script)
|
||||||
|
witness_bytes += 73 * 2 # 2 signatures (72 + 1 byts size)
|
||||||
|
witness_bytes += 2 # 2 empty witness stack values
|
||||||
|
witness_bytes += getCompactSizeLen(witness_bytes)
|
||||||
|
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||||
|
fee_rate_paid = fee_paid * 1000 / vsize
|
||||||
|
|
||||||
|
logging.info('tx amount, vsize, feerate: %ld, %ld, %ld', locked_coin, vsize, fee_rate_paid)
|
||||||
|
|
||||||
|
if not self.compareFeeRates(fee_rate_paid, feerate):
|
||||||
|
raise ValueError('Bad fee rate')
|
||||||
|
|
||||||
|
return tx_hash, locked_coin
|
||||||
|
|
||||||
|
def verifyLockRefundSpendTx(self, tx,
|
||||||
|
lock_refund_tx_id, prevout_script,
|
||||||
|
Kal,
|
||||||
|
prevout_value, feerate):
|
||||||
|
# Verify:
|
||||||
|
# Must have only one input with correct prevout (n is always 0) and sequence
|
||||||
|
# Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr
|
||||||
|
tx_hash = self.getTxHash(tx)
|
||||||
|
logging.info('Verifying lock refund spend tx: {}.'.format(b2h(tx_hash)))
|
||||||
|
|
||||||
|
assert_cond(tx.nVersion == self.txVersion(), 'Bad version')
|
||||||
|
assert_cond(tx.nLockTime == 0, 'nLockTime not 0')
|
||||||
|
assert_cond(len(tx.vin) == 1, 'tx doesn\'t have one input')
|
||||||
|
|
||||||
|
assert_cond(tx.vin[0].nSequence == 0, 'Bad input nSequence')
|
||||||
|
assert_cond(len(tx.vin[0].scriptSig) == 0, 'Input scriptsig not empty')
|
||||||
|
assert_cond(tx.vin[0].prevout.hash == b2i(lock_refund_tx_id) and tx.vin[0].prevout.n == 0, 'Input prevout mismatch')
|
||||||
|
|
||||||
|
assert_cond(len(tx.vout) == 1, 'tx doesn\'t have one output')
|
||||||
|
|
||||||
|
p2wpkh = CScript([OP_0, hash160(self.encodePubkey(Kal))])
|
||||||
|
locked_n = findOutput(tx, p2wpkh)
|
||||||
|
assert_cond(locked_n is not None, 'Output not found in lock refund spend tx')
|
||||||
|
tx_value = tx.vout[locked_n].nValue
|
||||||
|
|
||||||
|
fee_paid = prevout_value - tx_value
|
||||||
|
assert(fee_paid > 0)
|
||||||
|
|
||||||
|
witness_bytes = len(prevout_script)
|
||||||
|
witness_bytes += 73 * 2 # 2 signatures (72 + 1 byts size)
|
||||||
|
witness_bytes += 4 # 1 empty, 1 true witness stack values
|
||||||
|
witness_bytes += getCompactSizeLen(witness_bytes)
|
||||||
|
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||||
|
fee_rate_paid = fee_paid * 1000 / vsize
|
||||||
|
|
||||||
|
logging.info('tx amount, vsize, feerate: %ld, %ld, %ld', tx_value, vsize, fee_rate_paid)
|
||||||
|
|
||||||
|
if not self.compareFeeRates(fee_rate_paid, feerate):
|
||||||
|
raise ValueError('Bad fee rate')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def verifyLockSpendTx(self, tx,
|
||||||
|
lock_tx, lock_tx_script,
|
||||||
|
a_pkhash_f, feerate):
|
||||||
|
# Verify:
|
||||||
|
# Must have only one input with correct prevout (n is always 0) and sequence
|
||||||
|
# Must have only one output with destination and amount
|
||||||
|
|
||||||
|
tx_hash = self.getTxHash(tx)
|
||||||
|
logging.info('Verifying lock spend tx: {}.'.format(b2h(tx_hash)))
|
||||||
|
|
||||||
|
assert_cond(tx.nVersion == self.txVersion(), 'Bad version')
|
||||||
|
assert_cond(tx.nLockTime == 0, 'nLockTime not 0')
|
||||||
|
assert_cond(len(tx.vin) == 1, 'tx doesn\'t have one input')
|
||||||
|
|
||||||
|
lock_tx_id = self.getTxHash(lock_tx)
|
||||||
|
|
||||||
|
output_script = CScript([OP_0, hashlib.sha256(lock_tx_script).digest()])
|
||||||
|
locked_n = findOutput(lock_tx, output_script)
|
||||||
|
assert_cond(locked_n is not None, 'Output not found in tx')
|
||||||
|
locked_coin = lock_tx.vout[locked_n].nValue
|
||||||
|
|
||||||
|
assert_cond(tx.vin[0].nSequence == 0, 'Bad input nSequence')
|
||||||
|
assert_cond(len(tx.vin[0].scriptSig) == 0, 'Input scriptsig not empty')
|
||||||
|
assert_cond(tx.vin[0].prevout.hash == b2i(lock_tx_id) and tx.vin[0].prevout.n == locked_n, 'Input prevout mismatch')
|
||||||
|
|
||||||
|
assert_cond(len(tx.vout) == 1, 'tx doesn\'t have one output')
|
||||||
|
p2wpkh = CScript([OP_0, a_pkhash_f])
|
||||||
|
assert_cond(tx.vout[0].scriptPubKey == p2wpkh, 'Bad output destination')
|
||||||
|
|
||||||
|
fee_paid = locked_coin - tx.vout[0].nValue
|
||||||
|
assert(fee_paid > 0)
|
||||||
|
|
||||||
|
witness_bytes = len(lock_tx_script)
|
||||||
|
witness_bytes += 33 # sv, size
|
||||||
|
witness_bytes += 73 * 2 # 2 signatures (72 + 1 byts size)
|
||||||
|
witness_bytes += 4 # 1 empty, 1 true witness stack values
|
||||||
|
witness_bytes += getCompactSizeLen(witness_bytes)
|
||||||
|
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||||
|
fee_rate_paid = fee_paid * 1000 / vsize
|
||||||
|
|
||||||
|
logging.info('tx amount, vsize, feerate: %ld, %ld, %ld', tx.vout[0].nValue, vsize, fee_rate_paid)
|
||||||
|
|
||||||
|
if not self.compareFeeRates(fee_rate_paid, feerate):
|
||||||
|
raise ValueError('Bad fee rate')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def signTx(self, key_int, tx, prevout_n, prevout_script, prevout_value):
|
||||||
|
sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value)
|
||||||
|
|
||||||
|
eck = ECKey()
|
||||||
|
eck.set(i2b(key_int), compressed=True)
|
||||||
|
|
||||||
|
return eck.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL
|
||||||
|
|
||||||
|
def signTxOtVES(self, key_sign, key_encrypt, tx, prevout_n, prevout_script, prevout_value):
|
||||||
|
sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value)
|
||||||
|
return otves.EncSign(key_sign, key_encrypt, sig_hash)
|
||||||
|
|
||||||
|
def verifyTxOtVES(self, tx, sig, Ks, Ke, prevout_n, prevout_script, prevout_value):
|
||||||
|
sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value)
|
||||||
|
return otves.EncVrfy(Ks, Ke, sig_hash, sig)
|
||||||
|
|
||||||
|
def decryptOtVES(self, k, esig):
|
||||||
|
return otves.DecSig(k, esig) + b'\x01' # 0x1 is SIGHASH_ALL
|
||||||
|
|
||||||
|
def verifyTxSig(self, tx, sig, K, prevout_n, prevout_script, prevout_value):
|
||||||
|
sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value)
|
||||||
|
|
||||||
|
ecK = ECPubKey()
|
||||||
|
ecK.set_int(K.x(), K.y())
|
||||||
|
return ecK.verify_ecdsa(sig[: -1], sig_hash) # Pop the hashtype byte
|
||||||
|
|
||||||
|
def fundTx(self, tx, feerate):
|
||||||
|
feerate_str = format_amount(feerate, self.exp())
|
||||||
|
rv = self.rpc_callback('fundrawtransaction', [ToHex(tx), {'feeRate': feerate_str}])
|
||||||
|
return FromHex(tx, rv['hex'])
|
||||||
|
|
||||||
|
def signTxWithWallet(self, tx):
|
||||||
|
rv = self.rpc_callback('signrawtransactionwithwallet', [ToHex(tx)])
|
||||||
|
|
||||||
|
return FromHex(tx, rv['hex'])
|
||||||
|
|
||||||
|
def publishTx(self, tx):
|
||||||
|
return self.rpc_callback('sendrawtransaction', [ToHex(tx)])
|
||||||
|
|
||||||
|
def encodeTx(self, tx):
|
||||||
|
return tx.serialize()
|
||||||
|
|
||||||
|
def loadTx(self, tx_bytes):
|
||||||
|
# Load tx from bytes to internal representation
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.deserialize(BytesIO(tx_bytes))
|
||||||
|
return tx
|
||||||
|
|
||||||
|
def getTxHash(self, tx):
|
||||||
|
tx.rehash()
|
||||||
|
return i2b(tx.sha256)
|
||||||
|
|
||||||
|
def getPubkeyHash(self, K):
|
||||||
|
return hash160(self.encodePubkey(K))
|
||||||
|
|
||||||
|
def getScriptDest(self, script):
|
||||||
|
return CScript([OP_0, hashlib.sha256(script).digest()])
|
||||||
|
|
||||||
|
def getPkDest(self, K):
|
||||||
|
return CScript([OP_0, self.getPubkeyHash(K)])
|
||||||
|
|
||||||
|
def scanTxOutset(self, dest):
|
||||||
|
return self.rpc_callback('scantxoutset', ['start', ['raw({})'.format(dest.hex())]])
|
||||||
|
|
||||||
|
def getTransaction(self, txid):
|
||||||
|
try:
|
||||||
|
return self.rpc_callback('getrawtransaction', [txid.hex()])
|
||||||
|
except Exception as ex:
|
||||||
|
# TODO: filter errors
|
||||||
|
return None
|
||||||
|
|
||||||
|
def setTxSignature(self, tx, stack):
|
||||||
|
tx.wit.vtxinwit.clear()
|
||||||
|
tx.wit.vtxinwit.append(CTxInWitness())
|
||||||
|
tx.wit.vtxinwit[0].scriptWitness.stack = stack
|
||||||
|
return True
|
||||||
|
|
||||||
|
def extractLeaderSig(self, tx):
|
||||||
|
return tx.wit.vtxinwit[0].scriptWitness.stack[1]
|
||||||
|
|
||||||
|
def extractFollowerSig(self, tx):
|
||||||
|
return tx.wit.vtxinwit[0].scriptWitness.stack[2]
|
||||||
|
|
||||||
|
def createBLockTx(self, Kbs, output_amount):
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.nVersion = self.txVersion()
|
||||||
|
p2wpkh = self.getPkDest(Kbs)
|
||||||
|
tx.vout.append(self.txoType(output_amount, p2wpkh))
|
||||||
|
return tx
|
||||||
|
|
||||||
|
def publishBLockTx(self, Kbv, Kbs, output_amount, feerate):
|
||||||
|
b_lock_tx = self.createBLockTx(Kbs, output_amount)
|
||||||
|
|
||||||
|
b_lock_tx = self.fundTx(b_lock_tx, feerate)
|
||||||
|
b_lock_tx_id = self.getTxHash(b_lock_tx)
|
||||||
|
b_lock_tx = self.signTxWithWallet(b_lock_tx)
|
||||||
|
|
||||||
|
return self.publishTx(b_lock_tx)
|
||||||
|
|
||||||
|
def recoverEncKey(self, esig, sig, K):
|
||||||
|
return otves.RecoverEncKey(esig, sig[:-1], K) # Strip sighash type
|
||||||
|
|
||||||
|
def getTxVSize(self, tx, add_bytes=0, add_witness_bytes=0):
|
||||||
|
wsf = self.witnessScaleFactor()
|
||||||
|
len_full = len(tx.serialize_with_witness()) + add_bytes + add_witness_bytes
|
||||||
|
len_nwit = len(tx.serialize_without_witness()) + add_bytes
|
||||||
|
weight = len_nwit * (wsf - 1) + len_full
|
||||||
|
return (weight + wsf - 1) // wsf
|
||||||
|
|
||||||
|
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height):
|
||||||
|
raw_dest = self.getPkDest(Kbs)
|
||||||
|
|
||||||
|
rv = self.scanTxOutset(raw_dest)
|
||||||
|
print('scanTxOutset', dumpj(rv))
|
||||||
|
|
||||||
|
for utxo in rv['unspents']:
|
||||||
|
if 'height' in utxo and utxo['height'] > 0 and rv['height'] - utxo['height'] > cb_block_confirmed:
|
||||||
|
if utxo['amount'] * COIN != cb_swap_value:
|
||||||
|
logging.warning('Found output to lock tx pubkey of incorrect value: %s', str(utxo['amount']))
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def waitForLockTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed):
|
||||||
|
|
||||||
|
raw_dest = self.getPkDest(Kbs)
|
||||||
|
|
||||||
|
for i in range(20):
|
||||||
|
time.sleep(1)
|
||||||
|
rv = self.scanTxOutset(raw_dest)
|
||||||
|
print('scanTxOutset', dumpj(rv))
|
||||||
|
|
||||||
|
for utxo in rv['unspents']:
|
||||||
|
if 'height' in utxo and utxo['height'] > 0 and rv['height'] - utxo['height'] > cb_block_confirmed:
|
||||||
|
|
||||||
|
if utxo['amount'] * COIN != cb_swap_value:
|
||||||
|
logging.warning('Found output to lock tx pubkey of incorrect value: %s', str(utxo['amount']))
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def spendBLockTx(self, address_to, kbv, kbs, cb_swap_value, b_fee, restore_height):
|
||||||
|
print('TODO: spendBLockTx')
|
||||||
|
|
||||||
|
|
||||||
|
def testBTCInterface():
|
||||||
|
print('testBTCInterface')
|
||||||
|
script_bytes = bytes.fromhex('6382012088a820aaf125ff9a34a74c7a17f5e7ee9d07d17cc5e53a539f345d5f73baa7e79b65e28852210224019219ad43c47288c937ae508f26998dd81ec066827773db128fd5e262c04f21039a0fd752bd1a2234820707852e7a30253620052ecd162948a06532a817710b5952ae670114b2755221038689deba25c5578e5457ddadbaf8aeb8badf438dc22f540503dbd4ae10e14f512103c9c5d5acc996216d10852a72cd67c701bfd4b9137a4076350fd32f08db39575552ae68')
|
||||||
|
i = BTCInterface(None)
|
||||||
|
sh, a, b, csv_val, c, d = i.extractScriptLockScriptValues(script_bytes)
|
||||||
|
assert(csv_val == 20)
|
||||||
|
|
||||||
|
script_bytes_t = script_bytes + bytes((0x00,))
|
||||||
|
try:
|
||||||
|
sh, a, b, csv_val, c, d = i.extractScriptLockScriptValues(script_bytes_t)
|
||||||
|
assert(False), 'Should fail'
|
||||||
|
except Exception as e:
|
||||||
|
assert(str(e) == 'Bad script length')
|
||||||
|
|
||||||
|
script_bytes_t = script_bytes[:-1]
|
||||||
|
try:
|
||||||
|
sh, a, b, csv_val, c, d = i.extractScriptLockScriptValues(script_bytes_t)
|
||||||
|
assert(False), 'Should fail'
|
||||||
|
except Exception as e:
|
||||||
|
assert(str(e) == 'Bad script length')
|
||||||
|
|
||||||
|
script_bytes_t = bytes((0x00,)) + script_bytes[1:]
|
||||||
|
try:
|
||||||
|
sh, a, b, csv_val, c, d = i.extractScriptLockScriptValues(script_bytes_t)
|
||||||
|
assert(False), 'Should fail'
|
||||||
|
except Exception as e:
|
||||||
|
assert(str(e) == 'Bad opcode')
|
||||||
|
|
||||||
|
# Remove the csv value
|
||||||
|
script_part_a = script_bytes[:112]
|
||||||
|
script_part_b = script_bytes[114:]
|
||||||
|
|
||||||
|
script_bytes_t = script_part_a + bytes((0x00,)) + script_part_b
|
||||||
|
sh, a, b, csv_val, c, d = i.extractScriptLockScriptValues(script_bytes_t)
|
||||||
|
assert(csv_val == 0)
|
||||||
|
|
||||||
|
script_bytes_t = script_part_a + bytes((OP_16,)) + script_part_b
|
||||||
|
sh, a, b, csv_val, c, d = i.extractScriptLockScriptValues(script_bytes_t)
|
||||||
|
assert(csv_val == 16)
|
||||||
|
|
||||||
|
script_bytes_t = script_part_a + CScriptNum.encode(CScriptNum(17)) + script_part_b
|
||||||
|
sh, a, b, csv_val, c, d = i.extractScriptLockScriptValues(script_bytes_t)
|
||||||
|
assert(csv_val == 17)
|
||||||
|
|
||||||
|
script_bytes_t = script_part_a + CScriptNum.encode(CScriptNum(-15)) + script_part_b
|
||||||
|
sh, a, b, csv_val, c, d = i.extractScriptLockScriptValues(script_bytes_t)
|
||||||
|
assert(csv_val == -15)
|
||||||
|
|
||||||
|
script_bytes_t = script_part_a + CScriptNum.encode(CScriptNum(4000)) + script_part_b
|
||||||
|
sh, a, b, csv_val, c, d = i.extractScriptLockScriptValues(script_bytes_t)
|
||||||
|
assert(csv_val == 4000)
|
||||||
|
|
||||||
|
max_pos = 0x7FFFFFFF
|
||||||
|
script_bytes_t = script_part_a + CScriptNum.encode(CScriptNum(max_pos)) + script_part_b
|
||||||
|
sh, a, b, csv_val, c, d = i.extractScriptLockScriptValues(script_bytes_t)
|
||||||
|
assert(csv_val == max_pos)
|
||||||
|
script_bytes_t = script_part_a + CScriptNum.encode(CScriptNum(max_pos - 1)) + script_part_b
|
||||||
|
sh, a, b, csv_val, c, d = i.extractScriptLockScriptValues(script_bytes_t)
|
||||||
|
assert(csv_val == max_pos - 1)
|
||||||
|
|
||||||
|
script_bytes_t = script_part_a + CScriptNum.encode(CScriptNum(max_pos + 1)) + script_part_b
|
||||||
|
try:
|
||||||
|
sh, a, b, csv_val, c, d = i.extractScriptLockScriptValues(script_bytes_t)
|
||||||
|
assert(False), 'Should fail'
|
||||||
|
except Exception as e:
|
||||||
|
assert(str(e) == 'Bad scriptnum length')
|
||||||
|
|
||||||
|
min_neg = -2147483647
|
||||||
|
script_bytes_t = script_part_a + CScriptNum.encode(CScriptNum(min_neg)) + script_part_b
|
||||||
|
sh, a, b, csv_val, c, d = i.extractScriptLockScriptValues(script_bytes_t)
|
||||||
|
assert(csv_val == min_neg)
|
||||||
|
|
||||||
|
script_bytes_t = script_part_a + CScriptNum.encode(CScriptNum(min_neg - 1)) + script_part_b
|
||||||
|
try:
|
||||||
|
sh, a, b, csv_val, c, d = i.extractScriptLockScriptValues(script_bytes_t)
|
||||||
|
assert(False), 'Should fail'
|
||||||
|
except Exception as e:
|
||||||
|
assert(str(e) == 'Bad scriptnum length')
|
||||||
|
|
||||||
|
print('Passed.')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
testBTCInterface()
|
12
basicswap/interface_ltc.py
Normal file
12
basicswap/interface_ltc.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2020 tecnovert
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
from .interface_btc import BTCInterface
|
||||||
|
|
||||||
|
|
||||||
|
class LTCInterface(BTCInterface):
|
||||||
|
pass
|
28
basicswap/interface_part.py
Normal file
28
basicswap/interface_part.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2020 tecnovert
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
from .contrib.test_framework.messages import (
|
||||||
|
CTxOutPart,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .interface_btc import BTCInterface
|
||||||
|
from .chainparams import CoinInterface
|
||||||
|
from .rpc import make_rpc_func
|
||||||
|
|
||||||
|
|
||||||
|
class PARTInterface(BTCInterface):
|
||||||
|
@staticmethod
|
||||||
|
def witnessScaleFactor():
|
||||||
|
return 2
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def txVersion():
|
||||||
|
return 0xa0
|
||||||
|
|
||||||
|
def __init__(self, coin_settings):
|
||||||
|
self.rpc_callback = make_rpc_func(coin_settings['rpcport'], coin_settings['rpcauth'])
|
||||||
|
self.txoType = CTxOutPart
|
230
basicswap/interface_xmr.py
Normal file
230
basicswap/interface_xmr.py
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2020 tecnovert
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from .chainparams import CoinInterface
|
||||||
|
from .rpc_xmr import make_xmr_rpc_func, make_xmr_wallet_rpc_func
|
||||||
|
|
||||||
|
XMR_COIN = 10 ** 12
|
||||||
|
|
||||||
|
|
||||||
|
class XMRInterface(CoinInterface):
|
||||||
|
@staticmethod
|
||||||
|
def exp():
|
||||||
|
return 12
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def nbk():
|
||||||
|
return 32
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def nbK(): # No. of bytes requires to encode a public key
|
||||||
|
return 32
|
||||||
|
|
||||||
|
def __init__(self, coin_settings):
|
||||||
|
rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'])
|
||||||
|
rpc_wallet_cb = make_xmr_wallet_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'])
|
||||||
|
|
||||||
|
self.rpc_cb = rpc_cb # Not essential
|
||||||
|
self.rpc_wallet_cb = rpc_wallet_cb
|
||||||
|
|
||||||
|
def getNewSecretKey(self):
|
||||||
|
return edu.get_secret()
|
||||||
|
|
||||||
|
def pubkey(self, key):
|
||||||
|
return edf.scalarmult_B(key)
|
||||||
|
|
||||||
|
def encodePubkey(self, pk):
|
||||||
|
return edu.encodepoint(pk)
|
||||||
|
|
||||||
|
def decodePubkey(self, pke):
|
||||||
|
return edf.decodepoint(pke)
|
||||||
|
|
||||||
|
def decodeKey(self, k):
|
||||||
|
i = b2i(k)
|
||||||
|
assert(i < edf.l and i > 8)
|
||||||
|
return i
|
||||||
|
|
||||||
|
def sumKeys(self, ka, kb):
|
||||||
|
return (ka + kb) % edf.l
|
||||||
|
|
||||||
|
def sumPubkeys(self, Ka, Kb):
|
||||||
|
return edf.edwards_add(Ka, Kb)
|
||||||
|
|
||||||
|
def publishBLockTx(self, Kbv, Kbs, output_amount, feerate):
|
||||||
|
|
||||||
|
shared_addr = xmr_util.encode_address(self.encodePubkey(Kbv), self.encodePubkey(Kbs))
|
||||||
|
|
||||||
|
# TODO: How to set feerate?
|
||||||
|
params = {'destinations': [{'amount': output_amount, 'address': shared_addr}]}
|
||||||
|
rv = self.rpc_wallet_cb('transfer', params)
|
||||||
|
logging.info('publishBLockTx %s to address_b58 %s', rv['tx_hash'], shared_addr)
|
||||||
|
|
||||||
|
return rv['tx_hash']
|
||||||
|
|
||||||
|
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height):
|
||||||
|
Kbv_enc = self.encodePubkey(self.pubkey(kbv))
|
||||||
|
address_b58 = xmr_util.encode_address(Kbv_enc, self.encodePubkey(Kbs))
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.rpc_wallet_cb('close_wallet')
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning('close_wallet failed %s', str(e))
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'restore_height': restore_height,
|
||||||
|
'filename': address_b58,
|
||||||
|
'address': address_b58,
|
||||||
|
'viewkey': b2h(intToBytes32_le(kbv)),
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
rv = self.rpc_wallet_cb('open_wallet', {'filename': address_b58})
|
||||||
|
except Exception as e:
|
||||||
|
rv = self.rpc_wallet_cb('generate_from_keys', params)
|
||||||
|
logging.info('generate_from_keys %s', dumpj(rv))
|
||||||
|
rv = self.rpc_wallet_cb('open_wallet', {'filename': address_b58})
|
||||||
|
|
||||||
|
# Debug
|
||||||
|
try:
|
||||||
|
current_height = self.rpc_cb('get_block_count')['count']
|
||||||
|
logging.info('findTxB XMR current_height %d\nAddress: %s', current_height, address_b58)
|
||||||
|
except Exception as e:
|
||||||
|
logging.info('rpc_cb failed %s', str(e))
|
||||||
|
current_height = None # If the transfer is available it will be deep enough
|
||||||
|
|
||||||
|
# For a while after opening the wallet rpc cmds return empty data
|
||||||
|
for i in range(5):
|
||||||
|
params = {'transfer_type': 'available'}
|
||||||
|
rv = self.rpc_wallet_cb('incoming_transfers', params)
|
||||||
|
if 'transfers' in rv:
|
||||||
|
for transfer in rv['transfers']:
|
||||||
|
if transfer['amount'] == cb_swap_value \
|
||||||
|
and (current_height is None or current_height - transfer['block_height'] > cb_block_confirmed):
|
||||||
|
return True
|
||||||
|
time.sleep(1 + i)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def waitForLockTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height):
|
||||||
|
|
||||||
|
Kbv_enc = self.encodePubkey(self.pubkey(kbv))
|
||||||
|
address_b58 = xmr_util.encode_address(Kbv_enc, self.encodePubkey(Kbs))
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.rpc_wallet_cb('close_wallet')
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning('close_wallet failed %s', str(e))
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'filename': address_b58,
|
||||||
|
'address': address_b58,
|
||||||
|
'viewkey': b2h(intToBytes32_le(kbv)),
|
||||||
|
'restore_height': restore_height,
|
||||||
|
}
|
||||||
|
self.rpc_wallet_cb('generate_from_keys', params)
|
||||||
|
|
||||||
|
self.rpc_wallet_cb('open_wallet', {'filename': address_b58})
|
||||||
|
# For a while after opening the wallet rpc cmds return empty data
|
||||||
|
|
||||||
|
num_tries = 40
|
||||||
|
for i in range(num_tries + 1):
|
||||||
|
try:
|
||||||
|
current_height = self.rpc_cb('get_block_count')['count']
|
||||||
|
print('current_height', current_height)
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning('rpc_cb failed %s', str(e))
|
||||||
|
current_height = None # If the transfer is available it will be deep enough
|
||||||
|
|
||||||
|
# TODO: Make accepting current_height == None a user selectable option
|
||||||
|
# Or look for all transfers and check height
|
||||||
|
|
||||||
|
params = {'transfer_type': 'available'}
|
||||||
|
rv = self.rpc_wallet_cb('incoming_transfers', params)
|
||||||
|
print('rv', rv)
|
||||||
|
|
||||||
|
if 'transfers' in rv:
|
||||||
|
for transfer in rv['transfers']:
|
||||||
|
if transfer['amount'] == cb_swap_value \
|
||||||
|
and (current_height is None or current_height - transfer['block_height'] > cb_block_confirmed):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# TODO: Is it necessary to check the address?
|
||||||
|
|
||||||
|
'''
|
||||||
|
rv = self.rpc_wallet_cb('get_balance')
|
||||||
|
print('get_balance', rv)
|
||||||
|
|
||||||
|
if 'per_subaddress' in rv:
|
||||||
|
for sub_addr in rv['per_subaddress']:
|
||||||
|
if sub_addr['address'] == address_b58:
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
if i >= num_tries:
|
||||||
|
raise ValueError('Balance not confirming on node')
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def spendBLockTx(self, address_to, kbv, kbs, cb_swap_value, b_fee_rate, restore_height):
|
||||||
|
|
||||||
|
Kbv_enc = self.encodePubkey(self.pubkey(kbv))
|
||||||
|
Kbs_enc = self.encodePubkey(self.pubkey(kbs))
|
||||||
|
address_b58 = xmr_util.encode_address(Kbv_enc, Kbs_enc)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.rpc_wallet_cb('close_wallet')
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning('close_wallet failed %s', str(e))
|
||||||
|
|
||||||
|
wallet_filename = address_b58 + '_spend'
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'filename': wallet_filename,
|
||||||
|
'address': address_b58,
|
||||||
|
'viewkey': b2h(intToBytes32_le(kbv)),
|
||||||
|
'spendkey': b2h(intToBytes32_le(kbs)),
|
||||||
|
'restore_height': restore_height,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.rpc_wallet_cb('open_wallet', {'filename': wallet_filename})
|
||||||
|
except Exception as e:
|
||||||
|
rv = self.rpc_wallet_cb('generate_from_keys', params)
|
||||||
|
logging.info('generate_from_keys %s', dumpj(rv))
|
||||||
|
self.rpc_wallet_cb('open_wallet', {'filename': wallet_filename})
|
||||||
|
|
||||||
|
# For a while after opening the wallet rpc cmds return empty data
|
||||||
|
for i in range(10):
|
||||||
|
rv = self.rpc_wallet_cb('get_balance')
|
||||||
|
print('get_balance', rv)
|
||||||
|
if rv['balance'] >= cb_swap_value:
|
||||||
|
break
|
||||||
|
|
||||||
|
time.sleep(1 + i)
|
||||||
|
|
||||||
|
# TODO: need a subfee from output option
|
||||||
|
b_fee = b_fee_rate * 10 # Guess
|
||||||
|
|
||||||
|
num_tries = 20
|
||||||
|
for i in range(1 + num_tries):
|
||||||
|
try:
|
||||||
|
params = {'destinations': [{'amount': cb_swap_value - b_fee, 'address': address_to}]}
|
||||||
|
rv = self.rpc_wallet_cb('transfer', params)
|
||||||
|
print('transfer', rv)
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print('str(e)', str(e))
|
||||||
|
if i >= num_tries:
|
||||||
|
raise ValueError('transfer failed.')
|
||||||
|
b_fee += b_fee_rate
|
||||||
|
logging.info('Raising fee to %d', b_fee)
|
||||||
|
|
||||||
|
return rv['tx_hash']
|
@ -93,8 +93,8 @@ class Jsonrpc():
|
|||||||
def callrpc(rpc_port, auth, method, params=[], wallet=None):
|
def callrpc(rpc_port, auth, method, params=[], wallet=None):
|
||||||
try:
|
try:
|
||||||
url = 'http://%s@127.0.0.1:%d/' % (auth, rpc_port)
|
url = 'http://%s@127.0.0.1:%d/' % (auth, rpc_port)
|
||||||
if wallet:
|
if wallet is not None:
|
||||||
url += 'wallet/' + wallet
|
url += 'wallet/' + urllib.parse.quote(wallet)
|
||||||
x = Jsonrpc(url)
|
x = Jsonrpc(url)
|
||||||
|
|
||||||
v = x.json_request(method, params)
|
v = x.json_request(method, params)
|
||||||
@ -126,3 +126,14 @@ def callrpc_cli(bindir, datadir, chain, cmd, cli_bin='particl-cli'):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def make_rpc_func(port, auth, wallet=None):
|
||||||
|
port = port
|
||||||
|
auth = auth
|
||||||
|
wallet = wallet
|
||||||
|
|
||||||
|
def rpc_func(method, params=None, wallet_override=None):
|
||||||
|
nonlocal port, auth, wallet
|
||||||
|
return callrpc(port, auth, method, params, wallet if wallet_override is None else wallet_override)
|
||||||
|
return rpc_func
|
||||||
|
85
basicswap/rpc_xmr.py
Normal file
85
basicswap/rpc_xmr.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
def callrpc_xmr(rpc_port, auth, method, params=[], path='json_rpc'):
|
||||||
|
# auth is a tuple: (username, password)
|
||||||
|
try:
|
||||||
|
url = 'http://127.0.0.1:{}/{}'.format(rpc_port, path)
|
||||||
|
request_body = {
|
||||||
|
'method': method,
|
||||||
|
'params': params,
|
||||||
|
'id': 2,
|
||||||
|
'jsonrpc': '2.0'
|
||||||
|
}
|
||||||
|
headers = {
|
||||||
|
'content-type': 'application/json'
|
||||||
|
}
|
||||||
|
p = requests.post(url, data=json.dumps(request_body), auth=requests.auth.HTTPDigestAuth(auth[0], auth[1]), headers=headers)
|
||||||
|
r = json.loads(p.text)
|
||||||
|
except Exception as ex:
|
||||||
|
raise ValueError('RPC Server Error: {}'.format(str(ex)))
|
||||||
|
|
||||||
|
if 'error' in r and r['error'] is not None:
|
||||||
|
raise ValueError('RPC error ' + str(r['error']))
|
||||||
|
|
||||||
|
return r['result']
|
||||||
|
|
||||||
|
|
||||||
|
def callrpc_xmr_na(rpc_port, method, params=[], path='json_rpc'):
|
||||||
|
try:
|
||||||
|
url = 'http://127.0.0.1:{}/{}'.format(rpc_port, path)
|
||||||
|
request_body = {
|
||||||
|
'method': method,
|
||||||
|
'params': params,
|
||||||
|
'id': 2,
|
||||||
|
'jsonrpc': '2.0'
|
||||||
|
}
|
||||||
|
headers = {
|
||||||
|
'content-type': 'application/json'
|
||||||
|
}
|
||||||
|
p = requests.post(url, data=json.dumps(request_body), headers=headers)
|
||||||
|
r = json.loads(p.text)
|
||||||
|
except Exception as ex:
|
||||||
|
raise ValueError('RPC Server Error: {}'.format(str(ex)))
|
||||||
|
|
||||||
|
if 'error' in r and r['error'] is not None:
|
||||||
|
raise ValueError('RPC error ' + str(r['error']))
|
||||||
|
|
||||||
|
return r['result']
|
||||||
|
|
||||||
|
|
||||||
|
def callrpc_xmr2(rpc_port, method, params=[]):
|
||||||
|
try:
|
||||||
|
url = 'http://127.0.0.1:{}/{}'.format(rpc_port, method)
|
||||||
|
headers = {
|
||||||
|
'content-type': 'application/json'
|
||||||
|
}
|
||||||
|
p = requests.post(url, data=json.dumps(params), headers=headers)
|
||||||
|
r = json.loads(p.text)
|
||||||
|
except Exception as ex:
|
||||||
|
raise ValueError('RPC Server Error: {}'.format(str(ex)))
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def make_xmr_rpc_func(port):
|
||||||
|
port = port
|
||||||
|
|
||||||
|
def rpc_func(method, params=None, wallet=None):
|
||||||
|
nonlocal port
|
||||||
|
return callrpc_xmr_na(port, method, params)
|
||||||
|
return rpc_func
|
||||||
|
|
||||||
|
|
||||||
|
def make_xmr_wallet_rpc_func(port, auth):
|
||||||
|
port = port
|
||||||
|
auth = auth
|
||||||
|
|
||||||
|
def rpc_func(method, params=None, wallet=None):
|
||||||
|
nonlocal port, auth
|
||||||
|
return callrpc_xmr(port, auth, method, params)
|
||||||
|
return rpc_func
|
||||||
|
|
@ -9,12 +9,15 @@ import json
|
|||||||
import hashlib
|
import hashlib
|
||||||
from .contrib.segwit_addr import bech32_decode, convertbits, bech32_encode
|
from .contrib.segwit_addr import bech32_decode, convertbits, bech32_encode
|
||||||
|
|
||||||
|
OP_1 = 0x51
|
||||||
|
OP_16 = 0x60
|
||||||
COIN = 100000000
|
COIN = 100000000
|
||||||
DCOIN = decimal.Decimal(COIN)
|
DCOIN = decimal.Decimal(COIN)
|
||||||
|
|
||||||
|
|
||||||
def makeInt(v):
|
def assert_cond(v, err='Bad opcode'):
|
||||||
return int(dquantize(decimal.Decimal(v) * DCOIN).quantize(decimal.Decimal(1)))
|
if not v:
|
||||||
|
raise ValueError(err)
|
||||||
|
|
||||||
|
|
||||||
def format8(i):
|
def format8(i):
|
||||||
@ -188,3 +191,105 @@ def DeserialiseNum(b, o=0):
|
|||||||
if b[o + nb - 1] & 0x80:
|
if b[o + nb - 1] & 0x80:
|
||||||
return -(v & ~(0x80 << (8 * (nb - 1))))
|
return -(v & ~(0x80 << (8 * (nb - 1))))
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
def decodeScriptNum(script_bytes, o):
|
||||||
|
v = 0
|
||||||
|
num_len = script_bytes[o]
|
||||||
|
if num_len >= OP_1 and num_len <= OP_16:
|
||||||
|
return((num_len - OP_1) + 1, 1)
|
||||||
|
|
||||||
|
if num_len > 4:
|
||||||
|
raise ValueError('Bad scriptnum length') # Max 4 bytes
|
||||||
|
if num_len + o >= len(script_bytes):
|
||||||
|
raise ValueError('Bad script length')
|
||||||
|
o += 1
|
||||||
|
for i in range(num_len):
|
||||||
|
b = script_bytes[o + i]
|
||||||
|
# Negative flag set in last byte, if num is positive and > 0x80 an extra 0x00 byte will be appended
|
||||||
|
if i == num_len - 1 and b & 0x80:
|
||||||
|
b &= (~(0x80) & 0xFF)
|
||||||
|
v += int(b) << 8 * i
|
||||||
|
v *= -1
|
||||||
|
else:
|
||||||
|
v += int(b) << 8 * i
|
||||||
|
return(v, 1 + num_len)
|
||||||
|
|
||||||
|
|
||||||
|
def getCompactSizeLen(v):
|
||||||
|
# Compact Size
|
||||||
|
if v < 253:
|
||||||
|
return 1
|
||||||
|
if v < 0xffff: # USHRT_MAX
|
||||||
|
return 3
|
||||||
|
if v < 0xffffffff: # UINT_MAX
|
||||||
|
return 5
|
||||||
|
if v < 0xffffffffffffffff: # UINT_MAX
|
||||||
|
return 9
|
||||||
|
raise ValueError('Value too large')
|
||||||
|
|
||||||
|
|
||||||
|
def make_int(v, precision=8, r=0): # r = 0, no rounding, fail, r > 0 round up, r < 0 floor
|
||||||
|
if type(v) == float:
|
||||||
|
v = str(v)
|
||||||
|
elif type(v) == int:
|
||||||
|
return v * 10 ** precision
|
||||||
|
|
||||||
|
ep = 10 ** precision
|
||||||
|
have_dp = False
|
||||||
|
rv = 0
|
||||||
|
for c in v:
|
||||||
|
if c == '.':
|
||||||
|
rv *= ep
|
||||||
|
have_dp = True
|
||||||
|
continue
|
||||||
|
if not c.isdigit():
|
||||||
|
raise ValueError('Invalid char')
|
||||||
|
if have_dp:
|
||||||
|
ep //= 10
|
||||||
|
if ep <= 0:
|
||||||
|
if r == 0:
|
||||||
|
raise ValueError('Mantissa too long')
|
||||||
|
if r > 0:
|
||||||
|
# Round up
|
||||||
|
if int(c) > 4:
|
||||||
|
rv += 1
|
||||||
|
break
|
||||||
|
|
||||||
|
rv += ep * int(c)
|
||||||
|
else:
|
||||||
|
rv = rv * 10 + int(c)
|
||||||
|
if not have_dp:
|
||||||
|
rv *= ep
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
def validate_amount(amount, precision=8):
|
||||||
|
str_amount = str(amount)
|
||||||
|
has_decimal = False
|
||||||
|
for c in str_amount:
|
||||||
|
if c == '.' and not has_decimal:
|
||||||
|
has_decimal = True
|
||||||
|
continue
|
||||||
|
if not c.isdigit():
|
||||||
|
raise ValueError('Invalid amount')
|
||||||
|
|
||||||
|
ar = str_amount.split('.')
|
||||||
|
if len(ar) > 1 and len(ar[1]) > precision:
|
||||||
|
raise ValueError('Too many decimal places in amount {}'.format(str_amount))
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def format_amount(i, display_precision, precision=None):
|
||||||
|
if precision is None:
|
||||||
|
precision = display_precision
|
||||||
|
ep = 10 ** precision
|
||||||
|
n = abs(i)
|
||||||
|
quotient = n // ep
|
||||||
|
remainder = n % ep
|
||||||
|
if display_precision != precision:
|
||||||
|
remainder %= (10 ** display_precision)
|
||||||
|
rv = '{}.{:0>{prec}}'.format(quotient, remainder, prec=display_precision)
|
||||||
|
if i < 0:
|
||||||
|
rv = '-' + rv
|
||||||
|
return rv
|
||||||
|
17
basicswap/util_xmr.py
Normal file
17
basicswap/util_xmr.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import xmrswap.contrib.Keccak as Keccak
|
||||||
|
from .contrib.MoneroPy.base58 import encode as xmr_b58encode
|
||||||
|
|
||||||
|
|
||||||
|
def cn_fast_hash(s):
|
||||||
|
k = Keccak.Keccak()
|
||||||
|
return k.Keccak((len(s) * 8, s.hex()), 1088, 512, 32 * 8, False).lower() # r = bitrate = 1088, c = capacity, n = output length in bits
|
||||||
|
|
||||||
|
|
||||||
|
def encode_address(view_point, spend_point, version=18):
|
||||||
|
buf = bytes((version,)) + spend_point + view_point
|
||||||
|
h = cn_fast_hash(buf)
|
||||||
|
buf = buf + bytes.fromhex(h[0: 8])
|
||||||
|
|
||||||
|
return xmr_b58encode(buf.hex())
|
1
setup.py
1
setup.py
@ -29,6 +29,7 @@ setuptools.setup(
|
|||||||
"sqlalchemy",
|
"sqlalchemy",
|
||||||
"python-gnupg",
|
"python-gnupg",
|
||||||
"Jinja2",
|
"Jinja2",
|
||||||
|
"requests",
|
||||||
],
|
],
|
||||||
entry_points={
|
entry_points={
|
||||||
"console_scripts": [
|
"console_scripts": [
|
||||||
|
@ -4,6 +4,7 @@ import tests.basicswap.test_other as test_other
|
|||||||
import tests.basicswap.test_prepare as test_prepare
|
import tests.basicswap.test_prepare as test_prepare
|
||||||
import tests.basicswap.test_run as test_run
|
import tests.basicswap.test_run as test_run
|
||||||
import tests.basicswap.test_reload as test_reload
|
import tests.basicswap.test_reload as test_reload
|
||||||
|
import tests.basicswap.test_xmr as test_xmr
|
||||||
|
|
||||||
|
|
||||||
def test_suite():
|
def test_suite():
|
||||||
@ -12,5 +13,6 @@ def test_suite():
|
|||||||
suite.addTests(loader.loadTestsFromModule(test_prepare))
|
suite.addTests(loader.loadTestsFromModule(test_prepare))
|
||||||
suite.addTests(loader.loadTestsFromModule(test_run))
|
suite.addTests(loader.loadTestsFromModule(test_run))
|
||||||
suite.addTests(loader.loadTestsFromModule(test_reload))
|
suite.addTests(loader.loadTestsFromModule(test_reload))
|
||||||
|
suite.addTests(loader.loadTestsFromModule(test_xmr))
|
||||||
|
|
||||||
return suite
|
return suite
|
||||||
|
14
tests/basicswap/common.py
Normal file
14
tests/basicswap/common.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2020 tecnovert
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
def checkForks(ro):
|
||||||
|
if 'bip9_softforks' in ro:
|
||||||
|
assert(ro['bip9_softforks']['csv']['status'] == 'active')
|
||||||
|
assert(ro['bip9_softforks']['segwit']['status'] == 'active')
|
||||||
|
else:
|
||||||
|
assert(ro['softforks']['csv']['active'])
|
||||||
|
assert(ro['softforks']['segwit']['active'])
|
@ -9,7 +9,7 @@ import unittest
|
|||||||
from basicswap.util import (
|
from basicswap.util import (
|
||||||
SerialiseNum,
|
SerialiseNum,
|
||||||
DeserialiseNum,
|
DeserialiseNum,
|
||||||
makeInt,
|
make_int,
|
||||||
format8,
|
format8,
|
||||||
)
|
)
|
||||||
from basicswap.basicswap import (
|
from basicswap.basicswap import (
|
||||||
@ -57,19 +57,28 @@ class Test(unittest.TestCase):
|
|||||||
decoded = decodeSequence(encoded)
|
decoded = decodeSequence(encoded)
|
||||||
assert(decoded == blocks_val)
|
assert(decoded == blocks_val)
|
||||||
|
|
||||||
def test_makeInt(self):
|
def test_make_int(self):
|
||||||
def test_case(vs, vf, expect_int):
|
def test_case(vs, vf, expect_int):
|
||||||
assert(makeInt(vs) == expect_int)
|
i = make_int(vs)
|
||||||
assert(makeInt(vf) == expect_int)
|
assert(i == expect_int and isinstance(i, int))
|
||||||
vs_out = format8(makeInt(vs))
|
i = make_int(vf)
|
||||||
|
assert(i == expect_int and isinstance(i, int))
|
||||||
|
vs_out = format_amount(i, 8)
|
||||||
# Strip
|
# Strip
|
||||||
for i in range(7):
|
for i in range(7):
|
||||||
if vs_out[-1] == '0':
|
if vs_out[-1] == '0':
|
||||||
vs_out = vs_out[:-1]
|
vs_out = vs_out[:-1]
|
||||||
|
if '.' in vs:
|
||||||
assert(vs_out == vs)
|
assert(vs_out == vs)
|
||||||
|
else:
|
||||||
|
assert(vs_out[:-2] == vs)
|
||||||
|
test_case('0', 0, 0)
|
||||||
|
test_case('1', 1, 100000000)
|
||||||
|
test_case('10', 10, 1000000000)
|
||||||
test_case('0.00899999', 0.00899999, 899999)
|
test_case('0.00899999', 0.00899999, 899999)
|
||||||
test_case('899999.0', 899999.0, 89999900000000)
|
test_case('899999.0', 899999.0, 89999900000000)
|
||||||
test_case('899999.00899999', 899999.00899999, 89999900899999)
|
test_case('899999.00899999', 899999.00899999, 89999900899999)
|
||||||
|
test_case('0.0', 0.0, 0)
|
||||||
test_case('1.0', 1.0, 100000000)
|
test_case('1.0', 1.0, 100000000)
|
||||||
test_case('1.1', 1.1, 110000000)
|
test_case('1.1', 1.1, 110000000)
|
||||||
test_case('1.2', 1.2, 120000000)
|
test_case('1.2', 1.2, 120000000)
|
||||||
@ -79,6 +88,52 @@ class Test(unittest.TestCase):
|
|||||||
test_case('0.123', 0.123, 12300000)
|
test_case('0.123', 0.123, 12300000)
|
||||||
test_case('123000.000123', 123000.000123, 12300000012300)
|
test_case('123000.000123', 123000.000123, 12300000012300)
|
||||||
|
|
||||||
|
try:
|
||||||
|
make_int('0.123456789')
|
||||||
|
assert(False)
|
||||||
|
except Exception as e:
|
||||||
|
assert(str(e) == 'Mantissa too long')
|
||||||
|
validate_amount('0.12345678')
|
||||||
|
|
||||||
|
# floor
|
||||||
|
assert(make_int('0.123456789', r=-1) == 12345678)
|
||||||
|
# Round up
|
||||||
|
assert(make_int('0.123456789', r=1) == 12345679)
|
||||||
|
|
||||||
|
def test_make_int12(self):
|
||||||
|
def test_case(vs, vf, expect_int):
|
||||||
|
i = make_int(vs, 12)
|
||||||
|
assert(i == expect_int and isinstance(i, int))
|
||||||
|
i = make_int(vf, 12)
|
||||||
|
assert(i == expect_int and isinstance(i, int))
|
||||||
|
vs_out = format_amount(i, 12)
|
||||||
|
# Strip
|
||||||
|
for i in range(7):
|
||||||
|
if vs_out[-1] == '0':
|
||||||
|
vs_out = vs_out[:-1]
|
||||||
|
if '.' in vs:
|
||||||
|
assert(vs_out == vs)
|
||||||
|
else:
|
||||||
|
assert(vs_out[:-2] == vs)
|
||||||
|
test_case('0.123456789', 0.123456789, 123456789000)
|
||||||
|
test_case('0.123456789123', 0.123456789123, 123456789123)
|
||||||
|
try:
|
||||||
|
make_int('0.1234567891234', 12)
|
||||||
|
assert(False)
|
||||||
|
except Exception as e:
|
||||||
|
assert(str(e) == 'Mantissa too long')
|
||||||
|
validate_amount('0.123456789123', 12)
|
||||||
|
try:
|
||||||
|
validate_amount('0.1234567891234', 12)
|
||||||
|
assert(False)
|
||||||
|
except Exception as e:
|
||||||
|
assert('Too many decimal places' in str(e))
|
||||||
|
try:
|
||||||
|
validate_amount(0.1234567891234, 12)
|
||||||
|
assert(False)
|
||||||
|
except Exception as e:
|
||||||
|
assert('Too many decimal places' in str(e))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -48,6 +48,9 @@ from basicswap.contrib.key import (
|
|||||||
from basicswap.http_server import (
|
from basicswap.http_server import (
|
||||||
HttpThread,
|
HttpThread,
|
||||||
)
|
)
|
||||||
|
from tests.basicswap.common import (
|
||||||
|
checkForks,
|
||||||
|
)
|
||||||
from bin.basicswap_run import startDaemon
|
from bin.basicswap_run import startDaemon
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
@ -205,15 +208,6 @@ def run_loop(self):
|
|||||||
btcRpc('generatetoaddress 1 {}'.format(self.btc_addr))
|
btcRpc('generatetoaddress 1 {}'.format(self.btc_addr))
|
||||||
|
|
||||||
|
|
||||||
def checkForks(ro):
|
|
||||||
if 'bip9_softforks' in ro:
|
|
||||||
assert(ro['bip9_softforks']['csv']['status'] == 'active')
|
|
||||||
assert(ro['bip9_softforks']['segwit']['status'] == 'active')
|
|
||||||
else:
|
|
||||||
assert(ro['softforks']['csv']['active'])
|
|
||||||
assert(ro['softforks']['segwit']['active'])
|
|
||||||
|
|
||||||
|
|
||||||
class Test(unittest.TestCase):
|
class Test(unittest.TestCase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
246
tests/basicswap/test_xmr.py
Normal file
246
tests/basicswap/test_xmr.py
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2020 tecnovert
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import shutil
|
||||||
|
import time
|
||||||
|
import signal
|
||||||
|
import threading
|
||||||
|
from urllib.request import urlopen
|
||||||
|
from coincurve.ecdsaotves import (
|
||||||
|
ecdsaotves_enc_sign,
|
||||||
|
ecdsaotves_enc_verify,
|
||||||
|
ecdsaotves_dec_sig,
|
||||||
|
ecdsaotves_rec_enc_key)
|
||||||
|
from coincurve.dleag import (
|
||||||
|
dleag_prove,
|
||||||
|
dleag_verify)
|
||||||
|
|
||||||
|
import basicswap.config as cfg
|
||||||
|
from basicswap.basicswap import (
|
||||||
|
BasicSwap,
|
||||||
|
Coins,
|
||||||
|
SwapTypes,
|
||||||
|
BidStates,
|
||||||
|
TxStates,
|
||||||
|
SEQUENCE_LOCK_BLOCKS,
|
||||||
|
)
|
||||||
|
from basicswap.util import (
|
||||||
|
COIN,
|
||||||
|
toWIF,
|
||||||
|
dumpje,
|
||||||
|
)
|
||||||
|
from basicswap.rpc import (
|
||||||
|
callrpc_cli,
|
||||||
|
waitForRPC,
|
||||||
|
)
|
||||||
|
from basicswap.contrib.key import (
|
||||||
|
ECKey,
|
||||||
|
)
|
||||||
|
from basicswap.http_server import (
|
||||||
|
HttpThread,
|
||||||
|
)
|
||||||
|
from bin.basicswap_run import startDaemon
|
||||||
|
|
||||||
|
logger = logging.getLogger()
|
||||||
|
logger.level = logging.DEBUG
|
||||||
|
if not len(logger.handlers):
|
||||||
|
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||||
|
|
||||||
|
NUM_NODES = 3
|
||||||
|
BASE_PORT = 14792
|
||||||
|
BASE_RPC_PORT = 19792
|
||||||
|
BASE_ZMQ_PORT = 20792
|
||||||
|
PREFIX_SECRET_KEY_REGTEST = 0x2e
|
||||||
|
TEST_HTML_PORT = 1800
|
||||||
|
stop_test = False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def prepareOtherDir(datadir, nodeId, conf_file='litecoin.conf'):
|
||||||
|
node_dir = os.path.join(datadir, str(nodeId))
|
||||||
|
if not os.path.exists(node_dir):
|
||||||
|
os.makedirs(node_dir)
|
||||||
|
filePath = os.path.join(node_dir, conf_file)
|
||||||
|
|
||||||
|
with open(filePath, 'w+') as fp:
|
||||||
|
fp.write('regtest=1\n')
|
||||||
|
fp.write('[regtest]\n')
|
||||||
|
fp.write('port=' + str(BASE_PORT + nodeId) + '\n')
|
||||||
|
fp.write('rpcport=' + str(BASE_RPC_PORT + nodeId) + '\n')
|
||||||
|
|
||||||
|
fp.write('daemon=0\n')
|
||||||
|
fp.write('printtoconsole=0\n')
|
||||||
|
fp.write('server=1\n')
|
||||||
|
fp.write('discover=0\n')
|
||||||
|
fp.write('listenonion=0\n')
|
||||||
|
fp.write('bind=127.0.0.1\n')
|
||||||
|
fp.write('findpeers=0\n')
|
||||||
|
fp.write('debug=1\n')
|
||||||
|
fp.write('debugexclude=libevent\n')
|
||||||
|
fp.write('fallbackfee=0.0002\n')
|
||||||
|
|
||||||
|
fp.write('acceptnonstdtxn=0\n')
|
||||||
|
|
||||||
|
|
||||||
|
def prepareDir(datadir, nodeId, network_key, network_pubkey):
|
||||||
|
node_dir = os.path.join(datadir, str(nodeId))
|
||||||
|
if not os.path.exists(node_dir):
|
||||||
|
os.makedirs(node_dir)
|
||||||
|
filePath = os.path.join(node_dir, 'particl.conf')
|
||||||
|
|
||||||
|
with open(filePath, 'w+') as fp:
|
||||||
|
fp.write('regtest=1\n')
|
||||||
|
fp.write('[regtest]\n')
|
||||||
|
fp.write('port=' + str(BASE_PORT + nodeId) + '\n')
|
||||||
|
fp.write('rpcport=' + str(BASE_RPC_PORT + nodeId) + '\n')
|
||||||
|
|
||||||
|
fp.write('daemon=0\n')
|
||||||
|
fp.write('printtoconsole=0\n')
|
||||||
|
fp.write('server=1\n')
|
||||||
|
fp.write('discover=0\n')
|
||||||
|
fp.write('listenonion=0\n')
|
||||||
|
fp.write('bind=127.0.0.1\n')
|
||||||
|
fp.write('findpeers=0\n')
|
||||||
|
fp.write('debug=1\n')
|
||||||
|
fp.write('debugexclude=libevent\n')
|
||||||
|
fp.write('zmqpubsmsg=tcp://127.0.0.1:' + str(BASE_ZMQ_PORT + nodeId) + '\n')
|
||||||
|
|
||||||
|
fp.write('acceptnonstdtxn=0\n')
|
||||||
|
fp.write('minstakeinterval=5\n')
|
||||||
|
|
||||||
|
for i in range(0, NUM_NODES):
|
||||||
|
if nodeId == i:
|
||||||
|
continue
|
||||||
|
fp.write('addnode=127.0.0.1:%d\n' % (BASE_PORT + i))
|
||||||
|
|
||||||
|
if nodeId < 2:
|
||||||
|
fp.write('spentindex=1\n')
|
||||||
|
fp.write('txindex=1\n')
|
||||||
|
|
||||||
|
basicswap_dir = os.path.join(datadir, str(nodeId), 'basicswap')
|
||||||
|
if not os.path.exists(basicswap_dir):
|
||||||
|
os.makedirs(basicswap_dir)
|
||||||
|
|
||||||
|
ltcdatadir = os.path.join(datadir, str(LTC_NODE))
|
||||||
|
btcdatadir = os.path.join(datadir, str(BTC_NODE))
|
||||||
|
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
|
||||||
|
settings = {
|
||||||
|
'zmqhost': 'tcp://127.0.0.1',
|
||||||
|
'zmqport': BASE_ZMQ_PORT + nodeId,
|
||||||
|
'htmlhost': 'localhost',
|
||||||
|
'htmlport': 12700 + nodeId,
|
||||||
|
'network_key': network_key,
|
||||||
|
'network_pubkey': network_pubkey,
|
||||||
|
'chainclients': {
|
||||||
|
'particl': {
|
||||||
|
'connection_type': 'rpc',
|
||||||
|
'manage_daemon': False,
|
||||||
|
'rpcport': BASE_RPC_PORT + nodeId,
|
||||||
|
'datadir': node_dir,
|
||||||
|
'bindir': cfg.PARTICL_BINDIR,
|
||||||
|
'blocks_confirmed': 2, # Faster testing
|
||||||
|
},
|
||||||
|
'litecoin': {
|
||||||
|
'connection_type': 'rpc',
|
||||||
|
'manage_daemon': False,
|
||||||
|
'rpcport': BASE_RPC_PORT + LTC_NODE,
|
||||||
|
'datadir': ltcdatadir,
|
||||||
|
'bindir': cfg.LITECOIN_BINDIR,
|
||||||
|
# 'use_segwit': True,
|
||||||
|
},
|
||||||
|
'bitcoin': {
|
||||||
|
'connection_type': 'rpc',
|
||||||
|
'manage_daemon': False,
|
||||||
|
'rpcport': BASE_RPC_PORT + BTC_NODE,
|
||||||
|
'datadir': btcdatadir,
|
||||||
|
'bindir': cfg.BITCOIN_BINDIR,
|
||||||
|
'use_segwit': True,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'check_progress_seconds': 2,
|
||||||
|
'check_watched_seconds': 4,
|
||||||
|
'check_expired_seconds': 60,
|
||||||
|
'check_events_seconds': 1,
|
||||||
|
'min_delay_auto_accept': 1,
|
||||||
|
'max_delay_auto_accept': 5
|
||||||
|
}
|
||||||
|
with open(settings_path, 'w') as fp:
|
||||||
|
json.dump(settings, fp, indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
def partRpc(cmd, node_id=0):
|
||||||
|
return callrpc_cli(cfg.PARTICL_BINDIR, os.path.join(cfg.TEST_DATADIRS, str(node_id)), 'regtest', cmd, cfg.PARTICL_CLI)
|
||||||
|
|
||||||
|
|
||||||
|
def btcRpc(cmd):
|
||||||
|
return callrpc_cli(cfg.BITCOIN_BINDIR, os.path.join(cfg.TEST_DATADIRS, str(BTC_NODE)), 'regtest', cmd, cfg.BITCOIN_CLI)
|
||||||
|
|
||||||
|
|
||||||
|
def signal_handler(sig, frame):
|
||||||
|
global stop_test
|
||||||
|
print('signal {} detected.'.format(sig))
|
||||||
|
stop_test = True
|
||||||
|
|
||||||
|
|
||||||
|
def run_loop(self):
|
||||||
|
while not stop_test:
|
||||||
|
time.sleep(1)
|
||||||
|
for c in self.swap_clients:
|
||||||
|
c.update()
|
||||||
|
btcRpc('generatetoaddress 1 {}'.format(self.btc_addr))
|
||||||
|
|
||||||
|
|
||||||
|
def checkForks(ro):
|
||||||
|
if 'bip9_softforks' in ro:
|
||||||
|
assert(ro['bip9_softforks']['csv']['status'] == 'active')
|
||||||
|
assert(ro['bip9_softforks']['segwit']['status'] == 'active')
|
||||||
|
else:
|
||||||
|
assert(ro['softforks']['csv']['active'])
|
||||||
|
assert(ro['softforks']['segwit']['active'])
|
||||||
|
|
||||||
|
|
||||||
|
class Test(unittest.TestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super(Test, cls).setUpClass()
|
||||||
|
|
||||||
|
cls.swap_clients = []
|
||||||
|
cls.xmr_daemons = []
|
||||||
|
cls.xmr_wallet_auth = []
|
||||||
|
|
||||||
|
cls.part_stakelimit = 0
|
||||||
|
cls.xmr_addr = None
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
|
cls.update_thread = threading.Thread(target=run_loop, args=(cls,))
|
||||||
|
cls.update_thread.start()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
global stop_test
|
||||||
|
logging.info('Finalising')
|
||||||
|
stop_test = True
|
||||||
|
cls.update_thread.join()
|
||||||
|
|
||||||
|
super(Test, cls).tearDownClass()
|
||||||
|
|
||||||
|
def test_01_part_xmr(self):
|
||||||
|
logging.info('---------- Test PART to XMR')
|
||||||
|
#swap_clients = self.swap_clients
|
||||||
|
|
||||||
|
#offer_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 100 * COIN, 0.5 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
Loading…
Reference in New Issue
Block a user