diff --git a/src/addresses.py b/src/addresses.py index e48873a1..885c1f64 100644 --- a/src/addresses.py +++ b/src/addresses.py @@ -2,11 +2,16 @@ Operations with addresses """ # pylint: disable=inconsistent-return-statements -import hashlib + import logging from binascii import hexlify, unhexlify from struct import pack, unpack +try: + from highlevelcrypto import double_sha512 +except ImportError: + from .highlevelcrypto import double_sha512 + logger = logging.getLogger('default') @@ -134,15 +139,6 @@ def decodeVarint(data): return (encodedValue, 9) -def calculateInventoryHash(data): - """Calculate inventory hash from object data""" - sha = hashlib.new('sha512') - sha2 = hashlib.new('sha512') - sha.update(data) - sha2.update(sha.digest()) - return sha2.digest()[0:32] - - def encodeAddress(version, stream, ripe): """Convert ripe to address""" if version >= 2 and version < 4: @@ -166,12 +162,7 @@ def encodeAddress(version, stream, ripe): storedBinaryData = encodeVarint(version) + encodeVarint(stream) + ripe # Generate the checksum - sha = hashlib.new('sha512') - sha.update(storedBinaryData) - currentHash = sha.digest() - sha = hashlib.new('sha512') - sha.update(currentHash) - checksum = sha.digest()[0:4] + checksum = double_sha512(storedBinaryData)[0:4] # FIXME: encodeBase58 should take binary data, to reduce conversions # encodeBase58(storedBinaryData + checksum) @@ -207,13 +198,7 @@ def decodeAddress(address): data = unhexlify(hexdata) checksum = data[-4:] - sha = hashlib.new('sha512') - sha.update(data[:-4]) - currentHash = sha.digest() - sha = hashlib.new('sha512') - sha.update(currentHash) - - if checksum != sha.digest()[0:4]: + if checksum != double_sha512(data[:-4])[0:4]: status = 'checksumfailed' return status, 0, 0, '' diff --git a/src/api.py b/src/api.py index de220cc4..536768e8 100644 --- a/src/api.py +++ b/src/api.py @@ -71,6 +71,8 @@ from binascii import hexlify, unhexlify from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer from struct import pack +from six.moves import queue + import defaults import helper_inbox import helper_sent @@ -82,17 +84,17 @@ import shutdown import state from addresses import ( addBMIfNotPresent, - calculateInventoryHash, decodeAddress, decodeVarint, varintDecodeError ) from bmconfigparser import BMConfigParser from debug import logger -from helper_sql import SqlBulkExecute, sqlExecute, sqlQuery, sqlStoredProcedure, sql_ready +from helper_sql import ( + SqlBulkExecute, sqlExecute, sqlQuery, sql_ready, sqlStoredProcedure) +from highlevelcrypto import calculateInventoryHash from inventory import Inventory from network.threads import StoppableThread -from six.moves import queue from version import softwareVersion try: # TODO: write tests for XML vulnerabilities diff --git a/src/class_addressGenerator.py b/src/class_addressGenerator.py index 25b0c5df..06ca16d4 100644 --- a/src/class_addressGenerator.py +++ b/src/class_addressGenerator.py @@ -15,8 +15,6 @@ from addresses import decodeAddress, encodeAddress, encodeVarint from bmconfigparser import BMConfigParser from fallback import RIPEMD160Hash from network import StoppableThread -from pyelliptic import arithmetic -from pyelliptic.openssl import OpenSSL from six.moves import configparser, queue @@ -129,17 +127,13 @@ class addressGenerator(StoppableThread): # the \x00 or \x00\x00 bytes thus making the address shorter. startTime = time.time() numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix = 0 - potentialPrivSigningKey = OpenSSL.rand(32) - potentialPubSigningKey = highlevelcrypto.pointMult( - potentialPrivSigningKey) + privSigningKey, pubSigningKey = highlevelcrypto.random_keys() while True: numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix += 1 - potentialPrivEncryptionKey = OpenSSL.rand(32) - potentialPubEncryptionKey = highlevelcrypto.pointMult( - potentialPrivEncryptionKey) + potentialPrivEncryptionKey, potentialPubEncryptionKey = \ + highlevelcrypto.random_keys() sha = hashlib.new('sha512') - sha.update( - potentialPubSigningKey + potentialPubEncryptionKey) + sha.update(pubSigningKey + potentialPubEncryptionKey) ripe = RIPEMD160Hash(sha.digest()).digest() if ( ripe[:numberOfNullBytesDemandedOnFrontOfRipeHash] @@ -163,20 +157,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( + privSigningKey) + privEncryptionKeyWIF = highlevelcrypto.encodeWalletImportFormat( + potentialPrivEncryptionKey) BMConfigParser().add_section(address) BMConfigParser().set(address, 'label', label) @@ -246,18 +230,15 @@ class addressGenerator(StoppableThread): numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix = 0 while True: numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix += 1 - potentialPrivSigningKey = hashlib.sha512( - deterministicPassphrase - + encodeVarint(signingKeyNonce) - ).digest()[:32] - potentialPrivEncryptionKey = hashlib.sha512( - deterministicPassphrase - + encodeVarint(encryptionKeyNonce) - ).digest()[:32] - potentialPubSigningKey = highlevelcrypto.pointMult( - potentialPrivSigningKey) - potentialPubEncryptionKey = highlevelcrypto.pointMult( - potentialPrivEncryptionKey) + potentialPrivSigningKey, potentialPubSigningKey = \ + highlevelcrypto.deterministic_keys( + deterministicPassphrase, + encodeVarint(signingKeyNonce)) + potentialPrivEncryptionKey, potentialPubEncryptionKey = \ + highlevelcrypto.deterministic_keys( + deterministicPassphrase, + encodeVarint(encryptionKeyNonce)) + signingKeyNonce += 2 encryptionKeyNonce += 2 sha = hashlib.new('sha512') @@ -300,21 +281,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: BMConfigParser().add_section(address) @@ -367,10 +339,10 @@ class addressGenerator(StoppableThread): highlevelcrypto.makeCryptor( hexlify(potentialPrivEncryptionKey)) shared.myAddressesByHash[ripe] = address - tag = hashlib.sha512(hashlib.sha512( + tag = highlevelcrypto.double_sha512( encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + ripe - ).digest()).digest()[32:] + )[32:] shared.myAddressesByTag[tag] = address if addressVersionNumber == 3: # If this is a chan address, diff --git a/src/class_objectProcessor.py b/src/class_objectProcessor.py index 1bacf639..86ed2bcc 100644 --- a/src/class_objectProcessor.py +++ b/src/class_objectProcessor.py @@ -23,7 +23,7 @@ import queues import shared import state from addresses import ( - calculateInventoryHash, decodeAddress, decodeVarint, + decodeAddress, decodeVarint, encodeAddress, encodeVarint, varintDecodeError ) from bmconfigparser import BMConfigParser @@ -450,7 +450,7 @@ class objectProcessor(threading.Thread): streamNumberAsClaimedByMsg, streamNumberAsClaimedByMsgLength = \ decodeVarint(data[readPosition:readPosition + 9]) readPosition += streamNumberAsClaimedByMsgLength - inventoryHash = calculateInventoryHash(data) + inventoryHash = highlevelcrypto.calculateInventoryHash(data) initialDecryptionSuccessful = False # This is not an acknowledgement bound for me. See if it is a message @@ -580,8 +580,7 @@ class objectProcessor(threading.Thread): helper_bitcoin.calculateTestnetAddressFromPubkey(pubSigningKey) ) # Used to detect and ignore duplicate messages in our inbox - sigHash = hashlib.sha512( - hashlib.sha512(signature).digest()).digest()[32:] + sigHash = highlevelcrypto.double_sha512(signature)[32:] # calculate the fromRipe. sha = hashlib.new('sha512') @@ -751,7 +750,7 @@ class objectProcessor(threading.Thread): state.numberOfBroadcastsProcessed += 1 queues.UISignalQueue.put(( 'updateNumberOfBroadcastsProcessed', 'no data')) - inventoryHash = calculateInventoryHash(data) + inventoryHash = highlevelcrypto.calculateInventoryHash(data) readPosition = 20 # bypass the nonce, time, and object type broadcastVersion, broadcastVersionLength = decodeVarint( data[readPosition:readPosition + 9]) @@ -885,10 +884,10 @@ class objectProcessor(threading.Thread): ' itself. Ignoring message.' ) elif broadcastVersion == 5: - calculatedTag = hashlib.sha512(hashlib.sha512( + calculatedTag = highlevelcrypto.double_sha512( encodeVarint(sendersAddressVersion) + encodeVarint(sendersStream) + calculatedRipe - ).digest()).digest()[32:] + )[32:] if calculatedTag != embeddedTag: return logger.debug( 'The tag and encryption key used to encrypt this' @@ -918,8 +917,7 @@ class objectProcessor(threading.Thread): return logger.debug('ECDSA verify passed') # Used to detect and ignore duplicate messages in our inbox - sigHash = hashlib.sha512( - hashlib.sha512(signature).digest()).digest()[32:] + sigHash = highlevelcrypto.double_sha512(signature)[32:] fromAddress = encodeAddress( sendersAddressVersion, sendersStream, calculatedRipe) @@ -993,10 +991,10 @@ class objectProcessor(threading.Thread): # Let us create the tag from the address and see if we were waiting # for it. elif addressVersion >= 4: - tag = hashlib.sha512(hashlib.sha512( + tag = highlevelcrypto.double_sha512( encodeVarint(addressVersion) + encodeVarint(streamNumber) + ripe - ).digest()).digest()[32:] + )[32:] if tag in state.neededPubkeys: del state.neededPubkeys[tag] self.sendMessages(address) diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index fea842ea..c3230107 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -25,9 +25,7 @@ import queues import shared import state import tr -from addresses import ( - calculateInventoryHash, decodeAddress, decodeVarint, encodeVarint -) +from addresses import decodeAddress, decodeVarint, encodeVarint from bmconfigparser import BMConfigParser from helper_sql import sqlExecute, sqlQuery from inventory import Inventory @@ -50,6 +48,8 @@ class singleWorker(StoppableThread): def __init__(self): super(singleWorker, self).__init__(name="singleWorker") + self.digestAlg = BMConfigParser().safeGet( + 'bitmessagesettings', 'digestalg', 'sha256') proofofwork.init() def stopThread(self): @@ -73,18 +73,16 @@ class singleWorker(StoppableThread): queryreturn = sqlQuery( '''SELECT DISTINCT toaddress FROM sent''' ''' WHERE (status='awaitingpubkey' AND folder='sent')''') - for row in queryreturn: - toAddress, = row - # toStatus - _, toAddressVersionNumber, toStreamNumber, toRipe = \ - decodeAddress(toAddress) + for toAddress, in queryreturn: + toAddressVersionNumber, toStreamNumber, toRipe = \ + decodeAddress(toAddress)[1:] if toAddressVersionNumber <= 3: state.neededPubkeys[toAddress] = 0 elif toAddressVersionNumber >= 4: - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( + doubleHashOfAddressData = highlevelcrypto.double_sha512( encodeVarint(toAddressVersionNumber) + encodeVarint(toStreamNumber) + toRipe - ).digest()).digest() + ) # Note that this is the first half of the sha512 hash. privEncryptionKey = doubleHashOfAddressData[:32] tag = doubleHashOfAddressData[32:] @@ -195,15 +193,20 @@ class singleWorker(StoppableThread): self.logger.info("Quitting...") def _getKeysForAddress(self, address): - privSigningKeyBase58 = BMConfigParser().get( - address, 'privsigningkey') - privEncryptionKeyBase58 = BMConfigParser().get( - address, 'privencryptionkey') + try: + privSigningKeyBase58 = BMConfigParser().get( + address, 'privsigningkey') + privEncryptionKeyBase58 = BMConfigParser().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 @@ -254,9 +257,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)) @@ -269,18 +270,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: - self.logger.error( + pubSigningKey, pubEncryptionKey = self._getKeysForAddress( + myAddress)[2:] + except ValueError: + return + except Exception: + return self.logger.error( 'Error within doPOWForMyV2Pubkey. Could not read' ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) - return + ' address. %s\n', exc_info=True) payload += pubSigningKey + pubEncryptionKey @@ -288,7 +286,7 @@ class singleWorker(StoppableThread): payload = self._doPOWDefaults( payload, TTL, log_prefix='(For pubkey message)') - inventoryHash = calculateInventoryHash(payload) + inventoryHash = highlevelcrypto.calculateInventoryHash(payload) objectType = 1 Inventory()[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, '') @@ -318,9 +316,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)) - return + return self.logger.warning( # The address has been deleted. + "Can't find %s in myAddressByHash", hexlify(adressHash)) if BMConfigParser().safeGetBoolean(myAddress, 'chan'): self.logger.info('This is a chan address. Not sending pubkey.') return @@ -351,15 +348,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: - self.logger.error( + except ValueError: + return + except Exception: + return self.logger.error( 'Error within sendOutOrStoreMyV3Pubkey. Could not read' ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) - return + ' address. %s\n', exc_info=True) payload += pubSigningKey + pubEncryptionKey @@ -368,7 +363,8 @@ class singleWorker(StoppableThread): payload += encodeVarint(BMConfigParser().getint( myAddress, 'payloadlengthextrabytes')) - signature = highlevelcrypto.sign(payload, privSigningKeyHex) + signature = highlevelcrypto.sign( + payload, privSigningKeyHex, self.digestAlg) payload += encodeVarint(len(signature)) payload += signature @@ -376,7 +372,7 @@ class singleWorker(StoppableThread): payload = self._doPOWDefaults( payload, TTL, log_prefix='(For pubkey message)') - inventoryHash = calculateInventoryHash(payload) + inventoryHash = highlevelcrypto.calculateInventoryHash(payload) objectType = 1 Inventory()[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, '') @@ -425,15 +421,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: - self.logger.error( + except ValueError: + return + except Exception: + return self.logger.error( 'Error within sendOutOrStoreMyV4Pubkey. Could not read' ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) - return + ' address. %s\n', exc_info=True) dataToEncrypt += pubSigningKey + pubEncryptionKey @@ -449,14 +443,13 @@ class singleWorker(StoppableThread): # unencrypted, the pubkey with part of the hash so that nodes # know which pubkey object to try to decrypt # when they want to send a message. - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( + doubleHashOfAddressData = highlevelcrypto.double_sha512( encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + addressHash - ).digest()).digest() + ) payload += doubleHashOfAddressData[32:] # the tag signature = highlevelcrypto.sign( - payload + dataToEncrypt, privSigningKeyHex - ) + payload + dataToEncrypt, privSigningKeyHex, self.digestAlg) dataToEncrypt += encodeVarint(len(signature)) dataToEncrypt += signature @@ -469,7 +462,7 @@ class singleWorker(StoppableThread): payload = self._doPOWDefaults( payload, TTL, log_prefix='(For pubkey message)') - inventoryHash = calculateInventoryHash(payload) + inventoryHash = highlevelcrypto.calculateInventoryHash(payload) objectType = 1 Inventory()[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, @@ -505,7 +498,7 @@ class singleWorker(StoppableThread): objectType = protocol.OBJECT_ONIONPEER # FIXME: ideally the objectPayload should be signed objectPayload = encodeVarint(peer.port) + protocol.encodeHost(peer.host) - tag = calculateInventoryHash(objectPayload) + tag = highlevelcrypto.calculateInventoryHash(objectPayload) if Inventory().by_type_and_tag(objectType, tag): return # not expired @@ -519,7 +512,7 @@ class singleWorker(StoppableThread): payload = self._doPOWDefaults( payload, TTL, log_prefix='(For onionpeer object)') - inventoryHash = calculateInventoryHash(payload) + inventoryHash = highlevelcrypto.calculateInventoryHash(payload) Inventory()[inventoryHash] = ( objectType, streamNumber, buffer(payload), embeddedTime, buffer(tag) @@ -613,10 +606,10 @@ class singleWorker(StoppableThread): payload += encodeVarint(streamNumber) if addressVersionNumber >= 4: - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( + doubleHashOfAddressData = highlevelcrypto.double_sha512( encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + ripe - ).digest()).digest() + ) tag = doubleHashOfAddressData[32:] payload += tag else: @@ -641,7 +634,7 @@ class singleWorker(StoppableThread): dataToSign = payload + dataToEncrypt signature = highlevelcrypto.sign( - dataToSign, privSigningKeyHex) + dataToSign, privSigningKeyHex, self.digestAlg) dataToEncrypt += encodeVarint(len(signature)) dataToEncrypt += signature @@ -686,7 +679,7 @@ class singleWorker(StoppableThread): ) continue - inventoryHash = calculateInventoryHash(payload) + inventoryHash = highlevelcrypto.calculateInventoryHash(payload) objectType = 3 Inventory()[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, tag) @@ -795,10 +788,10 @@ class singleWorker(StoppableThread): if toAddressVersionNumber <= 3: toTag = '' else: - toTag = hashlib.sha512(hashlib.sha512( + toTag = highlevelcrypto.double_sha512( encodeVarint(toAddressVersionNumber) + encodeVarint(toStreamNumber) + toRipe - ).digest()).digest()[32:] + )[32:] if toaddress in state.neededPubkeys or \ toTag in state.neededPubkeys: # We already sent a request for the pubkey @@ -832,11 +825,11 @@ class singleWorker(StoppableThread): # already contains the toAddress and cryptor # object associated with the tag for this toAddress. if toAddressVersionNumber >= 4: - doubleHashOfToAddressData = hashlib.sha512( - hashlib.sha512( - encodeVarint(toAddressVersionNumber) + encodeVarint(toStreamNumber) + toRipe - ).digest() - ).digest() + doubleHashOfToAddressData = \ + highlevelcrypto.double_sha512( + encodeVarint(toAddressVersionNumber) + + encodeVarint(toStreamNumber) + toRipe + ) # The first half of the sha512 hash. privEncryptionKey = doubleHashOfToAddressData[:32] # The second half of the sha512 hash. @@ -1116,8 +1109,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 = \ @@ -1223,7 +1217,8 @@ class singleWorker(StoppableThread): payload += fullAckPayload dataToSign = pack('>Q', embeddedTime) + '\x00\x00\x00\x02' + \ encodeVarint(1) + encodeVarint(toStreamNumber) + payload - signature = highlevelcrypto.sign(dataToSign, privSigningKeyHex) + signature = highlevelcrypto.sign( + dataToSign, privSigningKeyHex, self.digestAlg) payload += encodeVarint(len(signature)) payload += signature @@ -1301,7 +1296,7 @@ class singleWorker(StoppableThread): ) continue - inventoryHash = calculateInventoryHash(encryptedPayload) + inventoryHash = highlevelcrypto.calculateInventoryHash(encryptedPayload) objectType = 2 Inventory()[inventoryHash] = ( objectType, toStreamNumber, encryptedPayload, embeddedTime, '') @@ -1351,8 +1346,7 @@ class singleWorker(StoppableThread): # the message in our own inbox. if BMConfigParser().has_section(toaddress): # Used to detect and ignore duplicate messages in our inbox - sigHash = hashlib.sha512(hashlib.sha512( - signature).digest()).digest()[32:] + sigHash = highlevelcrypto.double_sha512(signature)[32:] t = (inventoryHash, toaddress, fromaddress, subject, int( time.time()), message, 'inbox', encoding, 0, sigHash) helper_inbox.insert(t) @@ -1407,16 +1401,13 @@ class singleWorker(StoppableThread): # neededPubkeys dictionary. But if we are recovering # from a restart of the client then we have to put it in now. - # Note that this is the first half of the sha512 hash. - privEncryptionKey = hashlib.sha512(hashlib.sha512( + doubleHashOfAddressData = highlevelcrypto.double_sha512( encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + ripe - ).digest()).digest()[:32] + ) + privEncryptionKey = doubleHashOfAddressData[:32] # Note that this is the second half of the sha512 hash. - tag = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + ripe - ).digest()).digest()[32:] + tag = doubleHashOfAddressData[32:] if tag not in state.neededPubkeys: # We'll need this for when we receive a pubkey reply: # it will be encrypted and we'll need to decrypt it. @@ -1459,7 +1450,7 @@ class singleWorker(StoppableThread): payload = self._doPOWDefaults(payload, TTL) - inventoryHash = calculateInventoryHash(payload) + inventoryHash = highlevelcrypto.calculateInventoryHash(payload) objectType = 1 Inventory()[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, '') diff --git a/src/helper_ackPayload.py b/src/helper_ackPayload.py index d30f4c0d..1c5ddf98 100644 --- a/src/helper_ackPayload.py +++ b/src/helper_ackPayload.py @@ -22,26 +22,26 @@ def genAckPayload(streamNumber=1, stealthLevel=0): - level 1: a getpubkey request for a (random) dummy key hash - level 2: a standard message, encrypted to a random pubkey """ - if stealthLevel == 2: # Generate privacy-enhanced payload + if stealthLevel == 2: # Generate privacy-enhanced payload # Generate a dummy privkey and derive the pubkey dummyPubKeyHex = highlevelcrypto.privToPub( - hexlify(helper_random.randomBytes(32))) + hexlify(highlevelcrypto.randomBytes(32))) # Generate a dummy message of random length # (the smallest possible standard-formatted message is 234 bytes) - dummyMessage = helper_random.randomBytes( + dummyMessage = highlevelcrypto.randomBytes( helper_random.randomrandrange(234, 801)) # Encrypt the message using standard BM encryption (ECIES) ackdata = highlevelcrypto.encrypt(dummyMessage, dummyPubKeyHex) acktype = 2 # message version = 1 - elif stealthLevel == 1: # Basic privacy payload (random getpubkey) - ackdata = helper_random.randomBytes(32) + elif stealthLevel == 1: # Basic privacy payload (random getpubkey) + ackdata = highlevelcrypto.randomBytes(32) acktype = 0 # getpubkey version = 4 else: # Minimum viable payload (non stealth) - ackdata = helper_random.randomBytes(32) + ackdata = highlevelcrypto.randomBytes(32) acktype = 2 # message version = 1 diff --git a/src/helper_random.py b/src/helper_random.py index 2e6a151b..e6da707e 100644 --- a/src/helper_random.py +++ b/src/helper_random.py @@ -1,12 +1,7 @@ """Convenience functions for random operations. Not suitable for security / cryptography operations.""" -import os import random -try: - from pyelliptic.openssl import OpenSSL -except ImportError: - from .pyelliptic.openssl import OpenSSL NoneType = type(None) @@ -16,14 +11,6 @@ def seed(): random.seed() -def randomBytes(n): - """Method randomBytes.""" - try: - return os.urandom(n) - except NotImplementedError: - return OpenSSL.rand(n) - - def randomshuffle(population): """Method randomShuffle. diff --git a/src/highlevelcrypto.py b/src/highlevelcrypto.py index 82743acf..cd105763 100644 --- a/src/highlevelcrypto.py +++ b/src/highlevelcrypto.py @@ -7,17 +7,85 @@ High level cryptographic functions based on `.pyelliptic` OpenSSL bindings. `More discussion. `_ """ +import hashlib +import os from binascii import hexlify import pyelliptic from pyelliptic import OpenSSL from pyelliptic import arithmetic as a -from bmconfigparser import BMConfigParser __all__ = ['encrypt', 'makeCryptor', 'pointMult', 'privToPub', 'sign', 'verify'] +# Hashes + +def double_sha512(data): + """Binary double SHA512 digest""" + return hashlib.sha512(hashlib.sha512(data).digest()).digest() + + +def calculateInventoryHash(data): + """Calculate inventory hash from object data""" + return double_sha512(data)[:32] + + +# 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) + + +# Random + +def randomBytes(n): + """Get n random bytes""" + try: + return os.urandom(n) + except NotImplementedError: + return OpenSSL.rand(n) + + +# Keys + +def random_keys(): + """Return a pair of keys, private and public""" + priv = randomBytes(32) + pub = pointMult(priv) + return priv, pub + + +def deterministic_keys(passphrase, nonce): + """Generate keys from *passphrase* and *nonce* (encoded as varint)""" + priv = hashlib.sha512(passphrase + nonce).digest()[:32] + pub = pointMult(priv) + return priv, pub + + def makeCryptor(privkey): """Return a private `.pyelliptic.ECC` instance""" private_key = a.changebase(privkey, 16, 256, minlen=32) @@ -67,22 +135,17 @@ def decryptFast(msg, cryptor): return cryptor.decrypt(msg) -def sign(msg, hexPrivkey): +def sign(msg, hexPrivkey, digestAlg="sha256"): """ Signs with hex private key using SHA1 or SHA256 depending on - "digestalg" setting + *digestAlg* keyword. """ - digestAlg = BMConfigParser().safeGet( - 'bitmessagesettings', 'digestalg', 'sha256') - if digestAlg == "sha1": - # SHA1, this will eventually be deprecated - return makeCryptor(hexPrivkey).sign( - msg, digest_alg=OpenSSL.digest_ecdsa_sha1) - elif digestAlg == "sha256": - # SHA256. Eventually this will become the default - return makeCryptor(hexPrivkey).sign(msg, digest_alg=OpenSSL.EVP_sha256) - else: + if digestAlg not in ("sha1", "sha256"): raise ValueError("Unknown digest algorithm %s" % digestAlg) + # SHA1, this will eventually be deprecated + return makeCryptor(hexPrivkey).sign( + msg, digest_alg=OpenSSL.digest_ecdsa_sha1 + if digestAlg == "sha1" else OpenSSL.EVP_sha256) def verify(msg, sig, hexPubkey): diff --git a/src/network/bmobject.py b/src/network/bmobject.py index 12b997d7..49e3c2de 100644 --- a/src/network/bmobject.py +++ b/src/network/bmobject.py @@ -6,7 +6,7 @@ import time import protocol import state -from addresses import calculateInventoryHash +from highlevelcrypto import calculateInventoryHash from inventory import Inventory from network.dandelion import Dandelion diff --git a/src/network/tcp.py b/src/network/tcp.py index ff778378..77e8ba65 100644 --- a/src/network/tcp.py +++ b/src/network/tcp.py @@ -17,7 +17,7 @@ import knownnodes import protocol import state from bmconfigparser import BMConfigParser -from helper_random import randomBytes +from highlevelcrypto import randomBytes from inventory import Inventory from network.advanceddispatcher import AdvancedDispatcher from network.assemble import assemble_addr diff --git a/src/proofofwork.py b/src/proofofwork.py index 148d6734..69b04e2d 100644 --- a/src/proofofwork.py +++ b/src/proofofwork.py @@ -4,7 +4,6 @@ Proof of work calculation """ import ctypes -import hashlib import os import sys import tempfile @@ -12,6 +11,7 @@ import time from struct import pack, unpack from subprocess import call +import highlevelcrypto import openclpow import paths import queues @@ -87,13 +87,20 @@ def _set_idle(): pass +def trial_value(nonce, initialHash): + """Calculate PoW trial value""" + trialValue, = unpack( + '>Q', highlevelcrypto.double_sha512( + pack('>Q', nonce) + initialHash)[0:8]) + return trialValue + + def _pool_worker(nonce, initialHash, target, pool_size): _set_idle() trialValue = float('inf') while trialValue > target: nonce += pool_size - trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512( - pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) + trialValue = trial_value(nonce, initialHash) return [trialValue, nonce] @@ -103,10 +110,9 @@ def _doSafePoW(target, initialHash): trialValue = float('inf') while trialValue > target and state.shutdown == 0: nonce += 1 - trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512( - pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) + trialValue = trial_value(nonce, initialHash) if state.shutdown != 0: - raise StopIteration("Interrupted") # pylint: misplaced-bare-raise + raise StopIteration("Interrupted") logger.debug("Safe PoW done") return [trialValue, nonce] @@ -163,7 +169,7 @@ def _doCPoW(target, initialHash): logger.debug("C PoW start") nonce = bmpow(out_h, out_m) - trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512(pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) + trialValue = trial_value(nonce, initialHash) if state.shutdown != 0: raise StopIteration("Interrupted") logger.debug("C PoW done") @@ -173,7 +179,7 @@ def _doCPoW(target, initialHash): def _doGPUPoW(target, initialHash): logger.debug("GPU PoW start") nonce = openclpow.do_opencl_pow(initialHash.encode("hex"), target) - trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512(pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) + trialValue = trial_value(nonce, initialHash) if trialValue > target: deviceNames = ", ".join(gpu.name for gpu in openclpow.enabledGpus) queues.UISignalQueue.put(( diff --git a/src/protocol.py b/src/protocol.py index 1934d9cc..ac00c1e2 100644 --- a/src/protocol.py +++ b/src/protocol.py @@ -269,12 +269,11 @@ def isProofOfWorkSufficient( if payloadLengthExtraBytes < defaults.networkDefaultPayloadLengthExtraBytes: payloadLengthExtraBytes = defaults.networkDefaultPayloadLengthExtraBytes endOfLifeTime, = unpack('>Q', data[8:16]) - TTL = endOfLifeTime - (int(recvTime) if recvTime else int(time.time())) + TTL = endOfLifeTime - int(recvTime if recvTime else time.time()) if TTL < 300: TTL = 300 - POW, = unpack('>Q', hashlib.sha512(hashlib.sha512( - data[:8] + hashlib.sha512(data[8:]).digest() - ).digest()).digest()[0:8]) + POW, = unpack('>Q', highlevelcrypto.double_sha512( + data[:8] + hashlib.sha512(data[8:]).digest())[0:8]) return POW <= 2 ** 64 / ( nonceTrialsPerByte * ( len(data) + payloadLengthExtraBytes diff --git a/src/pyelliptic/tests/test_arithmetic.py b/src/pyelliptic/tests/test_arithmetic.py index 7b5c59b1..dbbceeda 100644 --- a/src/pyelliptic/tests/test_arithmetic.py +++ b/src/pyelliptic/tests/test_arithmetic.py @@ -23,6 +23,12 @@ sample_privsigningkey = \ sample_privencryptionkey = \ b'4b0b73a54e19b059dc274ab69df095fe699f43b17397bca26fdf40f4d7400a3a' +# [chan] bitmessage +sample_wif_privsigningkey = \ + b'a2e8b841a531c1c558ee0680c396789c7a2ea3ac4795ae3f000caf9fe367d144' +sample_wif_privencryptionkey = \ + b'114ec0e2dca24a826a0eed064b0405b0ac148abc3b1d52729697f4d7b873fdc6' + sample_factor = \ 66858749573256452658262553961707680376751171096153613379801854825275240965733 # G * sample_factor @@ -40,6 +46,38 @@ class TestArithmetic(unittest.TestCase): sample_point, arithmetic.base10_multiply(arithmetic.G, sample_factor)) + def test_base58(self): + """Test encoding/decoding base58 using arithmetic functions""" + self.assertEqual( + arithmetic.decode(arithmetic.changebase( + b'2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK', 58, 256), 256), + 25152821841976547050350277460563089811513157529113201589004) + self.assertEqual( + arithmetic.decode(arithmetic.changebase( + b'2DBPTgeSawWYZceFD69AbDT5q4iUWtj1ZN', 58, 256), 256), + 18875720106589866286514488037355423395410802084648916523381) + self.assertEqual( + arithmetic.changebase(arithmetic.encode( + 25152821841976547050350277460563089811513157529113201589004, + 256), 256, 58), b'2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK') + self.assertEqual( + arithmetic.changebase(arithmetic.encode( + 18875720106589866286514488037355423395410802084648916523381, + 256), 256, 58), b'2DBPTgeSawWYZceFD69AbDT5q4iUWtj1ZN') + + def test_wif(self): + """Decode WIFs of [chan] bitmessage and check the keys""" + self.assertEqual( + sample_wif_privsigningkey, + arithmetic.changebase(arithmetic.changebase( + b'5K42shDERM5g7Kbi3JT5vsAWpXMqRhWZpX835M2pdSoqQQpJMYm', 58, 256 + )[1:-4], 256, 16)) + self.assertEqual( + sample_wif_privencryptionkey, + arithmetic.changebase(arithmetic.changebase( + b'5HwugVWm31gnxtoYcvcK7oywH2ezYTh6Y4tzRxsndAeMi6NHqpA', 58, 256 + )[1:-4], 256, 16)) + def test_decode(self): """Decode sample privsigningkey from hex to int and compare to factor""" self.assertEqual( diff --git a/src/pyelliptic/tests/test_openssl.py b/src/pyelliptic/tests/test_openssl.py index cb789277..ef411ef6 100644 --- a/src/pyelliptic/tests/test_openssl.py +++ b/src/pyelliptic/tests/test_openssl.py @@ -4,9 +4,10 @@ Test if OpenSSL is working correctly import unittest try: + from pyelliptic.ecc import ECC from pyelliptic.openssl import OpenSSL except ImportError: - from pybitmessage.pyelliptic import OpenSSL + from pybitmessage.pyelliptic import ECC, OpenSSL try: OpenSSL.BN_bn2binpad @@ -55,3 +56,10 @@ class TestOpenSSL(unittest.TestCase): if b.raw != c.raw.rjust(OpenSSL.BN_num_bytes(n), b'\x00'): bad += 1 self.assertEqual(bad, 0) + + def test_random_keys(self): + """A dummy test for random keys in ECC object""" + eccobj = ECC(curve='secp256k1') + self.assertEqual(len(eccobj.privkey), 32) + pubkey = eccobj.get_pubkey() + self.assertEqual(pubkey[:4], b'\x02\xca\x00\x20') diff --git a/src/shared.py b/src/shared.py index 4a654932..3ac49199 100644 --- a/src/shared.py +++ b/src/shared.py @@ -23,8 +23,6 @@ from bmconfigparser import BMConfigParser 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 BMConfigParser().addresses(): isEnabled = BMConfigParser().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( - BMConfigParser().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( + BMConfigParser().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 = highlevelcrypto.double_sha512( + encodeVarint(addressVersionNumber) + + encodeVarint(streamNumber) + hashobj)[32:] + myAddressesByTag[tag] = addressInKeysFile if not keyfileSecure: fixSensitiveFilePermissions(os.path.join( @@ -174,10 +153,10 @@ def reloadBroadcastSendersForWhichImWatching(): MyECSubscriptionCryptorObjects[hashobj] = \ highlevelcrypto.makeCryptor(hexlify(privEncryptionKey)) else: - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( + doubleHashOfAddressData = highlevelcrypto.double_sha512( encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + hashobj - ).digest()).digest() + ) tag = doubleHashOfAddressData[32:] privEncryptionKey = doubleHashOfAddressData[:32] MyECSubscriptionCryptorObjects[tag] = \ diff --git a/src/tests/samples.py b/src/tests/samples.py index e1a3e676..52607732 100644 --- a/src/tests/samples.py +++ b/src/tests/samples.py @@ -2,6 +2,11 @@ from binascii import unhexlify +# hello, page 1 of the Specification +sample_double_sha512 = unhexlify( + '0592a10584ffabf96539f3d780d776828c67da1ab5b169e9e8aed838aaecc9ed36d49ff14' + '23c55f019e050c66c6324f53588be88894fef4dcffdb74b98e2b200') + magic = 0xE9BEB4D9 @@ -28,17 +33,39 @@ sample_point = ( 94730058721143827257669456336351159718085716196507891067256111928318063085006 ) -sample_seed = 'TIGER, tiger, burning bright. In the forests of the night' -# Deterministic addresses with stream 1 and versions 3, 4 +sample_seed = b'TIGER, tiger, burning bright. In the forests of the night' +# RIPE hash on step 22 with signing key nonce 42 sample_deterministic_ripe = b'00cfb69416ae76f68a81c459de4e13460c7d17eb' +# Deterministic addresses with stream 1 and versions 3, 4 sample_deterministic_addr3 = 'BM-2DBPTgeSawWYZceFD69AbDT5q4iUWtj1ZN' sample_deterministic_addr4 = 'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK' sample_daddr3_512 = 18875720106589866286514488037355423395410802084648916523381 sample_daddr4_512 = 25152821841976547050350277460563089811513157529113201589004 -sample_statusbar_msg = "new status bar message" -sample_inbox_msg_ids = ['27e644765a3e4b2e973ee7ccf958ea20', '51fc5531-3989-4d69-bbb5-68d64b756f5b', - '2c975c515f8b414db5eea60ba57ba455', 'bc1f2d8a-681c-4cc0-9a12-6067c7e1ac24'] -# second address in sample_test_subscription_address is for the announcement broadcast -sample_test_subscription_address = ['BM-2cWQLCBGorT9pUGkYSuGGVr9LzE4mRnQaq', 'BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw'] +sample_statusbar_msg = 'new status bar message' +sample_inbox_msg_ids = [ + '27e644765a3e4b2e973ee7ccf958ea20', '51fc5531-3989-4d69-bbb5-68d64b756f5b', + '2c975c515f8b414db5eea60ba57ba455', 'bc1f2d8a-681c-4cc0-9a12-6067c7e1ac24'] +# second address in sample_subscription_addresses +# is for the announcement broadcast +sample_subscription_addresses = [ + 'BM-2cWQLCBGorT9pUGkYSuGGVr9LzE4mRnQaq', + 'BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw'] sample_subscription_name = 'test sub' + +sample_msg = unhexlify( + '0592a10584ffabf96539f3d780d776828c67da1ab5b169e9e8aed838aaecc9ed36d49ff' + '1423c55f019e050c66c6324f53588be88894fef4dcffdb74b98e2b200') +sample_sig = unhexlify( + '304402202302475351db6b822de15d922e29397541f10d8a19780ba2ca4a920b1035f075' + '02205e5bba40d5f07a24c23a89ba5f01a3828371dfbb685dd5375fa1c29095fd232b') +sample_sig_sha1 = unhexlify( + '304502203b50123af78b4e40f5f819ae5b8786f48826e56d0f3e65744708a493f5b65de1' + '0221009ddce2981ea143c0ac70404a535327e774adce8eebbae2d35104f1d326255f9a') + + +# [chan] bitmessage +sample_wif_privsigningkey = unhexlify( + b'a2e8b841a531c1c558ee0680c396789c7a2ea3ac4795ae3f000caf9fe367d144') +sample_wif_privencryptionkey = unhexlify( + b'114ec0e2dca24a826a0eed064b0405b0ac148abc3b1d52729697f4d7b873fdc6') diff --git a/src/tests/test_addresses.py b/src/tests/test_addresses.py index 8f9c283d..4c4a33d6 100644 --- a/src/tests/test_addresses.py +++ b/src/tests/test_addresses.py @@ -2,12 +2,13 @@ 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_wif_privsigningkey, sample_wif_privencryptionkey) sample_addr3 = sample_deterministic_addr3.split('-')[1] sample_addr4 = sample_deterministic_addr4.split('-')[1] @@ -59,3 +60,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( + b'5K42shDERM5g7Kbi3JT5vsAWpXMqRhWZpX835M2pdSoqQQpJMYm')) + self.assertEqual( + sample_wif_privencryptionkey, + highlevelcrypto.decodeWalletImportFormat( + b'5HwugVWm31gnxtoYcvcK7oywH2ezYTh6Y4tzRxsndAeMi6NHqpA')) + self.assertEqual( + b'5K42shDERM5g7Kbi3JT5vsAWpXMqRhWZpX835M2pdSoqQQpJMYm', + highlevelcrypto.encodeWalletImportFormat( + sample_wif_privsigningkey)) + self.assertEqual( + b'5HwugVWm31gnxtoYcvcK7oywH2ezYTh6Y4tzRxsndAeMi6NHqpA', + highlevelcrypto.encodeWalletImportFormat( + sample_wif_privencryptionkey)) + + with self.assertRaises(ValueError): + highlevelcrypto.decodeWalletImportFormat( + b'5HwugVWm31gnxtoYcvcK7oywH2ezYTh6Y4tzRxsndAeMi6NHq') diff --git a/src/tests/test_api.py b/src/tests/test_api.py index 835b4afb..41a3c911 100644 --- a/src/tests/test_api.py +++ b/src/tests/test_api.py @@ -12,8 +12,10 @@ from six.moves import xmlrpc_client # nosec import psutil from .samples import ( - sample_seed, sample_deterministic_addr3, sample_deterministic_addr4, sample_statusbar_msg, - sample_inbox_msg_ids, sample_test_subscription_address, sample_subscription_name) + sample_seed, sample_deterministic_addr3, sample_deterministic_addr4, + sample_statusbar_msg, sample_inbox_msg_ids, sample_subscription_addresses, + sample_subscription_name +) from .test_process import TestProcessProto @@ -263,9 +265,10 @@ class TestAPI(TestAPIProto): def test_subscriptions(self): """Testing the API commands related to subscriptions""" - self.assertEqual( - self.api.addSubscription(sample_test_subscription_address[0], sample_subscription_name.encode('base64')), + self.api.addSubscription( + sample_subscription_addresses[0], + sample_subscription_name.encode('base64')), 'Added subscription.' ) @@ -273,18 +276,19 @@ class TestAPI(TestAPIProto): # check_address for sub in json.loads(self.api.listSubscriptions())['subscriptions']: # special address, added when sqlThread starts - if sub['address'] == sample_test_subscription_address[0]: + if sub['address'] == sample_subscription_addresses[0]: added_subscription = sub break self.assertEqual( - base64.decodestring(added_subscription['label']) if added_subscription['label'] else None, + base64.decodestring(added_subscription['label']) + if added_subscription['label'] else None, sample_subscription_name) self.assertTrue(added_subscription['enabled']) for s in json.loads(self.api.listSubscriptions())['subscriptions']: # special address, added when sqlThread starts - if s['address'] == sample_test_subscription_address[1]: + if s['address'] == sample_subscription_addresses[1]: self.assertEqual( base64.decodestring(s['label']), 'Bitmessage new releases/announcements') @@ -295,10 +299,10 @@ class TestAPI(TestAPIProto): 'Could not find Bitmessage new releases/announcements' ' in subscriptions') self.assertEqual( - self.api.deleteSubscription(sample_test_subscription_address[0]), + self.api.deleteSubscription(sample_subscription_addresses[0]), 'Deleted subscription if it existed.') self.assertEqual( - self.api.deleteSubscription(sample_test_subscription_address[1]), + self.api.deleteSubscription(sample_subscription_addresses[1]), 'Deleted subscription if it existed.') self.assertEqual( json.loads(self.api.listSubscriptions())['subscriptions'], []) diff --git a/src/tests/test_crypto.py b/src/tests/test_crypto.py index 38410359..44b53e6e 100644 --- a/src/tests/test_crypto.py +++ b/src/tests/test_crypto.py @@ -16,8 +16,10 @@ except ImportError: RIPEMD = None from .samples import ( - sample_pubsigningkey, sample_pubencryptionkey, - sample_privsigningkey, sample_privencryptionkey, sample_ripe + sample_deterministic_ripe, sample_double_sha512, + sample_msg, sample_pubsigningkey, sample_pubencryptionkey, + sample_privsigningkey, sample_privencryptionkey, sample_ripe, + sample_seed, sample_sig, sample_sig_sha1 ) @@ -62,6 +64,56 @@ class TestCrypto(RIPEMD160TestCase, unittest.TestCase): class TestHighlevelcrypto(unittest.TestCase): """Test highlevelcrypto public functions""" + # def test_sign(self): + # """Check the signature of the sample_msg created with sample key""" + # self.assertEqual( + # highlevelcrypto.sign(sample_msg, sample_privsigningkey), sample_sig) + + def test_double_sha512(self): + """Reproduce the example on page 1 of the Specification""" + self.assertEqual( + highlevelcrypto.double_sha512(b'hello'), sample_double_sha512) + + def test_randomBytes(self): + """Dummy checks for random bytes""" + for n in (8, 32, 64): + data = highlevelcrypto.randomBytes(n) + self.assertEqual(len(data), n) + self.assertNotEqual(len(set(data)), 1) + self.assertNotEqual(data, highlevelcrypto.randomBytes(n)) + + def test_random_keys(self): + """Dummy checks for random keys""" + priv, pub = highlevelcrypto.random_keys() + self.assertEqual(len(priv), 32) + self.assertEqual(highlevelcrypto.pointMult(priv), pub) + + def test_deterministic_keys(self): + """Generate deterministic keys, make ripe and compare it to sample""" + # encodeVarint(42) = b'*' + sigkey = highlevelcrypto.deterministic_keys(sample_seed, b'*')[1] + enkey = highlevelcrypto.deterministic_keys(sample_seed, b'+')[1] + self.assertEqual( + sample_deterministic_ripe, + hexlify(TestHashlib._hashdigest( + hashlib.sha512(sigkey + enkey).digest()))) + + def test_verify(self): + """Verify sample signatures and newly generated ones""" + pubkey_hex = hexlify(sample_pubsigningkey) + # pregenerated signatures + self.assertTrue( + highlevelcrypto.verify(sample_msg, sample_sig, pubkey_hex)) + self.assertTrue( + highlevelcrypto.verify(sample_msg, sample_sig_sha1, pubkey_hex)) + # new signatures + sig256 = highlevelcrypto.sign(sample_msg, sample_privsigningkey) + sig1 = highlevelcrypto.sign(sample_msg, sample_privsigningkey, "sha1") + self.assertTrue( + highlevelcrypto.verify(sample_msg, sig256, pubkey_hex)) + self.assertTrue( + highlevelcrypto.verify(sample_msg, sig1, pubkey_hex)) + def test_privtopub(self): """Generate public keys and check the result""" self.assertEqual( diff --git a/src/tests/test_openclpow.py b/src/tests/test_openclpow.py index 341beec9..4770072e 100644 --- a/src/tests/test_openclpow.py +++ b/src/tests/test_openclpow.py @@ -1,10 +1,10 @@ """ Tests for openclpow module """ -import hashlib + import unittest -from struct import pack, unpack -from pybitmessage import openclpow + +from pybitmessage import openclpow, proofofwork class TestOpenClPow(unittest.TestCase): @@ -25,7 +25,5 @@ class TestOpenClPow(unittest.TestCase): "b93f3ffeba0ef2fd08a8dc2f87b68ae5a0dc819ab57f22ad2c4c9c8618a43b3" ).decode("hex") nonce = openclpow.do_opencl_pow(initialHash.encode("hex"), target_) - trialValue, = unpack( - '>Q', hashlib.sha512(hashlib.sha512( - pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) - self.assertLess((nonce - trialValue), target_) + self.assertLess( + nonce - proofofwork.trial_value(nonce, initialHash), target_)