diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index c697843..7856288 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -38,23 +38,27 @@ from . import __version__ from .rpc_xmr import make_xmr_rpc2_func from .util import ( TemporaryError, - pubkeyToAddress, format_amount, format_timestamp, - encodeAddress, - decodeAddress, DeserialiseNum, - decodeWif, - toWIF, - getKeyID, make_int, - getP2SHScriptForHash, - getP2WSH, ensure, ) +from .util.script import ( + getP2WSH, + getP2SHScriptForHash, +) +from .util.address import ( + toWIF, + getKeyID, + decodeWif, + decodeAddress, + encodeAddress, + pubkeyToAddress, +) from .chainparams import ( - chainparams, Coins, + chainparams, ) from .script import ( OpCodes, diff --git a/basicswap/basicswap_util.py b/basicswap/basicswap_util.py index d4598f1..1615c2d 100644 --- a/basicswap/basicswap_util.py +++ b/basicswap/basicswap_util.py @@ -8,9 +8,9 @@ import struct import hashlib from enum import IntEnum, auto -from .util import ( - encodeAddress, +from .util.address import ( decodeAddress, + encodeAddress, ) from .chainparams import ( chainparams, diff --git a/basicswap/interface_btc.py b/basicswap/interface_btc.py index ac5919f..db9fba6 100644 --- a/basicswap/interface_btc.py +++ b/basicswap/interface_btc.py @@ -16,17 +16,26 @@ from basicswap.contrib.test_framework import segwit_addr from .util import ( dumpj, - toWIF, ensure, make_int, + b2h, i2b, b2i, i2h) +from .util.ecc import ( + ep, + pointToCPK, CPKToPoint, + getSecretInt) +from .util.script import ( + decodeScriptNum, + getCompactSizeLen, + SerialiseNumCompact, + getWitnessElementLen, +) +from .util.address import ( + toWIF, b58encode, decodeWif, decodeAddress, - decodeScriptNum, pubkeyToAddress, - getCompactSizeLen, - SerialiseNumCompact, - getWitnessElementLen) +) from coincurve.keys import ( PrivateKey, PublicKey) @@ -38,12 +47,6 @@ from coincurve.ecdsaotves import ( ecdsaotves_dec_sig, ecdsaotves_rec_enc_key) -from .ecc_util import ( - ep, - pointToCPK, CPKToPoint, - getSecretInt, - b2h, i2b, b2i, i2h) - from .contrib.test_framework.messages import ( COIN, COutPoint, diff --git a/basicswap/interface_part.py b/basicswap/interface_part.py index 4885f32..9ceff79 100644 --- a/basicswap/interface_part.py +++ b/basicswap/interface_part.py @@ -16,17 +16,20 @@ from .contrib.test_framework.script import ( OP_0, OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG ) -from .ecc_util import i2b - from .util import ( - toWIF, + i2b, ensure, make_int, - getP2WSH, TemporaryError, +) +from .util.script import ( + getP2WSH, getCompactSizeLen, - encodeStealthAddress, - getWitnessElementLen) + getWitnessElementLen, +) +from .util.address import ( + toWIF, + encodeStealthAddress) from .chainparams import Coins, chainparams from .interface_btc import BTCInterface diff --git a/basicswap/interface_xmr.py b/basicswap/interface_xmr.py index 30313e3..45cdea9 100644 --- a/basicswap/interface_xmr.py +++ b/basicswap/interface_xmr.py @@ -31,7 +31,7 @@ from .rpc_xmr import ( make_xmr_rpc_func, make_xmr_rpc2_func, make_xmr_wallet_rpc_func) -from .ecc_util import ( +from .util import ( b2i, b2h) from .chainparams import CoinInterface, Coins diff --git a/basicswap/util.py b/basicswap/util.py deleted file mode 100644 index 03ed30a..0000000 --- a/basicswap/util.py +++ /dev/null @@ -1,350 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2018-2021 tecnovert -# Distributed under the MIT software license, see the accompanying -# file LICENSE or http://www.opensource.org/licenses/mit-license.php. - - -import json -import time -import struct -import decimal -import hashlib - -from .script import OpCodes -from .contrib.segwit_addr import bech32_decode, convertbits, bech32_encode - - -COIN = 100000000 - - -decimal_ctx = decimal.Context() -decimal_ctx.prec = 20 - - -class TemporaryError(ValueError): - pass - - -def ensure(v, err_string): - if not v: - raise ValueError(err_string) - - -def toBool(s) -> bool: - return s.lower() in ["1", "true"] - - -def jsonDecimal(obj): - if isinstance(obj, decimal.Decimal): - return str(obj) - raise TypeError - - -def dumpj(jin, indent=4): - return json.dumps(jin, indent=indent, default=jsonDecimal) - - -def dumpje(jin): - return json.dumps(jin, default=jsonDecimal).replace('"', '\\"') - - -__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' - - -def b58decode(v, length=None): - long_value = 0 - for (i, c) in enumerate(v[::-1]): - ofs = __b58chars.find(c) - if ofs < 0: - return None - long_value += ofs * (58**i) - result = bytes() - while long_value >= 256: - div, mod = divmod(long_value, 256) - result = bytes((mod,)) + result - long_value = div - result = bytes((long_value,)) + result - nPad = 0 - for c in v: - if c == __b58chars[0]: - nPad += 1 - else: - break - pad = bytes((0,)) * nPad - result = pad + result - if length is not None and len(result) != length: - return None - return result - - -def b58encode(v): - long_value = 0 - for (i, c) in enumerate(v[::-1]): - long_value += (256**i) * c - - result = '' - while long_value >= 58: - div, mod = divmod(long_value, 58) - result = __b58chars[mod] + result - long_value = div - result = __b58chars[long_value] + result - - # leading 0-bytes in the input become leading-1s - nPad = 0 - for c in v: - if c == 0: - nPad += 1 - else: - break - return (__b58chars[0] * nPad) + result - - -def decodeWif(encoded_key): - key = b58decode(encoded_key)[1:-4] - if len(key) == 33: - return key[:-1] - return key - - -def toWIF(prefix_byte, b, compressed=True): - b = bytes((prefix_byte,)) + b - if compressed: - b += bytes((0x01,)) - b += hashlib.sha256(hashlib.sha256(b).digest()).digest()[:4] - return b58encode(b) - - -def bech32Decode(hrp, addr): - hrpgot, data = bech32_decode(addr) - if hrpgot != hrp: - return None - decoded = convertbits(data, 5, 8, False) - if decoded is None or len(decoded) < 2 or len(decoded) > 40: - return None - return bytes(decoded) - - -def bech32Encode(hrp, data): - ret = bech32_encode(hrp, convertbits(data, 8, 5)) - if bech32Decode(hrp, ret) is None: - return None - return ret - - -def decodeAddress(address_str): - b58_addr = b58decode(address_str) - if b58_addr is not None: - address = b58_addr[:-4] - checksum = b58_addr[-4:] - assert(hashlib.sha256(hashlib.sha256(address).digest()).digest()[:4] == checksum), 'Checksum mismatch' - return b58_addr[:-4] - return None - - -def encodeAddress(address): - checksum = hashlib.sha256(hashlib.sha256(address).digest()).digest() - return b58encode(address + checksum[0:4]) - - -def getKeyID(bytes): - data = hashlib.sha256(bytes).digest() - return hashlib.new("ripemd160", data).digest() - - -def pubkeyToAddress(prefix, pubkey): - return encodeAddress(bytes((prefix,)) + getKeyID(pubkey)) - - -def SerialiseNum(n): - if n == 0: - return bytes((0x00,)) - if n > 0 and n <= 16: - return bytes((0x50 + n,)) - rv = bytearray() - neg = n < 0 - absvalue = -n if neg else n - while(absvalue): - rv.append(absvalue & 0xff) - absvalue >>= 8 - if rv[-1] & 0x80: - rv.append(0x80 if neg else 0) - elif neg: - rv[-1] |= 0x80 - return bytes((len(rv),)) + rv - - -def DeserialiseNum(b, o=0) -> int: - if b[o] == 0: - return 0 - if b[o] > 0x50 and b[o] <= 0x50 + 16: - return b[o] - 0x50 - v = 0 - nb = b[o] - o += 1 - for i in range(0, nb): - v |= b[o + i] << (8 * i) - # If the input vector's most significant byte is 0x80, remove it from the result's msb and return a negative. - if b[o + nb - 1] & 0x80: - return -(v & ~(0x80 << (8 * (nb - 1)))) - return v - - -def decodeScriptNum(script_bytes, o): - v = 0 - num_len = script_bytes[o] - if num_len >= OpCodes.OP_1 and num_len <= OpCodes.OP_16: - return((num_len - OpCodes.OP_1) + 1, 1) - - if num_len > 4: - raise ValueError('Bad scriptnum length') # Max 4 bytes - if num_len + o >= len(script_bytes): - raise ValueError('Bad script length') - o += 1 - for i in range(num_len): - b = script_bytes[o + i] - # Negative flag set in last byte, if num is positive and > 0x80 an extra 0x00 byte will be appended - if i == num_len - 1 and b & 0x80: - b &= (~(0x80) & 0xFF) - v += int(b) << 8 * i - v *= -1 - else: - v += int(b) << 8 * i - return(v, 1 + num_len) - - -def getCompactSizeLen(v): - # Compact Size - if v < 253: - return 1 - if v <= 0xffff: # USHRT_MAX - return 3 - if v <= 0xffffffff: # UINT_MAX - return 5 - if v <= 0xffffffffffffffff: # UINT_MAX - return 9 - raise ValueError('Value too large') - - -def getWitnessElementLen(v): - return getCompactSizeLen(v) + v - - -def SerialiseNumCompact(v): - if v < 253: - return bytes((v,)) - if v <= 0xffff: # USHRT_MAX - return struct.pack(" 0 round up, r < 0 floor - if type(v) == float: - v = float_to_str(v) - elif type(v) == int: - return v * 10 ** scale - - sign = 1 - if v[0] == '-': - v = v[1:] - sign = -1 - ep = 10 ** scale - have_dp = False - rv = 0 - for c in v: - if c == '.': - rv *= ep - have_dp = True - continue - if not c.isdigit(): - raise ValueError('Invalid char: ' + c) - if have_dp: - ep //= 10 - if ep <= 0: - if r == 0: - raise ValueError('Mantissa too long') - if r > 0: - # Round up - if int(c) > 4: - rv += 1 - break - rv += ep * int(c) - else: - rv = rv * 10 + int(c) - if not have_dp: - rv *= ep - return rv * sign - - -def validate_amount(amount, scale=8) -> bool: - str_amount = float_to_str(amount) if type(amount) == float else str(amount) - has_decimal = False - for c in str_amount: - if c == '.' and not has_decimal: - has_decimal = True - continue - if not c.isdigit(): - raise ValueError('Invalid amount') - - ar = str_amount.split('.') - if len(ar) > 1 and len(ar[1]) > scale: - raise ValueError('Too many decimal places in amount {}'.format(str_amount)) - return True - - -def format_amount(i, display_scale, scale=None): - if not isinstance(i, int): - raise ValueError('Amount must be an integer.') # Raise error instead of converting as amounts should always be integers - if scale is None: - scale = display_scale - ep = 10 ** scale - n = abs(i) - quotient = n // ep - remainder = n % ep - if display_scale != scale: - remainder %= (10 ** display_scale) - rv = '{}.{:0>{scale}}'.format(quotient, remainder, scale=display_scale) - if i < 0: - rv = '-' + rv - return rv - - -def format_timestamp(value, with_seconds=False): - str_format = '%Y-%m-%d %H:%M' - if with_seconds: - str_format += ':%S' - str_format += ' %Z' - return time.strftime(str_format, time.localtime(value)) - - -def getP2SHScriptForHash(p2sh): - return bytes((OpCodes.OP_HASH160, 0x14)) \ - + p2sh \ - + bytes((OpCodes.OP_EQUAL,)) - - -def getP2WSH(script): - return bytes((OpCodes.OP_0, 0x20)) + hashlib.sha256(script).digest() - - -def encodeStealthAddress(prefix_byte, scan_pubkey, spend_pubkey): - data = bytes((0x00,)) - data += scan_pubkey - data += bytes((0x01,)) - data += spend_pubkey - data += bytes((0x00,)) # number_signatures - unused - data += bytes((0x00,)) # num prefix bits - - b = bytes((prefix_byte,)) + data - b += hashlib.sha256(hashlib.sha256(b).digest()).digest()[:4] - return b58encode(b) diff --git a/basicswap/util/__init__.py b/basicswap/util/__init__.py new file mode 100644 index 0000000..9d7141d --- /dev/null +++ b/basicswap/util/__init__.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2018-2022 tecnovert +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. + + +import json +import time +import decimal + + +COIN = 100000000 + + +decimal_ctx = decimal.Context() +decimal_ctx.prec = 20 + + +class TemporaryError(ValueError): + pass + + +def ensure(v, err_string): + if not v: + raise ValueError(err_string) + + +def toBool(s) -> bool: + return s.lower() in ['1', 'true'] + + +def jsonDecimal(obj): + if isinstance(obj, decimal.Decimal): + return str(obj) + raise TypeError + + +def dumpj(jin, indent=4): + return json.dumps(jin, indent=indent, default=jsonDecimal) + + +def dumpje(jin): + return json.dumps(jin, default=jsonDecimal).replace('"', '\\"') + + +def SerialiseNum(n): + if n == 0: + return bytes((0x00,)) + if n > 0 and n <= 16: + return bytes((0x50 + n,)) + rv = bytearray() + neg = n < 0 + absvalue = -n if neg else n + while(absvalue): + rv.append(absvalue & 0xff) + absvalue >>= 8 + if rv[-1] & 0x80: + rv.append(0x80 if neg else 0) + elif neg: + rv[-1] |= 0x80 + return bytes((len(rv),)) + rv + + +def DeserialiseNum(b, o=0) -> int: + if b[o] == 0: + return 0 + if b[o] > 0x50 and b[o] <= 0x50 + 16: + return b[o] - 0x50 + v = 0 + nb = b[o] + o += 1 + for i in range(0, nb): + v |= b[o + i] << (8 * i) + # If the input vector's most significant byte is 0x80, remove it from the result's msb and return a negative. + if b[o + nb - 1] & 0x80: + return -(v & ~(0x80 << (8 * (nb - 1)))) + return v + + +def float_to_str(f): + # stackoverflow.com/questions/38847690 + d1 = decimal_ctx.create_decimal(repr(f)) + return format(d1, 'f') + + +def make_int(v, scale=8, r=0): # r = 0, no rounding, fail, r > 0 round up, r < 0 floor + if type(v) == float: + v = float_to_str(v) + elif type(v) == int: + return v * 10 ** scale + + sign = 1 + if v[0] == '-': + v = v[1:] + sign = -1 + ep = 10 ** scale + have_dp = False + rv = 0 + for c in v: + if c == '.': + rv *= ep + have_dp = True + continue + if not c.isdigit(): + raise ValueError('Invalid char: ' + c) + if have_dp: + ep //= 10 + if ep <= 0: + if r == 0: + raise ValueError('Mantissa too long') + if r > 0: + # Round up + if int(c) > 4: + rv += 1 + break + rv += ep * int(c) + else: + rv = rv * 10 + int(c) + if not have_dp: + rv *= ep + return rv * sign + + +def validate_amount(amount, scale=8) -> bool: + str_amount = float_to_str(amount) if type(amount) == float else str(amount) + has_decimal = False + for c in str_amount: + if c == '.' and not has_decimal: + has_decimal = True + continue + if not c.isdigit(): + raise ValueError('Invalid amount') + + ar = str_amount.split('.') + if len(ar) > 1 and len(ar[1]) > scale: + raise ValueError('Too many decimal places in amount {}'.format(str_amount)) + return True + + +def format_amount(i, display_scale, scale=None): + if not isinstance(i, int): + raise ValueError('Amount must be an integer.') # Raise error instead of converting as amounts should always be integers + if scale is None: + scale = display_scale + ep = 10 ** scale + n = abs(i) + quotient = n // ep + remainder = n % ep + if display_scale != scale: + remainder %= (10 ** display_scale) + rv = '{}.{:0>{scale}}'.format(quotient, remainder, scale=display_scale) + if i < 0: + rv = '-' + rv + return rv + + +def format_timestamp(value, with_seconds=False): + str_format = '%Y-%m-%d %H:%M' + if with_seconds: + str_format += ':%S' + str_format += ' %Z' + return time.strftime(str_format, time.localtime(value)) + + +def b2i(b) -> int: + # bytes32ToInt + return int.from_bytes(b, byteorder='big') + + +def i2b(i: int) -> bytes: + # intToBytes32 + return i.to_bytes(32, byteorder='big') + + +def b2h(b: bytes) -> str: + return b.hex() + + +def h2b(h: str) -> bytes: + if h.startswith('0x'): + h = h[2:] + return bytes.fromhex(h) + + +def i2h(x): + return b2h(i2b(x)) diff --git a/basicswap/util/address.py b/basicswap/util/address.py new file mode 100644 index 0000000..ee8d3ee --- /dev/null +++ b/basicswap/util/address.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2022 tecnovert +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +import hashlib +from basicswap.contrib.segwit_addr import bech32_decode, convertbits, bech32_encode + + +__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' + + +def b58decode(v, length=None): + long_value = 0 + for (i, c) in enumerate(v[::-1]): + ofs = __b58chars.find(c) + if ofs < 0: + return None + long_value += ofs * (58**i) + result = bytes() + while long_value >= 256: + div, mod = divmod(long_value, 256) + result = bytes((mod,)) + result + long_value = div + result = bytes((long_value,)) + result + nPad = 0 + for c in v: + if c == __b58chars[0]: + nPad += 1 + else: + break + pad = bytes((0,)) * nPad + result = pad + result + if length is not None and len(result) != length: + return None + return result + + +def b58encode(v): + long_value = 0 + for (i, c) in enumerate(v[::-1]): + long_value += (256**i) * c + + result = '' + while long_value >= 58: + div, mod = divmod(long_value, 58) + result = __b58chars[mod] + result + long_value = div + result = __b58chars[long_value] + result + + # leading 0-bytes in the input become leading-1s + nPad = 0 + for c in v: + if c == 0: + nPad += 1 + else: + break + return (__b58chars[0] * nPad) + result + + +def encodeStealthAddress(prefix_byte, scan_pubkey, spend_pubkey): + data = bytes((0x00,)) + data += scan_pubkey + data += bytes((0x01,)) + data += spend_pubkey + data += bytes((0x00,)) # number_signatures - unused + data += bytes((0x00,)) # num prefix bits + + b = bytes((prefix_byte,)) + data + b += hashlib.sha256(hashlib.sha256(b).digest()).digest()[:4] + return b58encode(b) + + +def decodeWif(encoded_key): + key = b58decode(encoded_key)[1:-4] + if len(key) == 33: + return key[:-1] + return key + + +def toWIF(prefix_byte, b, compressed=True): + b = bytes((prefix_byte,)) + b + if compressed: + b += bytes((0x01,)) + b += hashlib.sha256(hashlib.sha256(b).digest()).digest()[:4] + return b58encode(b) + + +def getKeyID(bytes): + data = hashlib.sha256(bytes).digest() + return hashlib.new('ripemd160', data).digest() + + +def bech32Decode(hrp, addr): + hrpgot, data = bech32_decode(addr) + if hrpgot != hrp: + return None + decoded = convertbits(data, 5, 8, False) + if decoded is None or len(decoded) < 2 or len(decoded) > 40: + return None + return bytes(decoded) + + +def bech32Encode(hrp, data): + ret = bech32_encode(hrp, convertbits(data, 8, 5)) + if bech32Decode(hrp, ret) is None: + return None + return ret + + +def decodeAddress(address_str): + b58_addr = b58decode(address_str) + if b58_addr is not None: + address = b58_addr[:-4] + checksum = b58_addr[-4:] + assert(hashlib.sha256(hashlib.sha256(address).digest()).digest()[:4] == checksum), 'Checksum mismatch' + return b58_addr[:-4] + return None + + +def encodeAddress(address): + checksum = hashlib.sha256(hashlib.sha256(address).digest()).digest() + return b58encode(address + checksum[0:4]) + + +def pubkeyToAddress(prefix, pubkey): + return encodeAddress(bytes((prefix,)) + getKeyID(pubkey)) diff --git a/basicswap/ecc_util.py b/basicswap/util/ecc.py similarity index 87% rename from basicswap/ecc_util.py rename to basicswap/util/ecc.py index fad3277..96eb578 100644 --- a/basicswap/ecc_util.py +++ b/basicswap/util/ecc.py @@ -2,11 +2,11 @@ # -*- coding: utf-8 -*- import os -import codecs import hashlib import secrets -from .contrib.ellipticcurve import CurveFp, Point, INFINITY, jacobi_symbol +from basicswap.contrib.ellipticcurve import CurveFp, Point, INFINITY, jacobi_symbol +from . import i2b class ECCParameters(): @@ -37,31 +37,9 @@ def ToDER(P) -> bytes: return bytes((4, )) + int(P.x()).to_bytes(32, byteorder='big') + int(P.y()).to_bytes(32, byteorder='big') -def bytes32ToInt(b) -> int: - return int.from_bytes(b, byteorder='big') - - -def intToBytes32(i: int) -> bytes: - return i.to_bytes(32, byteorder='big') - - -def intToBytes32_le(i: int) -> bytes: - return i.to_bytes(32, byteorder='little') - - -def bytesToHexStr(b: bytes) -> str: - return codecs.encode(b, 'hex').decode('utf-8') - - -def hexStrToBytes(h: str) -> bytes: - if h.startswith('0x'): - h = h[2:] - return bytes.fromhex(h) - - def getSecretBytes() -> bytes: i = 1 + secrets.randbelow(ep.o - 1) - return intToBytes32(i) + return i2b(i) def getSecretInt() -> int: @@ -189,16 +167,6 @@ 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()') diff --git a/basicswap/util/rfc2440.py b/basicswap/util/rfc2440.py new file mode 100644 index 0000000..be7aa1e --- /dev/null +++ b/basicswap/util/rfc2440.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +import hashlib +import secrets + + +def rfc2440_hash_password(password, salt=None): + # Match tor --hash-password + # secret_to_key_rfc2440 + + EXPBIAS = 6 + c = 96 + count = (16 + (c & 15)) << ((c >> 4) + EXPBIAS) + + if salt is None: + salt = secrets.token_bytes(8) + assert(len(salt) == 8) + + hashbytes = salt + password.encode('utf-8') + len_hashbytes = len(hashbytes) + h = hashlib.sha1() + + while count > 0: + if count >= len_hashbytes: + h.update(hashbytes) + count -= len_hashbytes + continue + h.update(hashbytes[:count]) + break + rv = '16:' + salt.hex() + '60' + h.hexdigest() + return rv.upper() diff --git a/basicswap/util/script.py b/basicswap/util/script.py new file mode 100644 index 0000000..d732ef2 --- /dev/null +++ b/basicswap/util/script.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2022 tecnovert +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +import struct +import hashlib +from basicswap.script import OpCodes + + +def decodeScriptNum(script_bytes, o): + v = 0 + num_len = script_bytes[o] + if num_len >= OpCodes.OP_1 and num_len <= OpCodes.OP_16: + return((num_len - OpCodes.OP_1) + 1, 1) + + if num_len > 4: + raise ValueError('Bad scriptnum length') # Max 4 bytes + if num_len + o >= len(script_bytes): + raise ValueError('Bad script length') + o += 1 + for i in range(num_len): + b = script_bytes[o + i] + # Negative flag set in last byte, if num is positive and > 0x80 an extra 0x00 byte will be appended + if i == num_len - 1 and b & 0x80: + b &= (~(0x80) & 0xFF) + v += int(b) << 8 * i + v *= -1 + else: + v += int(b) << 8 * i + return(v, 1 + num_len) + + +def getP2SHScriptForHash(p2sh): + return bytes((OpCodes.OP_HASH160, 0x14)) \ + + p2sh \ + + bytes((OpCodes.OP_EQUAL,)) + + +def getP2WSH(script): + return bytes((OpCodes.OP_0, 0x20)) + hashlib.sha256(script).digest() + + +def SerialiseNumCompact(v): + if v < 253: + return bytes((v,)) + if v <= 0xffff: # USHRT_MAX + return struct.pack("