diff --git a/doc/addresses.md b/doc/addresses.md new file mode 100644 index 0000000..5ebea87 --- /dev/null +++ b/doc/addresses.md @@ -0,0 +1 @@ +![Address map](img/address_map.svg) \ No newline at end of file diff --git a/pybtc/blockchain.py b/pybtc/blockchain.py index aa647fc..3a64d23 100644 --- a/pybtc/blockchain.py +++ b/pybtc/blockchain.py @@ -83,6 +83,7 @@ class Script(): self.data = b'' self.type = "NON_STANDARD" self.ntype = 7 + self.witness_version = None self.op_sig_count = 0 if coinbase: self.pattern = b"" @@ -148,11 +149,13 @@ class Script(): self.type = "P2WPKH" self.op_sig_count = 1 self.ntype = 5 - self.address.append(b"\x00"+self.script[1].data) + self.witness_version = 0 + self.address.append(self.script[1].data) elif self.pattern == "OP_0 <32>": self.type = "P2WSH" self.ntype = 6 - self.address.append(b"\x00"+self.script[1].data) + self.witness_version = 0 + self.address.append(self.script[1].data) @@ -317,6 +320,13 @@ class Transaction(): self.tx_in_count += 1 self.recalculate_txid() + def add_output(self, amount, script): + if type(script)==str: + script = unhexlify(script) + self.tx_out.append(Output(amount,script)) + self.tx_out_count += 1 + self.recalculate_txid() + def add_P2SH_output(self, amount, p2sh_address): if type(p2sh_address)==str: p2sh_address = decode_base58(p2sh_address)[1:-4] diff --git a/pybtc/constants.py b/pybtc/constants.py new file mode 100644 index 0000000..4eb603a --- /dev/null +++ b/pybtc/constants.py @@ -0,0 +1,76 @@ +from secp256k1 import lib as secp256k1 +from secp256k1 import ffi +import random + +SIGHASH_ALL = 0x00000001 +SIGHASH_NONE = 0x00000002 +SIGHASH_SINGLE = 0x00000003 +SIGHASH_ANYONECANPAY = 0x00000080 +MAX_INT_PRIVATE_KEY = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 + +MAINNET_ADDRESS_BYTE_PREFIX = b'\x00' +TESTNET_ADDRESS_BYTE_PREFIX = b'\x6f' +MAINNET_SCRIPT_ADDRESS_BYTE_PREFIX = b'\x05' +TESTNET_SCRIPT_ADDRESS_BYTE_PREFIX = b'\xc4' +MAINNET_SEGWIT_ADDRESS_BYTE_PREFIX = b'\x03\x03\x00\x02\x03' +TESTNET_SEGWIT_ADDRESS_BYTE_PREFIX = b'\x03\x03\x00\x14\x02' + +MAINNET_ADDRESS_PREFIX = '1' +TESTNET_ADDRESS_PREFIX = 'm' +TESTNET_ADDRESS_PREFIX_2 = 'n' +MAINNET_SCRIPT_ADDRESS_PREFIX = '3' +TESTNET_SCRIPT_ADDRESS_PREFIX = '2' + +MAINNET_PRIVATE_KEY_UNCOMPRESSED_PREFIX = '5' +MAINNET_PRIVATE_KEY_COMPRESSED_PREFIX = 'K' +MAINNET_PRIVATE_KEY_COMPRESSED_PREFIX_2 = 'L' +TESTNET_PRIVATE_KEY_UNCOMPRESSED_PREFIX = '9' +TESTNET_PRIVATE_KEY_COMPRESSED_PREFIX = 'c' + +ADDRESS_PREFIX_LIST = (MAINNET_ADDRESS_PREFIX, + TESTNET_ADDRESS_PREFIX, + TESTNET_ADDRESS_PREFIX_2, + MAINNET_SCRIPT_ADDRESS_PREFIX, + TESTNET_SCRIPT_ADDRESS_PREFIX) + +PRIVATE_KEY_PREFIX_LIST = (MAINNET_PRIVATE_KEY_UNCOMPRESSED_PREFIX, + MAINNET_PRIVATE_KEY_COMPRESSED_PREFIX, + MAINNET_PRIVATE_KEY_COMPRESSED_PREFIX_2, + TESTNET_PRIVATE_KEY_UNCOMPRESSED_PREFIX, + TESTNET_PRIVATE_KEY_COMPRESSED_PREFIX) + +MAINNET_PRIVATE_KEY_BYTE_PREFIX = b'\x80' +TESTNET_PRIVATE_KEY_BYTE_PREFIX = b'\xef' + +MAINNET_SEGWIT_ADDRESS_PREFIX = 'bc' +TESTNET_SEGWIT_ADDRESS_PREFIX = 'tb' + + +EC_COMPRESSED = secp256k1.SECP256K1_EC_COMPRESSED +EC_UNCOMPRESSED = secp256k1.SECP256K1_EC_UNCOMPRESSED + +FLAG_SIGN = secp256k1.SECP256K1_CONTEXT_SIGN +FLAG_VERIFY = secp256k1.SECP256K1_CONTEXT_VERIFY +ALL_FLAGS = FLAG_SIGN | FLAG_VERIFY +NO_FLAGS = secp256k1.SECP256K1_CONTEXT_NONE + +HAS_RECOVERABLE = hasattr(secp256k1, 'secp256k1_ecdsa_sign_recoverable') +HAS_SCHNORR = hasattr(secp256k1, 'secp256k1_schnorr_sign') +HAS_ECDH = hasattr(secp256k1, 'secp256k1_ecdh') + +ECDSA_CONTEXT_SIGN = secp256k1.secp256k1_context_create(FLAG_SIGN) +ECDSA_CONTEXT_VERIFY = secp256k1.secp256k1_context_create(FLAG_VERIFY) +ECDSA_CONTEXT_ALL = secp256k1.secp256k1_context_create(ALL_FLAGS) +secp256k1.secp256k1_context_randomize(ECDSA_CONTEXT_SIGN, + random.SystemRandom().randint(0,MAX_INT_PRIVATE_KEY).to_bytes(32,byteorder="big")) + +SCRIPT_TYPES = { "P2PKH": 0, + "P2SH" : 1, + "PUBKEY": 2, + "NULL_DATA": 3, + "MULTISIG": 4, + "P2WPKH": 5, + "P2WSH": 6, + "NON_STANDART": 7 + } + diff --git a/pybtc/encode.py b/pybtc/encode.py new file mode 100644 index 0000000..ccbb546 --- /dev/null +++ b/pybtc/encode.py @@ -0,0 +1,119 @@ +from binascii import hexlify, unhexlify + +b58_digits = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' +base32charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" +base32charset_upcase = "QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L" + +int_base32_map = dict() +base32_int_map = dict() + +for n, i in enumerate(base32charset): + int_base32_map[i] = n + base32_int_map[n] = ord(i) +for n, i in enumerate(base32charset_upcase): + int_base32_map[i] = n + + + + +def rebasebits(data, frombits, tobits, pad=True): + """General power-of-2 base conversion.""" + acc = 0 + bits = 0 + ret = bytearray() + maxv = (1 << tobits) - 1 + max_acc = (1 << (frombits + tobits - 1)) - 1 + for value in data: + if value < 0 or (value >> frombits): + raise Exception("invalid bytes") + 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): + raise Exception("invalid padding") + return ret + +def rebase_5_to_8(data, pad = True): + return rebasebits(data, 5, 8, pad) + +def rebase_8_to_5(data, pad = True): + return rebasebits(data, 8, 5, pad) + +def rebase_32_to_5(data): + if type(data) == bytes: + data = data.decode() + b = bytearray() + try: + for i in data: + b.append(int_base32_map[i]) + except: + raise Exception("Non base32 characters") + return b + +def rebase_5_to_32(data, bytes = True): + r = bytearray() + for i in data: + r.append(base32_int_map[i]) + return r.decode() if not bytes else r + +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 ^ 1 + +def encode_base58(b): + """Encode bytes to a base58-encoded string""" + # Convert big-endian bytes to integer + n = int('0x0' + 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 = 0 + pad = 0 + for c in b: + if c == czero: + pad += 1 + else: + break + return b58_digits[0] * pad + res + +def decode_base58(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 Exception('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 = 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 diff --git a/pybtc/hash.py b/pybtc/hash.py new file mode 100644 index 0000000..51c779e --- /dev/null +++ b/pybtc/hash.py @@ -0,0 +1,43 @@ +import hashlib +import hmac +from binascii import unhexlify + +def sha256(h, hex = False): + if type(h) == str: + h = unhexlify(h) + if hex: + return hashlib.sha256(h).hexdigest() + return hashlib.sha256(h).digest() + +def double_sha256(h, hex = False): + if type(h) == str: + h = unhexlify(h) + if hex: + return sha256(sha256(h), 1) + return sha256(sha256(h)) + + +def hmac_sha512(key, data, hex = False): + if hex: + return hmac.new(key, data, hashlib.sha512).hexdigest() + return hmac.new(key, data, hashlib.sha512).digest() + + +def ripemd160(h, hex = False): + if type(h) == str: + h = unhexlify(h) + a = hashlib.new('ripemd160') + a.update(h) + if hex: + return a.hexdigest() + return a.digest() + +def hash160(h, hex = False): + if type(h) == str: + h = unhexlify(h) + if hex: + return ripemd160(sha256(h), 1) + return ripemd160(sha256(h)) + + +# \ No newline at end of file diff --git a/pybtc/tools.py b/pybtc/tools.py index 54fecbe..8d211bc 100644 --- a/pybtc/tools.py +++ b/pybtc/tools.py @@ -1,122 +1,17 @@ import hashlib from binascii import hexlify, unhexlify import time -import random import struct import hmac -from secp256k1 import lib as secp256k1 -from secp256k1 import ffi +from .constants import * from .opcodes import * - -SIGHASH_ALL = 0x00000001 -SIGHASH_NONE = 0x00000002 -SIGHASH_SINGLE = 0x00000003 -SIGHASH_ANYONECANPAY = 0x00000080 -MAX_INT_PRIVATE_KEY = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 - -EC_COMPRESSED = secp256k1.SECP256K1_EC_COMPRESSED -EC_UNCOMPRESSED = secp256k1.SECP256K1_EC_UNCOMPRESSED - -FLAG_SIGN = secp256k1.SECP256K1_CONTEXT_SIGN -FLAG_VERIFY = secp256k1.SECP256K1_CONTEXT_VERIFY -ALL_FLAGS = FLAG_SIGN | FLAG_VERIFY -NO_FLAGS = secp256k1.SECP256K1_CONTEXT_NONE - -HAS_RECOVERABLE = hasattr(secp256k1, 'secp256k1_ecdsa_sign_recoverable') -HAS_SCHNORR = hasattr(secp256k1, 'secp256k1_schnorr_sign') -HAS_ECDH = hasattr(secp256k1, 'secp256k1_ecdh') - -ECDSA_CONTEXT_SIGN = secp256k1.secp256k1_context_create(FLAG_SIGN) -ECDSA_CONTEXT_VERIFY = secp256k1.secp256k1_context_create(FLAG_VERIFY) -ECDSA_CONTEXT_ALL = secp256k1.secp256k1_context_create(ALL_FLAGS) -secp256k1.secp256k1_context_randomize(ECDSA_CONTEXT_SIGN, - random.SystemRandom().randint(0,MAX_INT_PRIVATE_KEY).to_bytes(32,byteorder="big")) - -SCRIPT_TYPES = { "P2PKH": 0, - "P2SH" : 1, - "PUBKEY": 2, - "NULL_DATA": 3, - "MULTISIG": 4, - "NON_STANDART": 5, - "SP2PKH": 6 - } - -b58_digits = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' - -# -# Encoding functions -# -def encode_base58(b): - """Encode bytes to a base58-encoded string""" - # Convert big-endian bytes to integer - n = int('0x0' + 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 = 0 - pad = 0 - for c in b: - if c == czero: - pad += 1 - else: - break - return b58_digits[0] * pad + res - -def decode_base58(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 Exception('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 = 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 - -# -# Hash functions -# -def sha256(bytes): - return hashlib.sha256(bytes).digest() - -def double_sha256(bytes): - return sha256(sha256(bytes)) - -def hmac_sha512(key, data): - return hmac.new(key, data, hashlib.sha512).digest() - -def ripemd160(bytes): - h = hashlib.new('ripemd160') - h.update(bytes) - return h.digest() - -def hash160(bytes): - return ripemd160(sha256(bytes)) +from .hash import * +from .encode import * -# # Bitcoin keys/ addresses # -def create_priv(): +def create_priv(hex=False): """ :return: 32 bytes private key """ @@ -131,6 +26,8 @@ def create_priv(): else: if int.from_bytes(h,byteorder="big")>>",block.bits) print(">>>",block.hash) diff --git a/test/hash_functions.py b/test/hash_functions.py index 4416afd..a779b94 100644 --- a/test/hash_functions.py +++ b/test/hash_functions.py @@ -1,6 +1,6 @@ import unittest from pybtc import tools -from binascii import unhexlify +from binascii import unhexlify, hexlify @@ -13,13 +13,24 @@ class HashFunctionsTests(unittest.TestCase): print("Double SHA256") self.assertEqual(tools.double_sha256(b"test double sha256"), unhexlify("1ab3067efb509c48bda198f48c473f034202537c28b7b4c3b2ab2c4bf4a95c8d")) + self.assertEqual(tools.double_sha256(hexlify(b"test double sha256").decode()), + unhexlify("1ab3067efb509c48bda198f48c473f034202537c28b7b4c3b2ab2c4bf4a95c8d")) + self.assertEqual(tools.double_sha256(hexlify(b"test double sha256").decode(), 1), + "1ab3067efb509c48bda198f48c473f034202537c28b7b4c3b2ab2c4bf4a95c8d") def test_ripemd160(self): print("RIPEMD160") self.assertEqual(tools.ripemd160(b"test ripemd160"), unhexlify("45b17861a7defaac439f740d890f3dac4813cc37")) - + self.assertEqual(tools.ripemd160(hexlify(b"test ripemd160").decode()), + unhexlify("45b17861a7defaac439f740d890f3dac4813cc37")) + self.assertEqual(tools.ripemd160(hexlify(b"test ripemd160").decode(), 1), + "45b17861a7defaac439f740d890f3dac4813cc37") def test_hash160(self): print("HASH160") self.assertEqual(tools.ripemd160(b"test hash160"), - unhexlify("46a80bd289028559818a222eea64552d7a6a966f")) \ No newline at end of file + unhexlify("46a80bd289028559818a222eea64552d7a6a966f")) + self.assertEqual(tools.ripemd160(hexlify(b"test hash160").decode()), + unhexlify("46a80bd289028559818a222eea64552d7a6a966f")) + self.assertEqual(tools.ripemd160(hexlify(b"test hash160").decode(), 1), + "46a80bd289028559818a222eea64552d7a6a966f") diff --git a/test/script_deserialize.py b/test/script_deserialize.py index b967cce..b6e6943 100644 --- a/test/script_deserialize.py +++ b/test/script_deserialize.py @@ -1,7 +1,7 @@ import unittest from pybtc import blockchain from binascii import unhexlify -from pybtc import address2hash160 +from pybtc import address2hash as address2hash160 class ScriptDeserializeTests(unittest.TestCase): diff --git a/test/sighash.py b/test/sighash.py index 9c627b3..947cfd4 100644 --- a/test/sighash.py +++ b/test/sighash.py @@ -1,7 +1,7 @@ import unittest from pybtc import * from binascii import unhexlify -from pybtc import address2hash160 +from pybtc import address2hash as address2hash160 class SighashTests(unittest.TestCase): @classmethod diff --git a/test/transaction_deserialize.py b/test/transaction_deserialize.py index dc8c7d4..572a5b4 100644 --- a/test/transaction_deserialize.py +++ b/test/transaction_deserialize.py @@ -1,7 +1,7 @@ import unittest from pybtc import blockchain from binascii import unhexlify -from pybtc import address2hash160 +from pybtc import address2hash as address2hash160 class TransactionDeserializeTests(unittest.TestCase):