From 40632f92332d6be53812959bca09c8f7196ad940 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 17 Aug 2024 12:33:20 +0300 Subject: [PATCH 1/6] Drop: disclaimer on docs and README.md --- README.md | 2 -- docs/index.rst | 2 -- 2 files changed, 4 deletions(-) diff --git a/README.md b/README.md index 1810d07..bbca156 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,6 @@ A Python library for the implementation of Bitcoin Improvement Proposal - 0038 / This library supports both [No EC-multiply](https://github.com/bitcoin/bips/blob/master/bip-0038.mediawiki#encryption-when-ec-multiply-flag-is-not-used) and [EC-multiply](https://github.com/bitcoin/bips/blob/master/bip-0038.mediawiki#encryption-when-ec-multiply-mode-is-used) modes and is compatible with over 150+ cryptocurrencies. It's specifically tailored for Pay-to-PubKey-Hash (P2PKH) address types. - ⚠️ Disclaimer: This library is intended for research purposes only and will not be responsible for any asset loss. - For more info see the [Passphrase-protected private key - BIP38](https://en.bitcoin.it/wiki/BIP_0038) spec. ## Installation diff --git a/docs/index.rst b/docs/index.rst index 096bc7f..e3a5994 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -26,8 +26,6 @@ A Python library for the implementation of Bitcoin Improvement Proposal - 0038 / This library supports both `No EC-multiply `_ and `EC-multiply `_ modes and is compatible with over 150+ cryptocurrencies. It's specifically tailored for Pay-to-PubKey-Hash (P2PKH) address types. - ⚠️ Disclaimer: This library is intended for research purposes only and will not be responsible for any asset loss. - For more info see the `Passphrase-protected private key - BIP38 `_ specs. Installing BIP38 From 1e7f9699135f8fcd868dfda18cc52275cbf90ff0 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 17 Aug 2024 12:34:50 +0300 Subject: [PATCH 2/6] Add: network arg on BIP38 methods --- bip38/bip38.py | 66 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/bip38/bip38.py b/bip38/bip38.py index b39421e..904e1e9 100644 --- a/bip38/bip38.py +++ b/bip38/bip38.py @@ -50,6 +50,14 @@ class BIP38: + """ + A class for BIP38 encryption and decryption of Wallet Import Format (WIF) keys. + + :param cryptocurrency: The cryptocurrency class to use (e.g., Bitcoin). + :type cryptocurrency: Type[ICryptocurrency] + :param network: The network for the WIF key (e.g., 'mainnet' or 'testnet'). Defaults to 'mainnet'. + :type network: str + """ network: str cryptocurrency: Type[ICryptocurrency] @@ -131,7 +139,7 @@ def intermediate_code( magic + owner_entropy + pass_point.raw_compressed() )) - def encrypt(self, wif: str, passphrase: str) -> str: + def encrypt(self, wif: str, passphrase: str, network: Optional[str] = None) -> str: """ Encrypts a Wallet Import Format (WIF) key using a passphrase with BIP38 encryption. @@ -139,6 +147,8 @@ def encrypt(self, wif: str, passphrase: str) -> str: :type wif: str :param passphrase: The passphrase for encryption. :type passphrase: str + :param network: Optional network for encryption. Defaults to the class's network if not provided. + :type network: Optional[str] :returns: Encrypted WIF key as a string. :rtype: str @@ -150,26 +160,28 @@ def encrypt(self, wif: str, passphrase: str) -> str: '6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg' >>> bip38.encrypt(wif="L44B5gGEpqEDRS9vVPz7QT35jcBG2r3CZwSwQ4fCewXAhAhqGVpP", passphrase="TestingOneTwoThree") '6PYNKZ1EAgYgmQfmNVamxyXVWHzK5s6DGhwP4J5o44cvXdoY7sRzhtpUeo' - >>> bip38: BIP38 = BIP38(cryptocurrency=Bitcoin, network="testnet") - >>> bip38.encrypt(wif="938jwjergAxARSWx2YSt9nSBWBz24h8gLhv7EUfgEP1wpMLg6iX", passphrase="TestingOneTwoThree") + >>> bip38.encrypt(wif="938jwjergAxARSWx2YSt9nSBWBz24h8gLhv7EUfgEP1wpMLg6iX", passphrase="TestingOneTwoThree", network="testnet") '6PRL8jj6dLQjBBJjHMdUKLSNLEpjTyAfmt8GnCnfT87NeQ2BU5eAW1tcsS' - >>> bip38.encrypt(wif="cURAYbG6FtvUasdBsooEmmY9MqUfhJ8tdybQWV7iA4BAwunCT2Fu", passphrase="TestingOneTwoThree") + >>> bip38.encrypt(wif="cURAYbG6FtvUasdBsooEmmY9MqUfhJ8tdybQWV7iA4BAwunCT2Fu", passphrase="TestingOneTwoThree", network="testnet") '6PYVB5nHnumbUua1UmsAMPHWHa76Ci48MY79aKYnpKmwxeGqHU2XpXtKvo' """ + network: str = ( + network if network else self.network + ) wif_type: str = get_wif_type( - wif=wif, cryptocurrency=self.cryptocurrency, network=self.network + wif=wif, cryptocurrency=self.cryptocurrency, network=network ) if wif_type == "wif": flag: bytes = integer_to_bytes(NO_EC_MULTIPLIED_WIF_FLAG) private_key: PrivateKey = PrivateKey.from_bytes(get_bytes(wif_to_private_key( - wif=wif, cryptocurrency=self.cryptocurrency, network=self.network + wif=wif, cryptocurrency=self.cryptocurrency, network=network ))) public_key_type: str = "uncompressed" elif wif_type == "wif-compressed": flag: bytes = integer_to_bytes(NO_EC_MULTIPLIED_WIF_COMPRESSED_FLAG) private_key: PrivateKey = PrivateKey.from_bytes(get_bytes(wif_to_private_key( - wif=wif, cryptocurrency=self.cryptocurrency, network=self.network + wif=wif, cryptocurrency=self.cryptocurrency, network=network ))) public_key_type: str = "compressed" else: @@ -177,7 +189,7 @@ def encrypt(self, wif: str, passphrase: str) -> str: address: str = P2PKHAddress.encode( public_key=private_key.public_key(), - address_prefix=self.cryptocurrency.NETWORKS[self.network]["address_prefix"], + address_prefix=self.cryptocurrency.NETWORKS[network]["address_prefix"], public_key_type=public_key_type ) address_hash: bytes = get_checksum(get_bytes(address, unhexlify=False)) @@ -205,7 +217,8 @@ def create_new_encrypted_wif( self, intermediate_passphrase: str, public_key_type: str = "uncompressed", - seed: Optional[Union[str, bytes]] = None + seed: Optional[Union[str, bytes]] = None, + network: Optional[str] = None ) -> dict: """ Creates a new encrypted WIF (Wallet Import Format). @@ -216,6 +229,8 @@ def create_new_encrypted_wif( :type public_key_type: str :param seed: Optional seed (default: random 24 bytes). :type seed: Optional[str, bytes] + :param network: Optional network for encryption. Defaults to the class's network if not provided. + :type network: Optional[str] :returns: A dictionary containing the encrypted WIF. :rtype: dict @@ -269,7 +284,7 @@ def create_new_encrypted_wif( ) address: str = P2PKHAddress.encode( public_key=public_key, - address_prefix=self.cryptocurrency.NETWORKS[self.network]["address_prefix"], + address_prefix=self.cryptocurrency.NETWORKS[network if network else self.network]["address_prefix"], public_key_type=public_key_type ) address_hash: bytes = get_checksum(get_bytes(address, unhexlify=False)) @@ -318,7 +333,7 @@ def create_new_encrypted_wif( ) def confirm_code( - self, passphrase: str, confirmation_code: str, detail: bool = False + self, passphrase: str, confirmation_code: str, network: Optional[str] = None, detail: bool = False ) -> Union[str, dict]: """ Confirms the passphrase using a confirmation code. @@ -327,6 +342,8 @@ def confirm_code( :type passphrase: str :param confirmation_code: The confirmation code. :type confirmation_code: str + :param network: Optional network for encryption. Defaults to the class's network if not provided. + :type network: Optional[str] :param detail: Whether to return detailed info (default: False). :type detail: bool @@ -338,10 +355,9 @@ def confirm_code( >>> bip38: BIP38 = BIP38(cryptocurrency=Bitcoin, network="testnet") >>> bip38.confirm_code(passphrase="TestingOneTwoThree", confirmation_code="cfrm38V8ZQSdCuzcrYYKGNXVwbHgdjsUEfAbFGoEUouB4YEKaXVdFiMcBWai1Exdu8jN7DcoKtM") 'mwW38M23zvDmhbsTdnVFzw3bVnueDhrKec' - >>> bip38: BIP38 = BIP38(cryptocurrency=Bitcoin, network="testnet") - >>> bip38.confirm_code(passphrase="TestingOneTwoThree", confirmation_code="cfrm38V8Foq3WpRPMXJD34SF6pGT6ht5ihYMWWMbezkzHgPpA1jVkfbTHwQzvuSA4ReF86PHZJY") + >>> bip38.confirm_code(passphrase="TestingOneTwoThree", confirmation_code="cfrm38V8Foq3WpRPMXJD34SF6pGT6ht5ihYMWWMbezkzHgPpA1jVkfbTHwQzvuSA4ReF86PHZJY", network="mainnet") '1JbyXoVN4hXWirGB265q9VE4pQ6qbY6kmr' - >>> bip38.confirm_code(passphrase="TestingOneTwoThree", confirmation_code="cfrm38VXL5T6qVke13sHUWtEjibAkK1RquBqMXb2azCv1Zna6JKvBhD1Gf2b15wBj7UPv2BQnf6", detail=True) + >>> bip38.confirm_code(passphrase="TestingOneTwoThree", confirmation_code="cfrm38VXL5T6qVke13sHUWtEjibAkK1RquBqMXb2azCv1Zna6JKvBhD1Gf2b15wBj7UPv2BQnf6", network="mainnet", detail=True) {'public_key': '02100bb0440ff4106a1743750813271e66a7017431e92921e520319f537c7357c1', 'public_key_type': 'compressed', 'address': '15PuNwFmDqYhRsC9MDPNFvNY4Npzibm67c', 'lot': 199999, 'sequence': 1} """ @@ -399,7 +415,7 @@ def confirm_code( address: str = P2PKHAddress.encode( public_key=public_key, - address_prefix=self.cryptocurrency.NETWORKS[self.network]["address_prefix"], + address_prefix=self.cryptocurrency.NETWORKS[network if network else self.network]["address_prefix"], public_key_type=public_key_type ) if get_checksum(get_bytes(address, unhexlify=False)) == address_hash: @@ -420,7 +436,7 @@ def confirm_code( raise ValueError("Incorrect passphrase or password") def decrypt( - self, encrypted_wif: str, passphrase: str, detail: bool = False + self, encrypted_wif: str, passphrase: str, network: Optional[str] = None, detail: bool = False ) -> Union[str, dict]: """ Decrypts an encrypted WIF (Wallet Import Format) using a passphrase. @@ -429,6 +445,8 @@ def decrypt( :type encrypted_wif: str :param passphrase: The passphrase or password. :type passphrase: str + :param network: Optional network for encryption. Defaults to the class's network if not provided. + :type network: Optional[str] :param detail: Whether to return detailed info (default: False). :type detail: bool @@ -442,13 +460,15 @@ def decrypt( '5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR' >>> bip38.decrypt(encrypted_wif="6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", passphrase="TestingOneTwoThree", detail=True) {'wif': '5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR', 'private_key': 'cbf4b9f70470856bb4f40f80b87edb90865997ffee6df315ab166d713af433a5', 'wif_type': 'wif', 'public_key': '04d2ce831dd06e5c1f5b1121ef34c2af4bcb01b126e309234adbc3561b60c9360ea7f23327b49ba7f10d17fad15f068b8807dbbc9e4ace5d4a0b40264eefaf31a4', 'public_key_type': 'uncompressed', 'seed': None, 'address': '1Jq6MksXQVWzrznvZzxkV6oY57oWXD9TXB', 'lot': None, 'sequence': None} - >>> bip38: BIP38 = BIP38(cryptocurrency=Bitcoin, network="testnet") - >>> bip38.decrypt(encrypted_wif="6PRL8jj6dLQjBBJjHMdUKLSNLEpjTyAfmt8GnCnfT87NeQ2BU5eAW1tcsS", passphrase="TestingOneTwoThree") + >>> bip38.decrypt(encrypted_wif="6PRL8jj6dLQjBBJjHMdUKLSNLEpjTyAfmt8GnCnfT87NeQ2BU5eAW1tcsS", passphrase="TestingOneTwoThree", network="testnet") '938jwjergAxARSWx2YSt9nSBWBz24h8gLhv7EUfgEP1wpMLg6iX' - >>> bip38.decrypt(encrypted_wif="6PRL8jj6dLQjBBJjHMdUKLSNLEpjTyAfmt8GnCnfT87NeQ2BU5eAW1tcsS", passphrase="TestingOneTwoThree", detail=True) + >>> bip38.decrypt(encrypted_wif="6PRL8jj6dLQjBBJjHMdUKLSNLEpjTyAfmt8GnCnfT87NeQ2BU5eAW1tcsS", passphrase="TestingOneTwoThree", network="testnet", detail=True) {'wif': '938jwjergAxARSWx2YSt9nSBWBz24h8gLhv7EUfgEP1wpMLg6iX', 'private_key': 'cbf4b9f70470856bb4f40f80b87edb90865997ffee6df315ab166d713af433a5', 'wif_type': 'wif', 'public_key': '04d2ce831dd06e5c1f5b1121ef34c2af4bcb01b126e309234adbc3561b60c9360ea7f23327b49ba7f10d17fad15f068b8807dbbc9e4ace5d4a0b40264eefaf31a4', 'public_key_type': 'uncompressed', 'seed': None, 'address': 'myM3eoxWDWxFe7GYHZw8K21rw7QDNZeDYM', 'lot': None, 'sequence': None} """ + network: str = ( + network if network else self.network + ) encrypted_wif_decode: bytes = decode(encrypted_wif) if len(encrypted_wif_decode) != 43: raise ValueError(f"Invalid encrypted WIF length (expected: 43, got: {len(encrypted_wif_decode)})") @@ -489,14 +509,14 @@ def decrypt( public_key: PublicKey = PrivateKey.from_bytes(private_key).public_key() address: str = P2PKHAddress.encode( public_key=public_key, - address_prefix=self.cryptocurrency.NETWORKS[self.network]["address_prefix"], + address_prefix=self.cryptocurrency.NETWORKS[network]["address_prefix"], public_key_type=public_key_type ) if get_checksum(get_bytes(address, unhexlify=False)) != address_hash: raise ValueError("Incorrect passphrase or password") wif: str = private_key_to_wif( - private_key=private_key, wif_type=wif_type, cryptocurrency=self.cryptocurrency, network=self.network + private_key=private_key, wif_type=wif_type, cryptocurrency=self.cryptocurrency, network=network ) if detail: return dict( @@ -564,12 +584,12 @@ def decrypt( wif_type = "wif-compressed" address: str = P2PKHAddress.encode( public_key=public_key, - address_prefix=self.cryptocurrency.NETWORKS[self.network]["address_prefix"], + address_prefix=self.cryptocurrency.NETWORKS[network]["address_prefix"], public_key_type=public_key_type ) if get_checksum(get_bytes(address, unhexlify=False)) == address_hash: wif: str = private_key_to_wif( - private_key=private_key, wif_type=wif_type, cryptocurrency=self.cryptocurrency, network=self.network + private_key=private_key, wif_type=wif_type, cryptocurrency=self.cryptocurrency, network=network ) lot: Optional[int] = None sequence: Optional[int] = None From d437d1a3967767e99d94dac67ac7cd8406ee1773 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 17 Aug 2024 12:49:02 +0300 Subject: [PATCH 3/6] Add: BIP38 custom exceptions --- bip38/exceptions.py | 51 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 bip38/exceptions.py diff --git a/bip38/exceptions.py b/bip38/exceptions.py new file mode 100644 index 0000000..43b23c1 --- /dev/null +++ b/bip38/exceptions.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +# Copyright © 2020-2024, Meheret Tesfaye Batu +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://opensource.org/license/mit + +from typing import ( + Optional, Any +) + + +class Error(Exception): + + def __init__( + self, + message: str, + detail: Optional[str] = None, + expected: Any = None, + got: Any = None + ): + self._message, self._detail, self._expected, self._got = ( + message, detail, None, f"'{got}'" + ) + + if isinstance(expected, list): + for expect in expected: + if self._expected is None: + self._expected = f"'{expect}'" + else: + self._expected += f", '{expect}'" + else: + self._expected = expected + + def __str__(self): + if self._expected and self._got and self._detail: + return f"{self._message}, (expected: {self._expected} | got: {self._got}) {self._detail}" + elif self._expected and self._got and not self._detail: + return f"{self._message}, (expected: {self._expected} | got: {self._got})" + elif self._detail: + return f"{self._message} {self._detail}" + else: + return f"{self._message}" + + + +class AddressError(Error): + pass + + +class PassphraseError(Error): + pass From 8b9b4603719c31965004f8aeb5ace548520761fc Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 17 Aug 2024 12:50:16 +0300 Subject: [PATCH 4/6] Add: network on BIP38 methods & update exceptions --- bip38/bip38.py | 59 ++++++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/bip38/bip38.py b/bip38/bip38.py index 904e1e9..7558023 100644 --- a/bip38/bip38.py +++ b/bip38/bip38.py @@ -26,9 +26,6 @@ from .crypto import ( double_sha256, get_checksum ) -from .utils import ( - integer_to_bytes, bytes_to_integer, bytes_to_string, get_bytes -) from .const import ( N, MAGIC_LOT_AND_SEQUENCE, @@ -44,6 +41,12 @@ CONFIRMATION_CODE_PREFIX, FLAGS ) +from .exceptions import ( + Error, PassphraseError +) +from .utils import ( + integer_to_bytes, bytes_to_integer, bytes_to_string, get_bytes +) from .wif import ( private_key_to_wif, wif_to_private_key, get_wif_type ) @@ -109,19 +112,19 @@ def intermediate_code( owner_salt: bytes = get_bytes(owner_salt) if owner_salt else os.urandom(8) if len(owner_salt) not in [4, 8]: - raise ValueError(f"Invalid owner salt length (expected: 8 or 16, got: {len(bytes_to_string(owner_salt))})") + raise Error(f"Invalid owner salt length (expected: 8 or 16, got: {len(bytes_to_string(owner_salt))})") if len(owner_salt) == 4 and (not lot or not sequence): - raise ValueError( + raise Error( f"Invalid owner salt length for non lot/sequence (expected: 16, got: {len(bytes_to_string(owner_salt))})") if (lot and not sequence) or (not lot and sequence): - raise ValueError(f"Both lot & sequence are required, got: (lot {lot}) (sequence {sequence})") + raise Error(f"Both lot & sequence are required, got: (lot {lot}) (sequence {sequence})") if lot and sequence: lot, sequence = int(lot), int(sequence) if not 100000 <= lot <= 999999: - raise ValueError(f"Invalid lot, (expected: 100000 <= lot <= 999999, got: {lot})") + raise Error(f"Invalid lot, (expected: 100000 <= lot <= 999999, got: {lot})") if not 0 <= sequence <= 4095: - raise ValueError(f"Invalid lot, (expected: 0 <= sequence <= 4095, got: {sequence})") + raise Error(f"Invalid lot, (expected: 0 <= sequence <= 4095, got: {sequence})") pre_factor: bytes = scrypt.hash(unicodedata.normalize("NFC", passphrase), owner_salt[:4], 16384, 8, 8, 32) owner_entropy: bytes = owner_salt[:4] + integer_to_bytes((lot * 4096 + sequence), 4) @@ -185,7 +188,7 @@ def encrypt(self, wif: str, passphrase: str, network: Optional[str] = None) -> s ))) public_key_type: str = "compressed" else: - raise ValueError("Wrong WIF (Wallet Important Format) type") + raise Error("Wrong WIF (Wallet Important Format) type") address: str = P2PKHAddress.encode( public_key=private_key.public_key(), @@ -247,7 +250,7 @@ def create_new_encrypted_wif( seed_b: bytes = get_bytes(seed) if seed else os.urandom(24) intermediate_decode: bytes = check_decode(intermediate_passphrase) if len(intermediate_decode) != 49: - raise ValueError(f"Invalid intermediate passphrase length (expected: 49, got: {len(intermediate_decode)})") + raise PassphraseError(f"Invalid intermediate passphrase length (expected: 49, got: {len(intermediate_decode)})") magic: bytes = intermediate_decode[:8] owner_entropy: bytes = intermediate_decode[8:16] @@ -259,7 +262,7 @@ def create_new_encrypted_wif( elif public_key_type == "compressed": flag: bytes = integer_to_bytes(MAGIC_LOT_AND_SEQUENCE_COMPRESSED_FLAG) else: - raise ValueError( + raise Error( f"Invalid public key type (expected: 'uncompressed' or 'compressed', got: {public_key_type})") elif magic == integer_to_bytes(MAGIC_NO_LOT_AND_SEQUENCE): if public_key_type == "uncompressed": @@ -267,17 +270,17 @@ def create_new_encrypted_wif( elif public_key_type == "compressed": flag: bytes = integer_to_bytes(MAGIC_NO_LOT_AND_SEQUENCE_COMPRESSED_FLAG) else: - raise ValueError( + raise Error( f"Invalid public key type (expected: 'uncompressed' or 'compressed', got: {public_key_type})") else: - raise ValueError( + raise Error( f"Invalid magic (expected: {bytes_to_string(integer_to_bytes(MAGIC_LOT_AND_SEQUENCE))}/" f"{bytes_to_string(integer_to_bytes(MAGIC_NO_LOT_AND_SEQUENCE))}, got: {bytes_to_string(magic)})" ) factor_b: bytes = double_sha256(seed_b) if not 0 < bytes_to_integer(factor_b) < N: - raise ValueError("Invalid EC encrypted WIF (Wallet Important Format)") + raise Error("Invalid EC encrypted WIF (Wallet Important Format)") public_key: PublicKey = PublicKey.from_point( PublicKey.from_bytes(pass_point).point() * bytes_to_integer(factor_b) @@ -315,10 +318,10 @@ def create_new_encrypted_wif( bytes_to_integer(point_b[17:]) ^ bytes_to_integer(derived_half_2) )) encrypted_point_b: bytes = ( - point_b_prefix + point_b_half_1 + point_b_half_2 + point_b_prefix + point_b_half_1 + point_b_half_2 ) confirmation_code: str = ensure_string(check_encode(( - integer_to_bytes(CONFIRMATION_CODE_PREFIX) + flag + address_hash + owner_entropy + encrypted_point_b + integer_to_bytes(CONFIRMATION_CODE_PREFIX) + flag + address_hash + owner_entropy + encrypted_point_b ))) return dict( @@ -363,12 +366,12 @@ def confirm_code( confirmation_code_decode: bytes = check_decode(confirmation_code) if len(confirmation_code_decode) != 51: - raise ValueError(f"Invalid confirmation code length (expected: 102, got: {len(confirmation_code_decode)})") + raise Error(f"Invalid confirmation code length (expected: 102, got: {len(confirmation_code_decode)})") prefix_length: int = len(integer_to_bytes(CONFIRMATION_CODE_PREFIX)) prefix_got: bytes = confirmation_code_decode[:prefix_length] if integer_to_bytes(CONFIRMATION_CODE_PREFIX) != prefix_got: - raise ValueError(f"Invalid confirmation code prefix (expected: {prefix_length}, got: {prefix_got})") + raise Error(f"Invalid confirmation code prefix (expected: {prefix_length}, got: {prefix_got})") flag: bytes = confirmation_code_decode[5:6] address_hash: bytes = confirmation_code_decode[6:10] @@ -386,7 +389,7 @@ def confirm_code( if lot_and_sequence: pass_factor: bytes = double_sha256(pass_factor + owner_entropy) if bytes_to_integer(pass_factor) == 0 or bytes_to_integer(pass_factor) >= N: - raise ValueError("Invalid EC encrypted WIF (Wallet Important Format)") + raise Error("Invalid EC encrypted WIF (Wallet Important Format)") pass_point: bytes = PrivateKey.from_bytes(pass_factor).public_key().raw_compressed() salt: bytes = address_hash + owner_entropy @@ -433,7 +436,7 @@ def confirm_code( sequence=sequence ) return address - raise ValueError("Incorrect passphrase or password") + raise PassphraseError("Incorrect passphrase or password") def decrypt( self, encrypted_wif: str, passphrase: str, network: Optional[str] = None, detail: bool = False @@ -471,7 +474,7 @@ def decrypt( ) encrypted_wif_decode: bytes = decode(encrypted_wif) if len(encrypted_wif_decode) != 43: - raise ValueError(f"Invalid encrypted WIF length (expected: 43, got: {len(encrypted_wif_decode)})") + raise Error(f"Invalid encrypted WIF length (expected: 43, got: {len(encrypted_wif_decode)})") prefix: bytes = encrypted_wif_decode[:2] flag: bytes = encrypted_wif_decode[2:3] @@ -486,7 +489,7 @@ def decrypt( wif_type: Literal["wif", "wif-compressed"] = "wif-compressed" public_key_type: str = "compressed" else: - raise ValueError( + raise Error( f"Invalid flag (expected: {bytes_to_string(integer_to_bytes(NO_EC_MULTIPLIED_WIF_FLAG))} or " f"{bytes_to_string(integer_to_bytes(NO_EC_MULTIPLIED_WIF_COMPRESSED_FLAG))}, got: {bytes_to_string(flag)})" ) @@ -504,7 +507,7 @@ def decrypt( bytes_to_integer(decrypted_half_1 + decrypted_half_2) ^ bytes_to_integer(derived_half_1) ) if bytes_to_integer(private_key) == 0 or bytes_to_integer(private_key) >= N: - raise ValueError("Invalid Non-EC encrypted WIF (Wallet Important Format)") + raise Error("Invalid Non-EC encrypted WIF (Wallet Important Format)") public_key: PublicKey = PrivateKey.from_bytes(private_key).public_key() address: str = P2PKHAddress.encode( @@ -513,7 +516,7 @@ def decrypt( public_key_type=public_key_type ) if get_checksum(get_bytes(address, unhexlify=False)) != address_hash: - raise ValueError("Incorrect passphrase or password") + raise PassphraseError("Incorrect passphrase or password") wif: str = private_key_to_wif( private_key=private_key, wif_type=wif_type, cryptocurrency=self.cryptocurrency, network=network @@ -548,7 +551,7 @@ def decrypt( if lot_and_sequence: pass_factor: bytes = double_sha256(pass_factor + owner_entropy) if bytes_to_integer(pass_factor) == 0 or bytes_to_integer(pass_factor) >= N: - raise ValueError("Invalid EC encrypted WIF (Wallet Important Format)") + raise Error("Invalid EC encrypted WIF (Wallet Important Format)") pre_public_key: PublicKey = PrivateKey.from_bytes(pass_factor).public_key() salt = address_hash + owner_entropy @@ -570,7 +573,7 @@ def decrypt( factor_b: bytes = double_sha256(seed_b) if bytes_to_integer(factor_b) == 0 or bytes_to_integer(factor_b) >= N: - raise ValueError("Invalid EC encrypted WIF (Wallet Important Format)") + raise Error("Invalid EC encrypted WIF (Wallet Important Format)") # multiply private key private_key: bytes = integer_to_bytes( @@ -609,9 +612,9 @@ def decrypt( sequence=sequence ) return wif - raise ValueError("Incorrect passphrase or password") + raise PassphraseError("Incorrect passphrase or password") else: - raise ValueError( + raise Error( f"Invalid prefix (expected: {bytes_to_string(integer_to_bytes(NO_EC_MULTIPLIED_PRIVATE_KEY_PREFIX))} or " f"{bytes_to_string(integer_to_bytes(EC_MULTIPLIED_PRIVATE_KEY_PREFIX))}, got: {bytes_to_string(prefix)})" ) From f87cb1808c08a1071f4827501c88625423f9bd8c Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 17 Aug 2024 12:50:44 +0300 Subject: [PATCH 5/6] Add: update p2pkh-address exceptions --- bip38/p2pkh_address.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bip38/p2pkh_address.py b/bip38/p2pkh_address.py index 8517851..f2e9dc4 100644 --- a/bip38/p2pkh_address.py +++ b/bip38/p2pkh_address.py @@ -14,6 +14,7 @@ from .secp256k1 import PublicKey from .cryptocurrencies import Bitcoin from .crypto import hash160 +from .exceptions import AddressError from .utils import ( get_bytes, integer_to_bytes, bytes_to_string ) @@ -87,10 +88,10 @@ def decode(cls, address: str, **kwargs: Any) -> str: expected_length: int = 20 + len(address_prefix) if len(address_decode) != expected_length: - raise ValueError(f"Invalid length (expected: {expected_length}, got: {len(address_decode)})") + raise AddressError(f"Invalid length (expected: {expected_length}, got: {len(address_decode)})") prefix_got: bytes = address_decode[:len(address_prefix)] if address_prefix != prefix_got: - raise ValueError(f"Invalid prefix (expected: {address_prefix}, got: {prefix_got})") + raise AddressError(f"Invalid prefix (expected: {address_prefix}, got: {prefix_got})") return bytes_to_string(address_decode[len(address_prefix):]) From 0d27aca392dfd09a4f592244472246060e742c48 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 17 Aug 2024 13:02:18 +0300 Subject: [PATCH 6/6] Bump: Python-BIP38 into v1.1.0 package --- bip38/info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bip38/info.py b/bip38/info.py index afa2c06..e8f3f52 100644 --- a/bip38/info.py +++ b/bip38/info.py @@ -7,7 +7,7 @@ from typing import List __name__: str = "bip38" -__version__: str = "v1.0.1" +__version__: str = "v1.1.0" __license__: str = "MIT" __author__: str = "Meheret Tesfaye Batu" __email__: str = "meherett.batu@gmail.com"