From 075f7390004bcd3c58e6663f0a987afa2a34eebc Mon Sep 17 00:00:00 2001 From: Aleksey Covacevice Date: Wed, 23 Feb 2022 22:40:46 -0300 Subject: [PATCH] Updates - Introduce `DEFAULT_SEMANTIC` to cryptocurrency description; - Introduce `"base58check"` semantic (for DeSo); - Introduce `address()` in `HDWallet`; - Fix xpub/xprv address prefixes for DESOTEST. The default semantic for Bitcoin is "p2wpkh". --- hdwallet/cryptocurrencies.py | 54 +++++++++--------------------------- hdwallet/hdwallet.py | 38 ++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 45 deletions(-) diff --git a/hdwallet/cryptocurrencies.py b/hdwallet/cryptocurrencies.py index bfb78a1..baff6d1 100644 --- a/hdwallet/cryptocurrencies.py +++ b/hdwallet/cryptocurrencies.py @@ -62,17 +62,19 @@ class Cryptocurrency(NestedNamespace): SOURCE_CODE: Optional[str] COIN_TYPE: CoinType - SCRIPT_ADDRESS: int + SCRIPT_ADDRESS: Optional[int] = None PUBLIC_KEY_ADDRESS: int = 0 PRIVATE_KEY_ADDRESS: int = 0 - SEGWIT_ADDRESS: SegwitAddress + SEGWIT_ADDRESS: Optional[SegwitAddress] = None EXTENDED_PRIVATE_KEY: ExtendedPrivateKey EXTENDED_PUBLIC_KEY: ExtendedPublicKey MESSAGE_PREFIX: Optional[str] DEFAULT_PATH: str - WIF_SECRET_KEY: int + WIF_SECRET_KEY: Optional[int] = None + + DEFAULT_SEMANTIC: str = "p2pkh" class AnonMainnet(Cryptocurrency): @@ -713,6 +715,7 @@ class BitcoinMainnet(Cryptocurrency): MESSAGE_PREFIX = "\x18Bitcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 + DEFAULT_SEMANTIC = "p2wpkh" class BitcoinPlusMainnet(Cryptocurrency): @@ -833,6 +836,7 @@ class BitcoinTestnet(Cryptocurrency): MESSAGE_PREFIX = "\x18Bitcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xef + DEFAULT_SEMANTIC = "p2wpkh" class BitcoinZMainnet(Cryptocurrency): @@ -1686,34 +1690,18 @@ class DeSoMainnet(Cryptocurrency): "HARDENED": True }) - SCRIPT_ADDRESS = 0x05 PUBLIC_KEY_ADDRESS = 0xCD1400 PRIVATE_KEY_ADDRESS = 0x350000 - SEGWIT_ADDRESS = SegwitAddress({ - "HRP": "bc", - "VERSION": 0x00 - }) - EXTENDED_PRIVATE_KEY = ExtendedPrivateKey({ - "P2PKH": 0x0488ade4, - "P2SH": 0x0488ade4, - "P2WPKH": 0x04b2430c, - "P2WPKH_IN_P2SH": 0x049d7878, - "P2WSH": 0x02aa7a99, - "P2WSH_IN_P2SH": 0x0295b005 + "BASE58CHECK": 0x0488ade4 }) EXTENDED_PUBLIC_KEY = ExtendedPublicKey({ - "P2PKH": 0x0488b21e, - "P2SH": 0x0488b21e, - "P2WPKH": 0x04b24746, - "P2WPKH_IN_P2SH": 0x049d7cb2, - "P2WSH": 0x02aa7ed3, - "P2WSH_IN_P2SH": 0x0295b43f + "BASE58CHECK": 0x0488b21e }) MESSAGE_PREFIX = "\x18DeSo Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" - WIF_SECRET_KEY = 0x80 + DEFAULT_SEMANTIC = "base58check" class DeSoTestnet(Cryptocurrency): @@ -1727,34 +1715,18 @@ class DeSoTestnet(Cryptocurrency): "HARDENED": True }) - SCRIPT_ADDRESS = 0x05 PUBLIC_KEY_ADDRESS = 0x11C200 PRIVATE_KEY_ADDRESS = 0x4F061B - SEGWIT_ADDRESS = SegwitAddress({ - "HRP": "bc", - "VERSION": 0x00 - }) - EXTENDED_PRIVATE_KEY = ExtendedPrivateKey({ - "P2PKH": 0x0488ade4, - "P2SH": 0x0488ade4, - "P2WPKH": 0x04b2430c, - "P2WPKH_IN_P2SH": 0x049d7878, - "P2WSH": 0x02aa7a99, - "P2WSH_IN_P2SH": 0x0295b005 + "BASE58CHECK": 0x04358394 }) EXTENDED_PUBLIC_KEY = ExtendedPublicKey({ - "P2PKH": 0x0488b21e, - "P2SH": 0x0488b21e, - "P2WPKH": 0x04b24746, - "P2WPKH_IN_P2SH": 0x049d7cb2, - "P2WSH": 0x02aa7ed3, - "P2WSH_IN_P2SH": 0x0295b43f + "BASE58CHECK": 0x043587cf }) MESSAGE_PREFIX = "\x18DeSo Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" - WIF_SECRET_KEY = 0x80 + DEFAULT_SEMANTIC = "base58check" class DiamondMainnet(Cryptocurrency): diff --git a/hdwallet/hdwallet.py b/hdwallet/hdwallet.py index ac31e3a..96f4da9 100644 --- a/hdwallet/hdwallet.py +++ b/hdwallet/hdwallet.py @@ -67,7 +67,7 @@ class HDWallet: :type symbol: str :param cryptocurrency: Cryptocurrency instance, defaults to ``None``. :type cryptocurrency: Cryptocurrency - :param semantic: Extended semantic, defaults to ``P2PKH``. + :param semantic: Extended semantic :type semantic: str :param use_default_path: Use default derivation path, defaults to ``False``. :type use_default_path: bool @@ -79,7 +79,7 @@ class HDWallet: """ def __init__(self, symbol: str = "BTC", cryptocurrency: Any = None, - semantic: str = "p2pkh", use_default_path: bool = False): + semantic: str = None, use_default_path: bool = False): self._cryptocurrency: Any = None if cryptocurrency: if not issubclass(cryptocurrency, Cryptocurrency): @@ -88,6 +88,9 @@ def __init__(self, symbol: str = "BTC", cryptocurrency: Any = None, else: self._cryptocurrency: Any = get_cryptocurrency(symbol=symbol) + if semantic is None: + semantic = self._cryptocurrency.DEFAULT_SEMANTIC + self._strength: Optional[int] = None self._entropy: Optional[str] = None self._mnemonic: Optional[str] = None @@ -878,6 +881,9 @@ def private_key_base58check(self) -> str: unhexlify(self.private_key()) ).decode() + def base58check_address(self) -> str: + return self.public_key_base58check() + def strength(self) -> Optional[int]: """ Get Entropy strength. @@ -1142,7 +1148,7 @@ def p2pkh_address(self) -> str: network_hash160_bytes = _unhexlify(self._cryptocurrency.PUBLIC_KEY_ADDRESS) + public_key_hash return ensure_string(base58.b58encode_check(network_hash160_bytes)) - def p2sh_address(self) -> str: + def p2sh_address(self) -> Optional[str]: """ Get Pay to Script Hash (P2SH) address. @@ -1157,6 +1163,9 @@ def p2sh_address(self) -> str: "3Jp6ad4ErhibQmhSRfavbPRiUyg2xTTT4j" """ + if self._cryptocurrency.SCRIPT_ADDRESS is None: + return None + compressed_public_key = unhexlify(self.compressed()) public_key_hash = hashlib.new('ripemd160', sha256(compressed_public_key).digest()).hexdigest() public_key_hash_script = unhexlify("76a914" + public_key_hash + "88ac") @@ -1181,6 +1190,8 @@ def p2wpkh_address(self) -> Optional[str]: compressed_public_key = unhexlify(self.compressed()) public_key_hash = hashlib.new('ripemd160', sha256(compressed_public_key).digest()).digest() + if self._cryptocurrency.SEGWIT_ADDRESS is None: + return None if self._cryptocurrency.SEGWIT_ADDRESS.HRP is None: return None return ensure_string(encode(self._cryptocurrency.SEGWIT_ADDRESS.HRP, 0, public_key_hash)) @@ -1200,10 +1211,15 @@ def p2wpkh_in_p2sh_address(self) -> Optional[str]: "3CCrxPrHNa6ePbnB7qjh7S3vaPx9qiLc3e" """ + if self._cryptocurrency.SCRIPT_ADDRESS is None: + return None + compressed_public_key = unhexlify(self.compressed()) public_key_hash = hashlib.new('ripemd160', sha256(compressed_public_key).digest()).hexdigest() script_hash = hashlib.new('ripemd160', sha256(unhexlify("0014" + public_key_hash)).digest()).digest() network_hash160_bytes = _unhexlify(self._cryptocurrency.SCRIPT_ADDRESS) + script_hash + if self._cryptocurrency.SEGWIT_ADDRESS is None: + return None if self._cryptocurrency.SEGWIT_ADDRESS.HRP is None: return None return ensure_string(base58.b58encode_check(network_hash160_bytes)) @@ -1225,6 +1241,8 @@ def p2wsh_address(self) -> Optional[str]: compressed_public_key = unhexlify("5121" + self.compressed() + "51ae") script_hash = sha256(compressed_public_key).digest() + if self._cryptocurrency.SEGWIT_ADDRESS is None: + return None if self._cryptocurrency.SEGWIT_ADDRESS.HRP is None: return None return ensure_string(encode(self._cryptocurrency.SEGWIT_ADDRESS.HRP, 0, script_hash)) @@ -1244,6 +1262,9 @@ def p2wsh_in_p2sh_address(self) -> Optional[str]: "38YMonfh2yLFRViLrM2kdvZx8ctcp1vbbV" """ + if self._cryptocurrency.SCRIPT_ADDRESS is None: + return None + compressed_public_key = unhexlify("5121" + self.compressed() + "51ae") script_hash = unhexlify("0020" + sha256(compressed_public_key).hexdigest()) script_hash = hashlib.new('ripemd160', sha256(script_hash).digest()).digest() @@ -1252,6 +1273,11 @@ def p2wsh_in_p2sh_address(self) -> Optional[str]: return None return ensure_string(base58.b58encode_check(network_hash160_bytes)) + def address(self, semantic=None): + if semantic == None: + semantic = self._cryptocurrency.DEFAULT_SEMANTIC + return getattr(self, f"{semantic}_address")() + def wif(self) -> Optional[str]: """ Get Wallet Important Format. @@ -1267,6 +1293,9 @@ def wif(self) -> Optional[str]: "KzsHWUJsrTWUUhBGPfMMxLLydiH7NhEn6z7mKHXD5qNkUWaC4TEn" """ + if self._cryptocurrency.WIF_SECRET_KEY is None: + return + return check_encode(_unhexlify(self._cryptocurrency.WIF_SECRET_KEY) + self._key.to_string() + b"\x01") if self._key else None def dumps(self) -> dict: @@ -1321,7 +1350,8 @@ def dumps(self) -> dict: p2wpkh_in_p2sh=self.p2wpkh_in_p2sh_address(), p2wsh=self.p2wsh_address(), p2wsh_in_p2sh=self.p2wsh_in_p2sh_address() - ) + ), + address=self.address() )