parent
4636d31ea9
commit
5d84d54e6f
33 changed files with 6838 additions and 29 deletions
@ -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) |
@ -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,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) |
@ -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) |
@ -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 |
@ -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 |
File diff suppressed because it is too large
Load Diff
@ -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) |
@ -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 |
@ -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 |
@ -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)) |
@ -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) |
@ -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() |
@ -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() |
@ -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 |
@ -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 |
@ -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'] |
@ -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 |
||||||
|
|
@ -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()) |
@ -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']) |
@ -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