diff --git a/jmbitcoin/jmbitcoin/__init__.py b/jmbitcoin/jmbitcoin/__init__.py index 209bc1d..ac763cc 100644 --- a/jmbitcoin/jmbitcoin/__init__.py +++ b/jmbitcoin/jmbitcoin/__init__.py @@ -1,3 +1,6 @@ +from __future__ import (absolute_import, division, + print_function, unicode_literals) +from builtins import * import coincurve as secp256k1 from jmbitcoin.secp256k1_main import * from jmbitcoin.secp256k1_transaction import * diff --git a/jmbitcoin/jmbitcoin/bci.py b/jmbitcoin/jmbitcoin/bci.py index 6eda124..37ca2a9 100644 --- a/jmbitcoin/jmbitcoin/bci.py +++ b/jmbitcoin/jmbitcoin/bci.py @@ -1,4 +1,7 @@ #!/usr/bin/python +from __future__ import (absolute_import, division, + print_function, unicode_literals) +from builtins import * # noqa: F401 import json, re import random import time diff --git a/jmbitcoin/jmbitcoin/bech32.py b/jmbitcoin/jmbitcoin/bech32.py index 45e65ce..36f3202 100644 --- a/jmbitcoin/jmbitcoin/bech32.py +++ b/jmbitcoin/jmbitcoin/bech32.py @@ -19,7 +19,9 @@ # THE SOFTWARE. """Reference implementation for Bech32 and segwit addresses.""" - +from __future__ import (absolute_import, division, + print_function, unicode_literals) +from builtins import * # noqa: F401 CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" diff --git a/jmbitcoin/jmbitcoin/btscript.py b/jmbitcoin/jmbitcoin/btscript.py index c658c4e..4ef74be 100644 --- a/jmbitcoin/jmbitcoin/btscript.py +++ b/jmbitcoin/jmbitcoin/btscript.py @@ -1,3 +1,6 @@ +from __future__ import (absolute_import, division, + print_function, unicode_literals) +from builtins import * # noqa: F401 #OP codes; disabled commented. # push value diff --git a/jmbitcoin/jmbitcoin/secp256k1_deterministic.py b/jmbitcoin/jmbitcoin/secp256k1_deterministic.py index c1d3436..1407769 100644 --- a/jmbitcoin/jmbitcoin/secp256k1_deterministic.py +++ b/jmbitcoin/jmbitcoin/secp256k1_deterministic.py @@ -1,6 +1,10 @@ +from __future__ import (absolute_import, division, + print_function, unicode_literals) +from builtins import * from jmbitcoin.secp256k1_main import * import hmac import hashlib +import struct # Below code ASSUMES binary inputs and compressed pubkeys MAINNET_PRIVATE = b'\x04\x88\xAD\xE4' @@ -32,29 +36,30 @@ def raw_bip32_ckd(rawtuple, i): hashlib.sha512).digest() if vbytes in PRIVATE: - newkey = add_privkeys(I[:32] + B'\x01', priv, False) + newkey = add_privkeys(I[:32] + b'\x01', priv, False) fingerprint = bin_hash160(privtopub(key, False))[:4] if vbytes in PUBLIC: - newkey = add_pubkeys([privtopub(I[:32] + '\x01', False), key], False) + newkey = add_pubkeys([privtopub(I[:32] + b'\x01', False), key], False) fingerprint = bin_hash160(key)[:4] return (vbytes, depth + 1, fingerprint, i, I[32:], newkey) def bip32_serialize(rawtuple): vbytes, depth, fingerprint, i, chaincode, key = rawtuple - i = encode(i, 256, 4) - chaincode = encode(hash_to_int(chaincode), 256, 32) + if isinstance(i, int): + i = struct.pack(b'>L', i) + chaincode = chaincode keydata = b'\x00' + key[:-1] if vbytes in PRIVATE else key bindata = vbytes + from_int_to_byte( depth % 256) + fingerprint + i + chaincode + keydata - return changebase(bindata + bin_dbl_sha256(bindata)[:4], 256, 58) + return b58encode(bindata + bin_dbl_sha256(bindata)[:4]) def bip32_deserialize(data): - dbin = changebase(data, 58, 256) + dbin = b58decode(data) if bin_dbl_sha256(dbin[:-4])[:4] != dbin[-4:]: raise Exception("Invalid checksum") vbytes = dbin[0:4] - depth = from_byte_to_int(dbin[4]) + depth = dbin[4] fingerprint = dbin[5:9] i = decode(dbin[9:13], 256) chaincode = dbin[13:45] diff --git a/jmbitcoin/jmbitcoin/secp256k1_main.py b/jmbitcoin/jmbitcoin/secp256k1_main.py index fa73fc0..c2a698a 100644 --- a/jmbitcoin/jmbitcoin/secp256k1_main.py +++ b/jmbitcoin/jmbitcoin/secp256k1_main.py @@ -1,96 +1,221 @@ #!/usr/bin/python -from __future__ import print_function +from __future__ import (absolute_import, division, + print_function, unicode_literals) +from builtins import * # noqa: F401 +from future.utils import bytes_to_native_str import binascii import hashlib -import re import sys import base64 +import struct import coincurve as secp256k1 #Required only for PoDLE calculation: N = 115792089237316195423570985008687907852837564279074904382605163141518161494337 #Standard prefix for Bitcoin message signing. -BITCOIN_MESSAGE_MAGIC = '\x18' + 'Bitcoin Signed Message:\n' - -if sys.version_info.major == 2: - string_types = (str, unicode) - string_or_bytes_types = string_types - int_types = (int, float, long) - - # Base switching - code_strings = { - 2: '01', - 10: '0123456789', - 16: '0123456789abcdef', - 32: 'abcdefghijklmnopqrstuvwxyz234567', - 58: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz', - 256: ''.join([chr(x) for x in range(256)]) - } - - def lpad(msg, symbol, length): - if len(msg) >= length: - return msg - return symbol * (length - len(msg)) + msg - - def get_code_string(base): - if base in code_strings: - return code_strings[base] +BITCOIN_MESSAGE_MAGIC = b'\x18' + b'Bitcoin Signed Message:\n' + +string_types = (str) +string_or_bytes_types = (str, bytes) +int_types = (int, float) + +# Base switching +code_strings = { + 2: '01', + 10: '0123456789', + 16: '0123456789abcdef', + 32: 'abcdefghijklmnopqrstuvwxyz234567', + 58: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz', + 256: ''.join([chr(x) for x in range(256)]) +} + +def lpad(msg, symbol, length): + if len(msg) >= length: + return msg + return symbol * (length - len(msg)) + msg + +def get_code_string(base): + if base in code_strings: + return code_strings[base] + else: + raise ValueError("Invalid base!") + +def bin_to_b58check(inp, magicbyte=b'\x00'): + if not isinstance(magicbyte, int): + magicbyte = struct.unpack(b'B', magicbyte)[0] + assert(0 <= magicbyte <= 0xff) + if magicbyte == 0: + inp_fmtd = struct.pack(b'B', magicbyte) + inp + while magicbyte > 0: + inp_fmtd = struct.pack(b'B', magicbyte % 256) + inp + magicbyte //= 256 + checksum = bin_dbl_sha256(inp_fmtd)[:4] + return b58encode(inp_fmtd + checksum) + +def bytes_to_hex_string(b): + return b.encode('hex') + +def safe_from_hex(s): + return s.decode('hex') + +def from_int_to_byte(a): + return struct.pack(b'B', a) + +def from_byte_to_int(a): + return struct.unpack(b'B', a)[0] + +def from_string_to_bytes(a): + return a if isinstance(a, bytes) else bytes(a, 'utf-8') + +def safe_hexlify(a): + return str(binascii.hexlify(a), 'utf-8') + +class SerializationError(Exception): + """Base class for serialization errors""" + + +class SerializationTruncationError(SerializationError): + """Serialized data was truncated + Thrown by deserialize() and stream_deserialize() + """ + +def ser_read(f, n): + """Read from a stream safely + Raises SerializationError and SerializationTruncationError appropriately. + Use this instead of f.read() in your classes stream_(de)serialization() + functions. + """ + MAX_SIZE = 0x02000000 + if n > MAX_SIZE: + raise SerializationError('Asked to read 0x%x bytes; MAX_SIZE exceeded' % n) + r = f.read(n) + if len(r) < n: + raise SerializationTruncationError('Asked to read %i bytes, but only got %i' % (n, len(r))) + return r + +B58_DIGITS = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' + +class Base58Error(Exception): + pass + +class InvalidBase58Error(Base58Error): + """Raised on generic invalid base58 data, such as bad characters. + Checksum failures raise Base58ChecksumError specifically. + """ + pass + +def b58encode(b): + """Encode bytes to a base58-encoded string""" + + # Convert big-endian bytes to integer + n = int('0x0' + binascii.hexlify(b).decode('utf8'), 16) + + # Divide that integer into bas58 + res = [] + while n > 0: + n, r = divmod(n, 58) + res.append(B58_DIGITS[r]) + res = ''.join(res[::-1]) + + # Encode leading zeros as base58 zeros + czero = b'\x00' + if sys.version > '3': + # In Python3 indexing a bytes returns numbers, not characters. + czero = 0 + pad = 0 + for c in b: + if c == czero: + pad += 1 else: - raise ValueError("Invalid base!") - - def changebase(string, frm, to, minlen=0): - if frm == to: - return lpad(string, get_code_string(frm)[0], minlen) - return encode(decode(string, frm), to, minlen) - - def bin_to_b58check(inp, magicbyte=0): - inp_fmtd = chr(int(magicbyte)) + inp - leadingzbytes = len(re.match('^\x00*', inp_fmtd).group(0)) - checksum = bin_dbl_sha256(inp_fmtd)[:4] - return '1' * leadingzbytes + changebase(inp_fmtd + checksum, 256, 58) - - def bytes_to_hex_string(b): - return b.encode('hex') - - def safe_from_hex(s): - return s.decode('hex') - - def from_int_to_byte(a): - return chr(a) - - def from_byte_to_int(a): - return ord(a) - - def from_string_to_bytes(a): - return a - - def safe_hexlify(a): - return binascii.hexlify(a) - - def encode(val, base, minlen=0): - base, minlen = int(base), int(minlen) - code_string = get_code_string(base) - result = "" - while val > 0: - result = code_string[val % base] + result - val //= base - return code_string[0] * max(minlen - len(result), 0) + result - - def decode(string, base): - base = int(base) - code_string = get_code_string(base) - result = 0 - if base == 16: - string = string.lower() - while len(string) > 0: - result *= base - result += code_string.find(string[0]) - string = string[1:] - return result - -else: - raise NotImplementedError("Only Python2 currently supported by btc interface") #pragma: no cover + break + return B58_DIGITS[0] * pad + res + +def b58decode(s): + """Decode a base58-encoding string, returning bytes""" + if not s: + return b'' + + # Convert the string to an integer + n = 0 + for c in s: + n *= 58 + if c not in B58_DIGITS: + raise InvalidBase58Error('Character %r is not a valid base58 character' % c) + digit = B58_DIGITS.index(c) + n += digit + + # Convert the integer to bytes + h = '%x' % n + if len(h) % 2: + h = '0' + h + res = bytes(binascii.unhexlify(h.encode('utf8'))) + + # Add padding back. + pad = 0 + for c in s[:-1]: + if c == B58_DIGITS[0]: pad += 1 + else: break + return b'\x00' * pad + res + +def uint256encode(s): + """Convert bytes to uint256""" + r = 0 + t = struct.unpack(b"> (i * 32) & 0xffffffff) + return r + +def encode(val, base, minlen=0): + base, minlen = int(base), int(minlen) + code_string = get_code_string(base) + result_bytes = bytes() + while val > 0: + curcode = code_string[val % base] + result_bytes = bytes([ord(curcode)]) + result_bytes + val //= base + + pad_size = minlen - len(result_bytes) + + padding_element = b'\x00' if base == 256 else b'1' \ + if base == 58 else b'0' + if (pad_size > 0): + result_bytes = padding_element*pad_size + result_bytes + + result_string = ''.join([chr(y) for y in result_bytes]) + result = result_bytes if base == 256 else result_string + + return result + +def decode(string, base): + if base == 256 and isinstance(string, str): + string = bytes(bytearray.fromhex(string)) + base = int(base) + code_string = get_code_string(base) + result = 0 + if base == 256: + def extract(d, cs): + if isinstance(d, int): + return d + else: + return struct.unpack(b'B', d)[0] + else: + def extract(d, cs): + return cs.find(d if isinstance(d, str) else chr(d)) + + if base == 16: + string = string.lower() + while len(string) > 0: + result *= base + result += extract(string[0], code_string) + string = string[1:] + return result """PoDLE related primitives """ @@ -98,7 +223,7 @@ def getG(compressed=True): """Returns the public key binary representation of secp256k1 G """ - priv = "\x00"*31 + "\x01" + priv = b"\x00"*31 + b"\x01" G = secp256k1.PrivateKey(priv).public_key.format(compressed) return G @@ -137,12 +262,11 @@ def bin_sha256(string): def sha256(string): return bytes_to_hex_string(bin_sha256(string)) -def bin_dbl_sha256(s): - bytes_to_hash = from_string_to_bytes(s) +def bin_dbl_sha256(bytes_to_hash): return hashlib.sha256(hashlib.sha256(bytes_to_hash).digest()).digest() def dbl_sha256(string): - return safe_hexlify(bin_dbl_sha256(string)) + return hashlib.sha256(hashlib.sha256(string).digest()).hexdigest() def hash_to_int(x): if len(x) in [40, 64]: @@ -150,11 +274,14 @@ def hash_to_int(x): return decode(x, 256) def num_to_var_int(x): - x = int(x) + if not isinstance(x, int): + if len(x) == 0: + return b'\x00' + x = struct.unpack(b'B', x)[0] if x < 253: return from_int_to_byte(x) - elif x < 65536: return from_int_to_byte(253) + encode(x, 256, 2)[::-1] - elif x < 4294967296: return from_int_to_byte(254) + encode(x, 256, 4)[::-1] - else: return from_int_to_byte(255) + encode(x, 256, 8)[::-1] + elif x < 65536: return from_int_to_byte(253) + struct.pack(b'= (3, 0): - x = bytes([witver + 0x50 if witver else 0, len(witprog)] + witprog) - else: - x = chr(witver + 0x50) if witver else '\x00' - x += chr(len(witprog)) - x += bytearray(witprog) + x = bytes([witver + 0x50 if witver else 0, len(witprog)] + witprog) return x def mk_native_segwit_script(addr): @@ -412,31 +437,53 @@ def p2sh_scriptaddr(script, magicbyte=5): scriptaddr = p2sh_scriptaddr -def deserialize_script(script): - if isinstance(script, str) and re.match('^[0-9a-fA-F]*$', script): - return json_changebase( - deserialize_script(binascii.unhexlify(script)), - lambda x: safe_hexlify(x)) - out, pos = [], 0 - while pos < len(script): - code = from_byte_to_int(script[pos]) +def deserialize_script(scriptinp): + """Note that this is not used internally, in + the jmbitcoin package, to deserialize() transactions; + its function is only to allow parsing of scripts by + external callers. Thus, it returns in the format used + outside the package: a deserialized script is a list of + entries which can be any of: + None + integer + hex string + """ + def hex_string(scriptbytes, hexout): + if hexout: + return binascii.hexlify(scriptbytes) + else: + return scriptbytes + + if isinstance(scriptinp, basestring) and re.match('^[0-9a-fA-F]*$', scriptinp): + script = BytesIO(binascii.unhexlify(scriptinp)) + hexout = True + else: + script = BytesIO(scriptinp) + hexout = False + script.seek(0, os.SEEK_END) + length = script.tell() + script.seek(0, os.SEEK_SET) + out = [] + while script.tell() < length: + code = from_byte_to_int(ser_read(script, 1)) if code == 0: out.append(None) - pos += 1 elif code <= 75: - out.append(script[pos + 1:pos + 1 + code]) - pos += 1 + code + out.append(hex_string(ser_read(script, code), hexout)) elif code <= 78: - szsz = pow(2, code - 76) - sz = decode(script[pos + szsz:pos:-1], 256) - out.append(script[pos + 1 + szsz:pos + 1 + szsz + sz]) - pos += 1 + szsz + sz + if code == 78: + sz = struct.unpack(b'= (3, 0): - x = bytes([witver + 0x50 if witver else 0, len(witprog)] + witprog) - else: - x = chr(witver + 0x50) if witver else '\x00' - x += chr(len(witprog)) - x += bytearray(witprog) - return x - VALID_CHECKSUM = [ "A12UEL5L", "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", @@ -120,7 +111,7 @@ class TestSegwitAddress(unittest.TestCase): hrp = "tb" witver, witprog = btc.bech32addr_decode(hrp, address) self.assertIsNotNone(witver) - scriptpubkey = segwit_scriptpubkey(witver, witprog) + scriptpubkey = btc.segwit_scriptpubkey(witver, witprog) self.assertEqual(scriptpubkey, binascii.unhexlify(hexscript)) addr = btc.bech32addr_encode(hrp, witver, witprog) self.assertEqual(address.lower(), addr) diff --git a/jmbitcoin/test/test_bip32.py b/jmbitcoin/test/test_bip32.py index dc92dcb..d26c07b 100644 --- a/jmbitcoin/test/test_bip32.py +++ b/jmbitcoin/test/test_bip32.py @@ -1,3 +1,6 @@ +from __future__ import (absolute_import, division, + print_function, unicode_literals) +from builtins import * # noqa: F401 import pytest import jmbitcoin as btc import binascii @@ -71,13 +74,13 @@ def test_bip32_vector(vector): currentkey = master for i in range(1, len(vector['depths'])): currentkey = btc.bip32_ckd(currentkey, vector['depths'][i]) - print currentkey - print vector['keys'][i][0] + print(currentkey) + print(vector['keys'][i][0]) assert currentkey == vector['keys'][i][ 0], 'failed: child priv key, should be: ' + vector['keys'][i][0] pub = btc.bip32_privtopub(currentkey) - print pub - print vector['keys'][i][1] + print(pub) + print(vector['keys'][i][1]) assert pub == vector['keys'][i][ 1], 'failed: child pub key, should be: ' + vector['keys'][i][1] @@ -95,13 +98,13 @@ def test_ckd_pubkeys(): pub = 'xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw' new_pub = btc.bip32_ckd(pub, 4) #how to check it's right? - print new_pub + print(new_pub) #try to do on hardened, should fail, that's the idea: with pytest.raises(Exception) as e_info: new_pub = btc.bip32_ckd(pub, 2**31+1) def test_bip32_descend(): - master = btc.bip32_master_key('\x07'*32) + master = btc.bip32_master_key(b'\x07'*32) end_key = btc.bip32_descend(master, [2, 3, 10000]) assert end_key=="6856ef965940a1a7b1311dc041050ac0013e326c7ff4e2c677a7694b4f0405c901" end_key = btc.bip32_descend(master, 2, 5, 4, 5) diff --git a/jmbitcoin/test/test_btc_formatting.py b/jmbitcoin/test/test_btc_formatting.py index 53691e0..cc15276 100644 --- a/jmbitcoin/test/test_btc_formatting.py +++ b/jmbitcoin/test/test_btc_formatting.py @@ -1,5 +1,7 @@ #! /usr/bin/env python -from __future__ import absolute_import +from __future__ import (absolute_import, division, + print_function, unicode_literals) +from builtins import * # noqa: F401 '''Test bitcoin module data handling''' import jmbitcoin as btc @@ -16,15 +18,6 @@ def test_bad_code_string(): with pytest.raises(ValueError) as e_info: btc.get_code_string(i) -@pytest.mark.parametrize( - "st, frm, to, minlen, res", - [ - ("0101aa", 16, 16, 12, "0000000101aa"), - ]) -def test_changebase(st, frm, to, minlen, res): - assert btc.changebase(st, frm, to, minlen) == res - - #Tests of compactsize encoding, see: #https://bitcoin.org/en/developer-reference#compactsize-unsigned-integers #note that little endian is used. diff --git a/jmbitcoin/test/test_ecc_signing.py b/jmbitcoin/test/test_ecc_signing.py index b201dd1..fa489b3 100644 --- a/jmbitcoin/test/test_ecc_signing.py +++ b/jmbitcoin/test/test_ecc_signing.py @@ -1,5 +1,7 @@ #! /usr/bin/env python -from __future__ import absolute_import +from __future__ import (absolute_import, division, + print_function, unicode_literals) +from builtins import * # noqa: F401 '''Test ECDSA signing and other key operations, including legacy message signature conversion.''' @@ -16,7 +18,7 @@ def test_valid_sigs(setup_ecc): msg = v['msg'] sig = v['sig'] priv = v['privkey'] - assert sig == btc.ecdsa_raw_sign(msg, priv, True, rawmsg=True)+'01' + assert btc.from_string_to_bytes(sig) == btc.ecdsa_raw_sign(msg, priv, True, rawmsg=True)+b'01' #check that the signature verifies against the key(pair) pubkey = btc.privtopub(priv) assert btc.ecdsa_raw_verify(msg, pubkey, sig[:-2], True, rawmsg=True) @@ -24,8 +26,8 @@ def test_valid_sigs(setup_ecc): for i in [0,1,2,4,7,25,55]: #corrupt one byte binsig = binascii.unhexlify(sig) - checksig = binascii.hexlify(binsig[:i] + chr( - (ord(binsig[i])+1) %256) + binsig[i+1:-1]) + checksig = binascii.hexlify(binsig[:i] + btc.from_string_to_bytes(chr( + (ord(binsig[i:i+1])+1) %256)) + binsig[i+1:-1]) #this kind of corruption will sometimes lead to an assert #failure (if the DER format is corrupted) and sometimes lead @@ -36,6 +38,7 @@ def test_valid_sigs(setup_ecc): continue assert res==False + @pytest.fixture(scope='module') def setup_ecc(): global vectors diff --git a/jmbitcoin/test/test_keys.py b/jmbitcoin/test/test_keys.py index f0f9c28..b1fdbc6 100644 --- a/jmbitcoin/test/test_keys.py +++ b/jmbitcoin/test/test_keys.py @@ -1,5 +1,7 @@ #! /usr/bin/env python -from __future__ import absolute_import +from __future__ import (absolute_import, division, + print_function, unicode_literals) +from builtins import * # noqa: F401 '''Public and private key validity and formatting tests.''' import jmbitcoin as btc @@ -10,30 +12,30 @@ import os testdir = os.path.dirname(os.path.realpath(__file__)) def test_read_raw_privkeys(): - badkeys = ['', '\x07'*31,'\x07'*34, '\x07'*33] + badkeys = [b'', b'\x07'*31,b'\x07'*34, b'\x07'*33] for b in badkeys: with pytest.raises(Exception) as e_info: c, k = btc.read_privkey(b) - goodkeys = [('\x07'*32, False), ('\x07'*32 + '\x01', True)] + goodkeys = [(b'\x07'*32, False), (b'\x07'*32 + b'\x01', True)] for g in goodkeys: c, k = btc.read_privkey(g[0]) assert c == g[1] def test_wif_privkeys_invalid(): #first try to create wif privkey from key of wrong length - bad_privs = ['\x01\x02'*17] #some silly private key but > 33 bytes + bad_privs = [b'\x01\x02'*17] #some silly private key but > 33 bytes #next try to create wif with correct length but wrong compression byte - bad_privs.append('\x07'*32 + '\x02') + bad_privs.append(b'\x07'*32 + b'\x02') for priv in bad_privs: with pytest.raises(Exception) as e_info: fake_wif = btc.wif_compressed_privkey(binascii.hexlify(priv)) #Create a wif with wrong length - bad_wif1 = btc.bin_to_b58check('\x01\x02'*34, 128) + bad_wif1 = btc.bin_to_b58check(b'\x01\x02'*34, b'\x80') #Create a wif with wrong compression byte - bad_wif2 = btc.bin_to_b58check('\x07'*33, 128) + bad_wif2 = btc.bin_to_b58check(b'\x07'*33, b'\x80') for bw in [bad_wif1, bad_wif2]: with pytest.raises(Exception) as e_info: fake_priv = btc.from_wif_privkey(bw) @@ -47,7 +49,7 @@ def test_wif_privkeys_invalid(): bad_key = k[0] for netval in ["mainnet", "testnet"]: #if using pytest -s ; sanity check to see what's actually being tested - print 'testing this key: ' + bad_key + print('testing this key: ' + bad_key) #should throw exception with pytest.raises(Exception) as e_info: from_wif_key = btc.from_wif_privkey(bad_key, @@ -55,7 +57,7 @@ def test_wif_privkeys_invalid(): #in case the b58 check encoding is valid, we should #also check if the leading version byte is in the #expected set, and throw an error if not. - if chr(btc.get_version_byte(bad_key)) not in '\x80\xef': + if chr(btc.get_version_byte(bad_key)) not in b'\x80\xef': raise Exception("Invalid version byte") def test_wif_privkeys_valid(): @@ -66,14 +68,14 @@ def test_wif_privkeys_valid(): key, hex_key, prop_dict = a if prop_dict["isPrivkey"]: netval = "testnet" if prop_dict["isTestnet"] else "mainnet" - print 'testing this key: ' + key - assert chr(btc.get_version_byte( - key)) in '\x80\xef', "not valid network byte" + print('testing this key: ' + key) + assert btc.get_version_byte( + key) in b'\x80\xef', "not valid network byte" comp = prop_dict["isCompressed"] from_wif_key = btc.from_wif_privkey( key, compressed=comp, - vbyte=btc.get_version_byte(key)-128) + vbyte=btc.from_int_to_byte(btc.from_byte_to_int(btc.get_version_byte(key))-128)) expected_key = hex_key if comp: expected_key += '01' assert from_wif_key == expected_key, "Incorrect key decoding: " + \ diff --git a/jmbitcoin/test/test_main.py b/jmbitcoin/test/test_main.py index 1532eed..2caf52f 100644 --- a/jmbitcoin/test/test_main.py +++ b/jmbitcoin/test/test_main.py @@ -1,5 +1,7 @@ #! /usr/bin/env python -from __future__ import absolute_import +from __future__ import (absolute_import, division, + print_function, unicode_literals) +from builtins import * # noqa: F401 '''Testing mostly exceptional cases in secp256k1_main. Some of these may represent code that should be removed, TODO.''' @@ -22,7 +24,7 @@ def test_lpad(): assert btc.lpad("aaaa", "b", 3) == "aaaa" def test_safe_from_hex(): - assert btc.safe_from_hex('ff0100') == '\xff\x01\x00' + assert btc.safe_from_hex('ff0100') == b'\xff\x01\x00' def test_hash2int(): assert btc.hash_to_int("aa"*32) == \ @@ -30,9 +32,9 @@ def test_hash2int(): @btc.hexbin def dummyforwrap(a, b, c, d="foo", e="bar"): - newa = a+"\x01" + newa = a+b"\x01" x, y = b - newb = [x+"\x02", y+"\x03"] + newb = [x+b"\x02", y+b"\x03"] if d == "foo": return newb[1] else: @@ -41,7 +43,7 @@ def dummyforwrap(a, b, c, d="foo", e="bar"): def test_hexbin(): assert dummyforwrap("aa", ["bb", "cc"], True) == "cc03" assert dummyforwrap("aa", ["bb", "cc"], True, d="baz") == "bb02" - assert dummyforwrap("\xaa", ["\xbb", "\xcc"], False) == "\xcc\x03" + assert dummyforwrap(b"\xaa", [b"\xbb", b"\xcc"], False) == b"\xcc\x03" def test_add_privkeys(): with pytest.raises(Exception) as e_info: @@ -54,7 +56,7 @@ def test_ecdsa_raw_sign(): assert e_info.match("Invalid hash input") #build non-raw priv object as input privraw = "aa"*32 - msghash = "\xbb"*32 + msghash = b"\xbb"*32 sig = binascii.hexlify(btc.ecdsa_raw_sign(msghash, privraw, False, rawpriv=False, rawmsg=True)) assert sig == "3045022100b81960b4969b423199dea555f562a66b7f49dea5836a0168361f1a5f8a3c8298022003eea7d7ee4462e3e9d6d59220f950564caeb77f7b1cdb42af3c83b013ff3b2f" diff --git a/jmbitcoin/test/test_tx_serialize.py b/jmbitcoin/test/test_tx_serialize.py index c538efb..e145a87 100644 --- a/jmbitcoin/test/test_tx_serialize.py +++ b/jmbitcoin/test/test_tx_serialize.py @@ -1,3 +1,6 @@ +from __future__ import (absolute_import, division, + print_function, unicode_literals) +from builtins import * # noqa: F401 import jmbitcoin as btc import pytest import json @@ -228,7 +231,7 @@ def test_serialization_roundtrip2(): #ignore comment entries if len(j) < 2: continue - print j + print(j) deserialized = btc.deserialize(str(j[0])) - print deserialized + print(deserialized) assert j[0] == btc.serialize(deserialized) diff --git a/jmclient/jmclient/btc.py b/jmclient/jmclient/btc.py index 6ea8119..dac9d88 100644 --- a/jmclient/jmclient/btc.py +++ b/jmclient/jmclient/btc.py @@ -2,8 +2,8 @@ different codebase than joinmarket's own. """ #Protocol constants -BTC_P2PK_VBYTE = {"mainnet": 0x00, "testnet": 0x6f} -BTC_P2SH_VBYTE = {"mainnet": 0x05, "testnet": 0xc4} +BTC_P2PK_VBYTE = {"mainnet": b'\x00', "testnet": b'\x6f'} +BTC_P2SH_VBYTE = {"mainnet": b'\x05', "testnet": b'\xc4'} PODLE_COMMIT_FILE = None from jmbase.support import get_log @@ -138,22 +138,11 @@ except ImportError: def b58check_to_bin(addr, version=False): """optionally include the version byte for get_version_byte. - Note that jmbitcoin does not need this flag, because it - uses the changebase() function, which here has been restricted. """ if not version: return ebt.DecodeBase58Check(addr)[1:] else: return ebt.DecodeBase58Check(addr) - - def changebase(inp, frm=256, to=58): - """Implementation of base58 (*not* b58check) conversion - only. Used in message channel verifiable nick construction. - Explicitly disabling any other conversion for now. - """ - if not (frm==256 and to==58): - raise NotImplementedError - return ebt.base_encode(inp, 58) def address_to_script(addr): return etr.Transaction.pay_script(ebt.TYPE_ADDRESS, addr) diff --git a/jmclient/jmclient/client_protocol.py b/jmclient/jmclient/client_protocol.py index 24e9fe3..1e4134d 100644 --- a/jmclient/jmclient/client_protocol.py +++ b/jmclient/jmclient/client_protocol.py @@ -62,7 +62,7 @@ class JMClientProtocol(amp.AMP): self.nick_pubkey = btc.privtopub(self.nick_priv) self.nick_pkh_raw = hashlib.sha256(self.nick_pubkey).digest()[ :self.nick_hashlen] - self.nick_pkh = btc.changebase(self.nick_pkh_raw, 256, 58) + self.nick_pkh = btc.b58encode(self.nick_pkh_raw) #right pad to maximum possible; b58 is not fixed length. #Use 'O' as one of the 4 not included chars in base58. self.nick_pkh += 'O' * (self.nick_maxencoded - len(self.nick_pkh)) @@ -111,9 +111,9 @@ class JMClientProtocol(amp.AMP): nick_stripped = nick[2:2 + max_encoded] #strip right padding nick_unpadded = ''.join([x for x in nick_stripped if x != 'O']) - if not nick_unpadded == btc.changebase(nick_pkh_raw, 256, 58): + if not nick_unpadded == btc.b58encode(nick_pkh_raw): jlog.debug("Nick hash check failed, expected: " + str(nick_unpadded) - + ", got: " + str(btc.changebase(nick_pkh_raw, 256, 58))) + + ", got: " + str(btc.b58encode(nick_pkh_raw))) verif_result = False d = self.callRemote(commands.JMMsgSignatureVerify, verif_result=verif_result, diff --git a/jmclient/jmclient/cryptoengine.py b/jmclient/jmclient/cryptoengine.py index 693dd5f..62d1901 100644 --- a/jmclient/jmclient/cryptoengine.py +++ b/jmclient/jmclient/cryptoengine.py @@ -3,6 +3,7 @@ from __future__ import print_function, absolute_import, division, unicode_litera from binascii import hexlify, unhexlify from collections import OrderedDict +import struct from . import btc @@ -12,7 +13,7 @@ from .configure import get_network TYPE_P2PKH, TYPE_P2SH_P2WPKH, TYPE_P2WPKH = range(3) NET_MAINNET, NET_TESTNET = range(2) NET_MAP = {'mainnet': NET_MAINNET, 'testnet': NET_TESTNET} -WIF_PREFIX_MAP = {'mainnet': 0x80, 'testnet': 0xef} +WIF_PREFIX_MAP = {'mainnet': b'\x80', 'testnet': b'\xef'} BIP44_COIN_MAP = {'mainnet': 2**31, 'testnet': 2**31 + 1} @@ -123,11 +124,11 @@ class BTCEngine(object): @classmethod def wif_to_privkey(cls, wif): raw = btc.b58check_to_bin(wif) - vbyte = btc.get_version_byte(wif) + vbyte = struct.unpack('B', btc.get_version_byte(wif))[0] - if (btc.BTC_P2PK_VBYTE[get_network()] + cls.WIF_PREFIX) & 0xff == vbyte: + if (struct.unpack('B', btc.BTC_P2PK_VBYTE[get_network()])[0] + struct.unpack('B', cls.WIF_PREFIX)[0]) & 0xff == vbyte: key_type = TYPE_P2PKH - elif (btc.BTC_P2SH_VBYTE[get_network()] + cls.WIF_PREFIX) & 0xff == vbyte: + elif (struct.unpack('B', btc.BTC_P2SH_VBYTE[get_network()])[0] + struct.unpack('B', cls.WIF_PREFIX)[0]) & 0xff == vbyte: key_type = TYPE_P2SH_P2WPKH else: key_type = None diff --git a/jmclient/jmclient/maker.py b/jmclient/jmclient/maker.py index 90c180b..bf1b858 100644 --- a/jmclient/jmclient/maker.py +++ b/jmclient/jmclient/maker.py @@ -7,6 +7,7 @@ import sys from binascii import unhexlify import btc +from btc import SerializationError, SerializationTruncationError from jmclient.configure import jm_single from jmbase.support import get_log from jmclient.support import (calc_cj_fee) @@ -110,7 +111,7 @@ class Maker(object): """ try: tx = btc.deserialize(txhex) - except IndexError as e: + except (IndexError, SerializationError, SerializationTruncationError) as e: return (False, 'malformed txhex. ' + repr(e)) jlog.info('obtained tx\n' + pprint.pformat(tx)) goodtx, errmsg = self.verify_unsigned_tx(tx, offerinfo) diff --git a/jmclient/test/commontest.py b/jmclient/test/commontest.py index 9ce12c2..e5795e9 100644 --- a/jmclient/test/commontest.py +++ b/jmclient/test/commontest.py @@ -149,7 +149,6 @@ def make_sign_and_push(ins_full, binarize_tx(de_tx) de_tx = wallet.sign_tx(de_tx, scripts, hashcode=hashcode) #pushtx returns False on any error - print(de_tx) tx = binascii.hexlify(btc.serialize(de_tx)) push_succeed = jm_single().bc_interface.pushtx(tx) if push_succeed: diff --git a/jmdaemon/test/test_message_channel.py b/jmdaemon/test/test_message_channel.py index 15f41b5..9f54beb 100644 --- a/jmdaemon/test/test_message_channel.py +++ b/jmdaemon/test/test_message_channel.py @@ -27,7 +27,7 @@ def make_valid_nick(i=0): nick_priv = hashlib.sha256(chr(i)*16).hexdigest() + '01' nick_pubkey = bitcoin.privtopub(nick_priv) nick_pkh_raw = hashlib.sha256(nick_pubkey).digest()[:NICK_HASH_LENGTH] - nick_pkh = bitcoin.changebase(nick_pkh_raw, 256, 58) + nick_pkh = bitcoin.b58encode(nick_pkh_raw) #right pad to maximum possible; b58 is not fixed length. #Use 'O' as one of the 4 not included chars in base58. nick_pkh += 'O' * (NICK_MAX_ENCODED - len(nick_pkh)) diff --git a/scripts/obwatch/ob-watcher.py b/scripts/obwatch/ob-watcher.py index 8760f53..46ffef4 100644 --- a/scripts/obwatch/ob-watcher.py +++ b/scripts/obwatch/ob-watcher.py @@ -412,7 +412,7 @@ def get_dummy_nick(): privacy, a conformant nick is created based on a random pseudo-pubkey.""" nick_pkh_raw = hashlib.sha256(os.urandom(10)).digest()[:NICK_HASH_LENGTH] - nick_pkh = btc.changebase(nick_pkh_raw, 256, 58) + nick_pkh = btc.b58encode(nick_pkh_raw) #right pad to maximum possible; b58 is not fixed length. #Use 'O' as one of the 4 not included chars in base58. nick_pkh += 'O' * (NICK_MAX_ENCODED - len(nick_pkh))