From c1ca7044d2eeca9e799dd33b3a80ffbdf5438762 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Fri, 6 Aug 2021 15:44:35 +0300 Subject: [PATCH 1/2] Moved decodeWalletImportFormat() from shared to highlevelcrypto, not addresses, where it's supposed to be because it uses pyelliptic.arithmetic, addresses.decodeBase58() returns int which needs to be encoded. Defined encodeWalletImportFormat() and replaced all uses. --- src/class_addressGenerator.py | 40 ++++------------ src/class_singleWorker.py | 64 +++++++++++++------------- src/highlevelcrypto.py | 34 +++++++++++++- src/shared.py | 87 +++++++++++++---------------------- 4 files changed, 107 insertions(+), 118 deletions(-) diff --git a/src/class_addressGenerator.py b/src/class_addressGenerator.py index 06a0521a..d42469c3 100644 --- a/src/class_addressGenerator.py +++ b/src/class_addressGenerator.py @@ -16,7 +16,6 @@ from addresses import decodeAddress, encodeAddress, encodeVarint from bmconfigparser import config from fallback import RIPEMD160Hash from network import StoppableThread -from pyelliptic import arithmetic from pyelliptic.openssl import OpenSSL from tr import _translate @@ -164,20 +163,10 @@ class addressGenerator(StoppableThread): address = encodeAddress( addressVersionNumber, streamNumber, ripe) - # An excellent way for us to store our keys - # is in Wallet Import Format. Let us convert now. - # https://en.bitcoin.it/wiki/Wallet_import_format - privSigningKey = b'\x80' + potentialPrivSigningKey - checksum = hashlib.sha256(hashlib.sha256( - privSigningKey).digest()).digest()[0:4] - privSigningKeyWIF = arithmetic.changebase( - privSigningKey + checksum, 256, 58) - - privEncryptionKey = b'\x80' + potentialPrivEncryptionKey - checksum = hashlib.sha256(hashlib.sha256( - privEncryptionKey).digest()).digest()[0:4] - privEncryptionKeyWIF = arithmetic.changebase( - privEncryptionKey + checksum, 256, 58) + privSigningKeyWIF = highlevelcrypto.encodeWalletImportFormat( + potentialPrivSigningKey) + privEncryptionKeyWIF = highlevelcrypto.encodeWalletImportFormat( + potentialPrivEncryptionKey) config.add_section(address) config.set(address, 'label', label) @@ -303,21 +292,12 @@ class addressGenerator(StoppableThread): saveAddressToDisk = False if saveAddressToDisk and live: - # An excellent way for us to store our keys is - # in Wallet Import Format. Let us convert now. - # https://en.bitcoin.it/wiki/Wallet_import_format - privSigningKey = b'\x80' + potentialPrivSigningKey - checksum = hashlib.sha256(hashlib.sha256( - privSigningKey).digest()).digest()[0:4] - privSigningKeyWIF = arithmetic.changebase( - privSigningKey + checksum, 256, 58) - - privEncryptionKey = b'\x80' + \ - potentialPrivEncryptionKey - checksum = hashlib.sha256(hashlib.sha256( - privEncryptionKey).digest()).digest()[0:4] - privEncryptionKeyWIF = arithmetic.changebase( - privEncryptionKey + checksum, 256, 58) + privSigningKeyWIF = \ + highlevelcrypto.encodeWalletImportFormat( + potentialPrivSigningKey) + privEncryptionKeyWIF = \ + highlevelcrypto.encodeWalletImportFormat( + potentialPrivEncryptionKey) try: config.add_section(address) diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index 60cfd9d7..d9ee69e7 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -197,15 +197,18 @@ class singleWorker(StoppableThread): self.logger.info("Quitting...") def _getKeysForAddress(self, address): - privSigningKeyBase58 = config.get( - address, 'privsigningkey') - privEncryptionKeyBase58 = config.get( - address, 'privencryptionkey') + try: + privSigningKeyBase58 = config.get(address, 'privsigningkey') + privEncryptionKeyBase58 = config.get(address, 'privencryptionkey') + except (configparser.NoSectionError, configparser.NoOptionError): + self.logger.error( + 'Could not read or decode privkey for address %s', address) + raise ValueError - privSigningKeyHex = hexlify(shared.decodeWalletImportFormat( - privSigningKeyBase58)) - privEncryptionKeyHex = hexlify(shared.decodeWalletImportFormat( - privEncryptionKeyBase58)) + privSigningKeyHex = hexlify( + highlevelcrypto.decodeWalletImportFormat(privSigningKeyBase58)) + privEncryptionKeyHex = hexlify( + highlevelcrypto.decodeWalletImportFormat(privEncryptionKeyBase58)) # The \x04 on the beginning of the public keys are not sent. # This way there is only one acceptable way to encode @@ -256,9 +259,7 @@ class singleWorker(StoppableThread): message once it is done with the POW""" # Look up my stream number based on my address hash myAddress = shared.myAddressesByHash[adressHash] - # status - _, addressVersionNumber, streamNumber, adressHash = ( - decodeAddress(myAddress)) + addressVersionNumber, streamNumber = decodeAddress(myAddress)[1:3] # 28 days from now plus or minus five minutes TTL = int(28 * 24 * 60 * 60 + helper_random.randomrandrange(-300, 300)) @@ -271,17 +272,15 @@ class singleWorker(StoppableThread): payload += protocol.getBitfield(myAddress) try: - # privSigningKeyHex, privEncryptionKeyHex - _, _, pubSigningKey, pubEncryptionKey = \ - self._getKeysForAddress(myAddress) - except (configparser.NoSectionError, configparser.NoOptionError) as err: - self.logger.warning("Section or Option did not found: %s", err) - except Exception as err: + pubSigningKey, pubEncryptionKey = self._getKeysForAddress( + myAddress)[2:] + except ValueError: + return + except Exception: # pylint:disable=broad-exception-caught self.logger.error( 'Error within doPOWForMyV2Pubkey. Could not read' ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) + ' address. %s\n', exc_info=True) return payload += pubSigningKey + pubEncryptionKey @@ -320,8 +319,8 @@ class singleWorker(StoppableThread): try: myAddress = shared.myAddressesByHash[adressHash] except KeyError: - # The address has been deleted. - self.logger.warning("Can't find %s in myAddressByHash", hexlify(adressHash)) + self.logger.warning( # The address has been deleted. + "Can't find %s in myAddressByHash", hexlify(adressHash)) return if config.safeGetBoolean(myAddress, 'chan'): self.logger.info('This is a chan address. Not sending pubkey.') @@ -353,14 +352,13 @@ class singleWorker(StoppableThread): # , privEncryptionKeyHex privSigningKeyHex, _, pubSigningKey, pubEncryptionKey = \ self._getKeysForAddress(myAddress) - except (configparser.NoSectionError, configparser.NoOptionError) as err: - self.logger.warning("Section or Option did not found: %s", err) - except Exception as err: + except ValueError: + return + except Exception: # pylint:disable=broad-exception-caught self.logger.error( 'Error within sendOutOrStoreMyV3Pubkey. Could not read' ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) + ' address. %s\n', exc_info=True) return payload += pubSigningKey + pubEncryptionKey @@ -428,14 +426,13 @@ class singleWorker(StoppableThread): # , privEncryptionKeyHex privSigningKeyHex, _, pubSigningKey, pubEncryptionKey = \ self._getKeysForAddress(myAddress) - except (configparser.NoSectionError, configparser.NoOptionError) as err: - self.logger.warning("Section or Option did not found: %s", err) - except Exception as err: + except ValueError: + return + except Exception: # pylint:disable=broad-exception-caught self.logger.error( 'Error within sendOutOrStoreMyV4Pubkey. Could not read' ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) + ' address. %s\n', exc_info=True) return dataToEncrypt += pubSigningKey + pubEncryptionKey @@ -1118,8 +1115,9 @@ class singleWorker(StoppableThread): ' from the keys.dat file for our own address. %s\n', err) continue - privEncryptionKeyHex = hexlify(shared.decodeWalletImportFormat( - privEncryptionKeyBase58)) + privEncryptionKeyHex = hexlify( + highlevelcrypto.decodeWalletImportFormat( + privEncryptionKeyBase58)) pubEncryptionKeyBase256 = unhexlify(highlevelcrypto.privToPub( privEncryptionKeyHex))[1:] requiredAverageProofOfWorkNonceTrialsPerByte = \ diff --git a/src/highlevelcrypto.py b/src/highlevelcrypto.py index 1bdb1593..a52ac77f 100644 --- a/src/highlevelcrypto.py +++ b/src/highlevelcrypto.py @@ -7,6 +7,7 @@ High level cryptographic functions based on `.pyelliptic` OpenSSL bindings. `More discussion. `_ """ +import hashlib from binascii import hexlify import pyelliptic @@ -14,7 +15,38 @@ from pyelliptic import OpenSSL from pyelliptic import arithmetic as a -__all__ = ['encrypt', 'makeCryptor', 'pointMult', 'privToPub', 'sign', 'verify'] +__all__ = [ + 'decodeWalletImportFormat', 'encodeWalletImportFormat', + 'encrypt', 'makeCryptor', 'pointMult', 'privToPub', 'sign', 'verify'] + + +# WIF (uses arithmetic ): +def decodeWalletImportFormat(WIFstring): + """ + Convert private key from base58 that's used in the config file to + 8-bit binary string. + """ + fullString = a.changebase(WIFstring, 58, 256) + privkey = fullString[:-4] + if fullString[-4:] != \ + hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]: + raise ValueError('Checksum failed') + elif privkey[0:1] == b'\x80': # checksum passed + return privkey[1:] + + raise ValueError('No hex 80 prefix') + + +# An excellent way for us to store our keys +# is in Wallet Import Format. Let us convert now. +# https://en.bitcoin.it/wiki/Wallet_import_format +def encodeWalletImportFormat(privKey): + """ + Convert private key from binary 8-bit string into base58check WIF string. + """ + privKey = b'\x80' + privKey + checksum = hashlib.sha256(hashlib.sha256(privKey).digest()).digest()[0:4] + return a.changebase(privKey + checksum, 256, 58) def makeCryptor(privkey, curve='secp256k1'): diff --git a/src/shared.py b/src/shared.py index cb7968f6..114bd916 100644 --- a/src/shared.py +++ b/src/shared.py @@ -23,8 +23,6 @@ from bmconfigparser import config from debug import logger from helper_sql import sqlQuery -from pyelliptic import arithmetic - myECCryptorObjects = {} MyECSubscriptionCryptorObjects = {} @@ -76,35 +74,6 @@ def isAddressInMyAddressBookSubscriptionsListOrWhitelist(address): return False -def decodeWalletImportFormat(WIFstring): - # pylint: disable=inconsistent-return-statements - """ - Convert private key from base58 that's used in the config file to - 8-bit binary string - """ - fullString = arithmetic.changebase(WIFstring, 58, 256) - privkey = fullString[:-4] - if fullString[-4:] != \ - hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]: - logger.critical( - 'Major problem! When trying to decode one of your' - ' private keys, the checksum failed. Here are the first' - ' 6 characters of the PRIVATE key: %s', - str(WIFstring)[:6] - ) - os._exit(0) # pylint: disable=protected-access - # return "" - elif privkey[0] == '\x80': # checksum passed - return privkey[1:] - - logger.critical( - 'Major problem! When trying to decode one of your private keys,' - ' the checksum passed but the key doesn\'t begin with hex 80.' - ' Here is the PRIVATE key: %s', WIFstring - ) - os._exit(0) # pylint: disable=protected-access - - def reloadMyAddressHashes(): """Reload keys for user's addresses from the config file""" logger.debug('reloading keys from keys.dat file') @@ -118,29 +87,39 @@ def reloadMyAddressHashes(): hasEnabledKeys = False for addressInKeysFile in config.addresses(): isEnabled = config.getboolean(addressInKeysFile, 'enabled') - if isEnabled: - hasEnabledKeys = True - # status - addressVersionNumber, streamNumber, hashobj = decodeAddress(addressInKeysFile)[1:] - if addressVersionNumber in (2, 3, 4): - # Returns a simple 32 bytes of information encoded - # in 64 Hex characters, or null if there was an error. - privEncryptionKey = hexlify(decodeWalletImportFormat( - config.get(addressInKeysFile, 'privencryptionkey'))) - # It is 32 bytes encoded as 64 hex characters - if len(privEncryptionKey) == 64: - myECCryptorObjects[hashobj] = \ - highlevelcrypto.makeCryptor(privEncryptionKey) - myAddressesByHash[hashobj] = addressInKeysFile - tag = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + hashobj).digest()).digest()[32:] - myAddressesByTag[tag] = addressInKeysFile - else: - logger.error( - 'Error in reloadMyAddressHashes: Can\'t handle' - ' address versions other than 2, 3, or 4.' - ) + if not isEnabled: + continue + + hasEnabledKeys = True + + addressVersionNumber, streamNumber, hashobj = decodeAddress( + addressInKeysFile)[1:] + if addressVersionNumber not in (2, 3, 4): + logger.error( + 'Error in reloadMyAddressHashes: Can\'t handle' + ' address versions other than 2, 3, or 4.') + continue + + # Returns a simple 32 bytes of information encoded in 64 Hex characters + try: + privEncryptionKey = hexlify( + highlevelcrypto.decodeWalletImportFormat( + config.get(addressInKeysFile, 'privencryptionkey') + )) + except ValueError: + logger.error( + 'Error in reloadMyAddressHashes: failed to decode' + ' one of the private keys for address %s', addressInKeysFile) + continue + # It is 32 bytes encoded as 64 hex characters + if len(privEncryptionKey) == 64: + myECCryptorObjects[hashobj] = \ + highlevelcrypto.makeCryptor(privEncryptionKey) + myAddressesByHash[hashobj] = addressInKeysFile + tag = hashlib.sha512(hashlib.sha512( + encodeVarint(addressVersionNumber) + + encodeVarint(streamNumber) + hashobj).digest()).digest()[32:] + myAddressesByTag[tag] = addressInKeysFile if not keyfileSecure: fixSensitiveFilePermissions(os.path.join( -- 2.45.1 From feaee606321dbae2fc2b796784e3e38d046e3ca8 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Thu, 29 Jul 2021 16:33:33 +0300 Subject: [PATCH 2/2] Add a test for WIF decoding and encoding --- src/tests/samples.py | 10 ++++++++++ src/tests/test_addresses.py | 29 +++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/tests/samples.py b/src/tests/samples.py index e33c5f50..42a14f58 100644 --- a/src/tests/samples.py +++ b/src/tests/samples.py @@ -73,3 +73,13 @@ sample_sig = unhexlify( sample_sig_sha1 = unhexlify( '30460221008ad234687d1bdc259932e28ea6ee091b88b0900d8134902aa8c2fd7f016b96e' 'd022100dafb94e28322c2fa88878f9dcbf0c2d33270466ab3bbffaec3dca0a2d1ef9354') + +# [chan] bitmessage +sample_wif_privsigningkey = unhexlify( + b'a2e8b841a531c1c558ee0680c396789c7a2ea3ac4795ae3f000caf9fe367d144') +sample_wif_privencryptionkey = unhexlify( + b'114ec0e2dca24a826a0eed064b0405b0ac148abc3b1d52729697f4d7b873fdc6') +sample_privsigningkey_wif = \ + b'5K42shDERM5g7Kbi3JT5vsAWpXMqRhWZpX835M2pdSoqQQpJMYm' +sample_privencryptionkey_wif = \ + b'5HwugVWm31gnxtoYcvcK7oywH2ezYTh6Y4tzRxsndAeMi6NHqpA' diff --git a/src/tests/test_addresses.py b/src/tests/test_addresses.py index 8f9c283d..dd989562 100644 --- a/src/tests/test_addresses.py +++ b/src/tests/test_addresses.py @@ -2,12 +2,14 @@ import unittest from binascii import unhexlify -from pybitmessage import addresses +from pybitmessage import addresses, highlevelcrypto from .samples import ( sample_address, sample_daddr3_512, sample_daddr4_512, sample_deterministic_addr4, sample_deterministic_addr3, - sample_deterministic_ripe, sample_ripe) + sample_deterministic_ripe, sample_ripe, + sample_privsigningkey_wif, sample_privencryptionkey_wif, + sample_wif_privsigningkey, sample_wif_privencryptionkey) sample_addr3 = sample_deterministic_addr3.split('-')[1] sample_addr4 = sample_deterministic_addr4.split('-')[1] @@ -59,3 +61,26 @@ class TestAddresses(unittest.TestCase): sample_addr4, addresses.encodeBase58(sample_daddr4_512)) self.assertEqual( sample_addr3, addresses.encodeBase58(sample_daddr3_512)) + + def test_wif(self): + """Decode WIFs of [chan] bitmessage and check the keys""" + self.assertEqual( + sample_wif_privsigningkey, + highlevelcrypto.decodeWalletImportFormat( + sample_privsigningkey_wif)) + self.assertEqual( + sample_wif_privencryptionkey, + highlevelcrypto.decodeWalletImportFormat( + sample_privencryptionkey_wif)) + self.assertEqual( + sample_privsigningkey_wif, + highlevelcrypto.encodeWalletImportFormat( + sample_wif_privsigningkey)) + self.assertEqual( + sample_privencryptionkey_wif, + highlevelcrypto.encodeWalletImportFormat( + sample_wif_privencryptionkey)) + + with self.assertRaises(ValueError): + highlevelcrypto.decodeWalletImportFormat( + sample_privencryptionkey_wif[:-2]) -- 2.45.1