From 5dde25141e79f7eaf8cd991e853191935664e8df Mon Sep 17 00:00:00 2001 From: Oskar Hladky Date: Thu, 30 Nov 2017 13:31:11 +0100 Subject: [PATCH 1/2] add dash and litecoin support --- .gitignore | 1 + btcpy/constants.py | 57 ++++++++++++++++++++++++++++++++++++++++ btcpy/lib/codecs.py | 16 ++++------- btcpy/setup.py | 2 +- btcpy/structs/address.py | 8 +++--- btcpy/structs/crypto.py | 18 +++++-------- btcpy/structs/hd.py | 22 ++++++++-------- 7 files changed, 85 insertions(+), 39 deletions(-) create mode 100644 btcpy/constants.py diff --git a/.gitignore b/.gitignore index 70c81f7..8995629 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist/ build/ *.egg-info/ +testing.py \ No newline at end of file diff --git a/btcpy/constants.py b/btcpy/constants.py new file mode 100644 index 0000000..b135201 --- /dev/null +++ b/btcpy/constants.py @@ -0,0 +1,57 @@ +from .setup import net_name + +wif_prefixes = { + 'mainnet': 0x80, + 'testnet': 0xEF, + 'litecoin': 0xB0, + 'dashcoin': 0xCC +} + +raw_prefixes_c = {('mainnet', 'p2pkh'): bytearray(b'\x00'), + ('testnet', 'p2pkh'): bytearray(b'\x6f'), + ('mainnet', 'p2sh'): bytearray(b'\x05'), + ('testnet', 'p2sh'): bytearray(b'\xc4'), + ('litecoin', 'p2pkh'): bytearray(b'\x30'), + ('litecoin', 'p2sh'): bytearray(b'\x32'), + ('dashcoin', 'p2pkh'): bytearray(b'\x4c'), + ('dashcoin', 'p2sh'): bytearray(b'\x13') + } + +prefixes_c = {'1': ('p2pkh', 'mainnet'), + 'm': ('p2pkh', 'testnet'), + 'n': ('p2pkh', 'testnet'), + '3': ('p2sh', 'mainnet'), + '2': ('p2sh', 'testnet'), + 'L': ('p2pkh', 'litecoin'), + 'M': ('p2sh', 'litecoin'), + 'X': ('p2pkh', 'dashcoin') + } + +raw_prefixes_to_init = { + 'litecoin': + { + 'L': ('p2pkh', 'litecoin'), + 'M': ('p2sh', 'litecoin') + }, + 'dashcoin': + { + 'X': ('p2pkh', 'dashcoin'), + '8': ('p2sh', 'dashcoin'), + '9': ('p2sh', 'dashcoin') + }, + 'mainnet': + { + '1': ('p2pkh', 'mainnet'), + '3': ('p2sh', 'mainnet') + }, + 'testnet': + { + 'm': ('p2pkh', 'testnet'), + 'n': ('p2pkh', 'testnet'), + '2': ('p2sh', 'testnet') + } +} + + +def prefixes_c(): + return raw_prefixes_to_init[net_name()] diff --git a/btcpy/lib/codecs.py b/btcpy/lib/codecs.py index 89919c0..129df24 100644 --- a/btcpy/lib/codecs.py +++ b/btcpy/lib/codecs.py @@ -15,6 +15,7 @@ from .bech32 import decode, encode from ..setup import is_mainnet, net_name from ..structs.address import Address, SegWitAddress +from ..constants import prefixes_c, raw_prefixes_c class CouldNotDecode(ValueError): @@ -39,22 +40,15 @@ def decode(string: str, check_network=True) -> Address: @classmethod def check_network(cls, network): - if (network == 'mainnet') != is_mainnet(): + if network != net_name(): raise CouldNotDecode('Trying to parse {} address in {} environment'.format(network, net_name())) class Base58Codec(Codec): - raw_prefixes = {('mainnet', 'p2pkh'): bytearray(b'\x00'), - ('testnet', 'p2pkh'): bytearray(b'\x6f'), - ('mainnet', 'p2sh'): bytearray(b'\x05'), - ('testnet', 'p2sh'): bytearray(b'\xc4')} + raw_prefixes = raw_prefixes_c - prefixes = {'1': ('p2pkh', 'mainnet'), - 'm': ('p2pkh', 'testnet'), - 'n': ('p2pkh', 'testnet'), - '3': ('p2sh', 'mainnet'), - '2': ('p2sh', 'testnet')} + prefixes = prefixes_c() hash_len = 20 @@ -81,7 +75,7 @@ def decode(string, check_network=True): if check_network: Base58Codec.check_network(network) - return Address(addr_type, hashed_data, network == 'mainnet') + return Address(addr_type, hashed_data) class Bech32Codec(Codec): diff --git a/btcpy/setup.py b/btcpy/setup.py index a3c21de..70b53a7 100644 --- a/btcpy/setup.py +++ b/btcpy/setup.py @@ -9,7 +9,7 @@ # propagated, or distributed except according to the terms contained in the # LICENSE.md file. -networks = {'mainnet', 'testnet', 'regtest'} +networks = {'mainnet', 'testnet', 'regtest', 'litecoin', 'dashcoin'} MAINNET = None NETNAME = None diff --git a/btcpy/structs/address.py b/btcpy/structs/address.py index b4e960a..f278f24 100644 --- a/btcpy/structs/address.py +++ b/btcpy/structs/address.py @@ -11,7 +11,7 @@ from abc import ABCMeta, abstractmethod -from ..setup import is_mainnet +from ..setup import is_mainnet, net_name class BaseAddress(metaclass=ABCMeta): @@ -50,9 +50,7 @@ def get_codec(): return Base58Codec def __init__(self, addr_type, hashed_data, mainnet=None): - if mainnet is None: - mainnet = is_mainnet() - network = 'mainnet' if mainnet else 'testnet' + network = net_name() self.network = network self.type = addr_type self.hash = hashed_data @@ -79,7 +77,7 @@ def to_address(self): addr_type = 'p2sh' else: raise ValueError('SegWitAddress type does not match p2wpkh nor p2wsh, {} instead'.format(self.type)) - return Address(addr_type, self.hash, self.network == 'mainnet') + return Address(addr_type, self.hash) def __eq__(self, other): return super().__eq__(other) and self.version == other.version diff --git a/btcpy/structs/crypto.py b/btcpy/structs/crypto.py index 1329dda..125f061 100644 --- a/btcpy/structs/crypto.py +++ b/btcpy/structs/crypto.py @@ -19,7 +19,8 @@ from ..lib.types import HexSerializable from .address import Address, SegWitAddress -from ..setup import is_mainnet +from ..setup import is_mainnet, net_name +from ..constants import wif_prefixes class Key(HexSerializable, metaclass=ABCMeta): @@ -39,14 +40,11 @@ def from_wif(wif, check_network=True): decoded = b58decode_check(wif) prefix, *rest = decoded - if prefix not in {0x80, 0xef}: + if prefix not in wif_prefixes.values(): raise ValueError('Unknown private key prefix: {:02x}'.format(prefix)) - if check_network: - if prefix == 0x80 and not is_mainnet(): - raise ValueError('Mainnet prefix in testnet environment') - elif prefix == 0xef and is_mainnet(): - raise ValueError('Testnet prefix in mainnet envirnment') + if check_network and not prefix == wif_prefixes[net_name()]: + raise ValueError('Bad enviroment') public_compressed = len(rest) == 33 privk = rest[0:32] @@ -61,10 +59,8 @@ def __init__(self, priv, public_compressed=True): self.key = priv self.public_compressed = public_compressed - def to_wif(self, mainnet=None): - if mainnet is None: - mainnet = is_mainnet() - prefix = bytearray([0x80]) if mainnet else bytearray([0xef]) + def to_wif(self): + prefix = bytearray([wif_prefixes[net_name()]]) decoded = prefix + self.key if self.public_compressed: decoded.append(0x01) diff --git a/btcpy/structs/hd.py b/btcpy/structs/hd.py index ffa2cad..2de298d 100644 --- a/btcpy/structs/hd.py +++ b/btcpy/structs/hd.py @@ -20,33 +20,33 @@ from ..lib.types import HexSerializable from ..lib.parsing import Stream, Parser -from ..setup import is_mainnet +from ..setup import is_mainnet, net_name from .crypto import PrivateKey, PublicKey class ExtendedKey(HexSerializable, metaclass=ABCMeta): - - master_parent_fingerprint = bytearray([0]*4) + master_parent_fingerprint = bytearray([0] * 4) first_hardened_index = 1 << 31 curve_order = SECP256k1.order + networks = { + 'x': 'mainnet', + 't': 'testnet', + } @classmethod def master(cls, key, chaincode): return cls(key, chaincode, 0, cls.master_parent_fingerprint, 0, hardened=True) @classmethod - def decode(cls, string, check_network=True): - if string[0] == 'x': - mainnet = True - elif string[0] == 't': - mainnet = False + def decode(cls, string, check_network=False): + if string[0] in cls.networks: + network = cls.networks[string[0]] else: raise ValueError('Encoded key not recognised: {}'.format(string)) - if check_network and mainnet != is_mainnet(): + if check_network and network != net_name(): raise ValueError('Trying to decode {}mainnet key ' - 'in {}mainnet environment'.format('' if mainnet else 'non-', - 'non-' if mainnet else '')) + 'in {}' + network + ' environment') decoded = b58decode_check(string) parser = Parser(bytearray(decoded)) From 36d270f4a7ed090aca60caf06731d87dd5bf534c Mon Sep 17 00:00:00 2001 From: Oskar Hladky Date: Thu, 30 Nov 2017 13:57:56 +0100 Subject: [PATCH 2/2] check network fix in address decode --- btcpy/structs/hd.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/btcpy/structs/hd.py b/btcpy/structs/hd.py index 2de298d..fe49e5e 100644 --- a/btcpy/structs/hd.py +++ b/btcpy/structs/hd.py @@ -29,8 +29,8 @@ class ExtendedKey(HexSerializable, metaclass=ABCMeta): first_hardened_index = 1 << 31 curve_order = SECP256k1.order networks = { - 'x': 'mainnet', - 't': 'testnet', + 'x': ['mainnet', 'litecoin', 'dashcoin'], + 't': ['testnet'], } @classmethod @@ -38,13 +38,13 @@ def master(cls, key, chaincode): return cls(key, chaincode, 0, cls.master_parent_fingerprint, 0, hardened=True) @classmethod - def decode(cls, string, check_network=False): + def decode(cls, string, check_network=True): if string[0] in cls.networks: network = cls.networks[string[0]] else: raise ValueError('Encoded key not recognised: {}'.format(string)) - if check_network and network != net_name(): + if check_network and net_name() in network: raise ValueError('Trying to decode {}mainnet key ' 'in {}' + network + ' environment')