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