2013-11-14 03:45:10 +00:00
|
|
|
import hashlib
|
|
|
|
import random
|
2018-05-17 09:38:46 +00:00
|
|
|
import shared
|
|
|
|
import threading
|
|
|
|
import time
|
2016-03-23 22:26:57 +00:00
|
|
|
from binascii import hexlify
|
2018-05-17 09:38:46 +00:00
|
|
|
from subprocess import call # nosec
|
2013-11-14 03:45:10 +00:00
|
|
|
|
|
|
|
import highlevelcrypto
|
2019-05-10 06:03:29 +00:00
|
|
|
import knownnodes
|
2018-05-17 09:38:46 +00:00
|
|
|
from addresses import (
|
|
|
|
calculateInventoryHash, decodeAddress, decodeVarint, encodeAddress,
|
|
|
|
encodeVarint, varintDecodeError
|
|
|
|
)
|
2017-02-22 08:34:54 +00:00
|
|
|
from bmconfigparser import BMConfigParser
|
2013-11-14 03:45:10 +00:00
|
|
|
import helper_bitcoin
|
|
|
|
import helper_inbox
|
2016-11-14 19:23:58 +00:00
|
|
|
import helper_msgcoding
|
2013-11-14 03:45:10 +00:00
|
|
|
import helper_sent
|
2018-05-17 09:38:46 +00:00
|
|
|
from helper_sql import SqlBulkExecute, sqlExecute, sqlQuery
|
2017-09-30 09:19:44 +00:00
|
|
|
from helper_ackPayload import genAckPayload
|
2018-10-12 21:12:00 +00:00
|
|
|
from network import bmproto
|
2017-01-11 13:27:19 +00:00
|
|
|
import protocol
|
2017-02-08 12:41:56 +00:00
|
|
|
import queues
|
2017-01-14 22:20:15 +00:00
|
|
|
import state
|
2013-11-14 03:45:10 +00:00
|
|
|
import tr
|
|
|
|
from debug import logger
|
2019-01-31 15:42:22 +00:00
|
|
|
from fallback import RIPEMD160Hash
|
2014-08-06 02:01:01 +00:00
|
|
|
import l10n
|
2013-11-14 03:45:10 +00:00
|
|
|
|
2018-05-02 15:29:55 +00:00
|
|
|
|
2013-11-14 03:45:10 +00:00
|
|
|
class objectProcessor(threading.Thread):
|
|
|
|
"""
|
|
|
|
The objectProcessor thread, of which there is only one, receives network
|
2016-03-18 17:56:40 +00:00
|
|
|
objects (msg, broadcast, pubkey, getpubkey) from the receiveDataThreads.
|
2013-11-14 03:45:10 +00:00
|
|
|
"""
|
|
|
|
def __init__(self):
|
2019-08-01 11:37:26 +00:00
|
|
|
threading.Thread.__init__(self, name="objectProcessor")
|
|
|
|
random.seed()
|
2018-10-19 07:12:48 +00:00
|
|
|
# It may be the case that the last time Bitmessage was running,
|
|
|
|
# the user closed it before it finished processing everything in the
|
|
|
|
# objectProcessorQueue. Assuming that Bitmessage wasn't closed
|
|
|
|
# forcefully, it should have saved the data in the queue into the
|
|
|
|
# objectprocessorqueue table. Let's pull it out.
|
2013-12-02 06:35:34 +00:00
|
|
|
queryreturn = sqlQuery(
|
|
|
|
'''SELECT objecttype, data FROM objectprocessorqueue''')
|
2016-01-22 10:17:10 +00:00
|
|
|
for row in queryreturn:
|
|
|
|
objectType, data = row
|
2018-05-17 09:38:46 +00:00
|
|
|
queues.objectProcessorQueue.put((objectType, data))
|
2013-12-02 06:35:34 +00:00
|
|
|
sqlExecute('''DELETE FROM objectprocessorqueue''')
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.debug(
|
|
|
|
'Loaded %s objects from disk into the objectProcessorQueue.',
|
|
|
|
len(queryreturn))
|
2018-10-12 21:12:00 +00:00
|
|
|
self._ack_obj = bmproto.BMStringParser()
|
2019-02-01 17:17:49 +00:00
|
|
|
self.successfullyDecryptMessageTimings = []
|
2013-11-14 03:45:10 +00:00
|
|
|
|
|
|
|
def run(self):
|
|
|
|
while True:
|
2017-02-08 12:41:56 +00:00
|
|
|
objectType, data = queues.objectProcessorQueue.get()
|
2013-11-14 03:45:10 +00:00
|
|
|
|
2017-04-04 08:43:29 +00:00
|
|
|
self.checkackdata(data)
|
|
|
|
|
2014-08-27 07:14:32 +00:00
|
|
|
try:
|
2018-10-19 07:12:48 +00:00
|
|
|
if objectType == protocol.OBJECT_GETPUBKEY:
|
2014-08-27 07:14:32 +00:00
|
|
|
self.processgetpubkey(data)
|
2018-10-19 07:12:48 +00:00
|
|
|
elif objectType == protocol.OBJECT_PUBKEY:
|
2014-08-27 07:14:32 +00:00
|
|
|
self.processpubkey(data)
|
2018-10-19 07:12:48 +00:00
|
|
|
elif objectType == protocol.OBJECT_MSG:
|
2014-08-27 07:14:32 +00:00
|
|
|
self.processmsg(data)
|
2018-10-19 07:12:48 +00:00
|
|
|
elif objectType == protocol.OBJECT_BROADCAST:
|
2014-08-27 07:14:32 +00:00
|
|
|
self.processbroadcast(data)
|
2019-04-26 13:59:56 +00:00
|
|
|
elif objectType == protocol.OBJECT_ONIONPEER:
|
|
|
|
self.processonion(data)
|
2018-05-17 09:38:46 +00:00
|
|
|
# is more of a command, not an object type. Is used to get
|
|
|
|
# this thread past the queue.get() so that it will check
|
|
|
|
# the shutdown variable.
|
|
|
|
elif objectType == 'checkShutdownVariable':
|
2014-08-27 07:14:32 +00:00
|
|
|
pass
|
|
|
|
else:
|
2017-07-05 07:01:40 +00:00
|
|
|
if isinstance(objectType, int):
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'Don\'t know how to handle object type 0x%08X',
|
|
|
|
objectType)
|
2017-07-05 07:01:40 +00:00
|
|
|
else:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'Don\'t know how to handle object type %s',
|
|
|
|
objectType)
|
2017-05-15 10:23:16 +00:00
|
|
|
except helper_msgcoding.DecompressionSizeException as e:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.error(
|
|
|
|
'The object is too big after decompression (stopped'
|
|
|
|
' decompressing at %ib, your configured limit %ib).'
|
|
|
|
' Ignoring',
|
|
|
|
e.size, BMConfigParser().safeGetInt("zlib", "maxsize"))
|
2014-08-27 07:14:32 +00:00
|
|
|
except varintDecodeError as e:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.debug(
|
|
|
|
'There was a problem with a varint while processing an'
|
|
|
|
' object. Some details: %s', e)
|
|
|
|
except Exception:
|
|
|
|
logger.critical(
|
|
|
|
'Critical error within objectProcessorThread: \n',
|
|
|
|
exc_info=True)
|
2013-11-20 06:29:37 +00:00
|
|
|
|
2017-01-14 22:20:15 +00:00
|
|
|
if state.shutdown:
|
2018-05-17 09:38:46 +00:00
|
|
|
# Wait just a moment for most of the connections to close
|
|
|
|
time.sleep(.5)
|
2013-12-02 06:35:34 +00:00
|
|
|
numberOfObjectsThatWereInTheObjectProcessorQueue = 0
|
|
|
|
with SqlBulkExecute() as sql:
|
2017-02-08 12:41:56 +00:00
|
|
|
while queues.objectProcessorQueue.curSize > 0:
|
|
|
|
objectType, data = queues.objectProcessorQueue.get()
|
2018-05-17 09:38:46 +00:00
|
|
|
sql.execute(
|
|
|
|
'INSERT INTO objectprocessorqueue VALUES (?,?)',
|
|
|
|
objectType, data)
|
2013-12-02 06:35:34 +00:00
|
|
|
numberOfObjectsThatWereInTheObjectProcessorQueue += 1
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.debug(
|
|
|
|
'Saved %s objects from the objectProcessorQueue to'
|
|
|
|
' disk. objectProcessorThread exiting.',
|
|
|
|
numberOfObjectsThatWereInTheObjectProcessorQueue)
|
2017-01-14 22:20:15 +00:00
|
|
|
state.shutdown = 2
|
2013-12-02 06:35:34 +00:00
|
|
|
break
|
2017-04-04 08:43:29 +00:00
|
|
|
|
|
|
|
def checkackdata(self, data):
|
|
|
|
# Let's check whether this is a message acknowledgement bound for us.
|
|
|
|
if len(data) < 32:
|
|
|
|
return
|
2017-09-30 09:19:44 +00:00
|
|
|
|
|
|
|
# bypass nonce and time, retain object type/version/stream + body
|
|
|
|
readPosition = 16
|
2017-09-25 09:12:00 +00:00
|
|
|
|
|
|
|
if data[readPosition:] in shared.ackdataForWhichImWatching:
|
2017-04-04 08:43:29 +00:00
|
|
|
logger.info('This object is an acknowledgement bound for me.')
|
2017-09-25 09:12:00 +00:00
|
|
|
del shared.ackdataForWhichImWatching[data[readPosition:]]
|
2018-05-17 09:38:46 +00:00
|
|
|
sqlExecute(
|
|
|
|
'UPDATE sent SET status=?, lastactiontime=?'
|
|
|
|
' WHERE ackdata=?',
|
|
|
|
'ackreceived', int(time.time()), data[readPosition:])
|
|
|
|
queues.UISignalQueue.put((
|
|
|
|
'updateSentItemStatusByAckdata',
|
|
|
|
(data[readPosition:],
|
|
|
|
tr._translate(
|
|
|
|
"MainWindow",
|
|
|
|
"Acknowledgement of the message received %1"
|
|
|
|
).arg(l10n.formatTimestamp()))
|
|
|
|
))
|
2017-04-04 08:43:29 +00:00
|
|
|
else:
|
|
|
|
logger.debug('This object is not an acknowledgement bound for me.')
|
|
|
|
|
2019-05-06 10:05:21 +00:00
|
|
|
@staticmethod
|
|
|
|
def processonion(data):
|
|
|
|
"""Process onionpeer object"""
|
2019-04-26 13:59:56 +00:00
|
|
|
readPosition = 20 # bypass the nonce, time, and object type
|
|
|
|
length = decodeVarint(data[readPosition:readPosition + 10])[1]
|
|
|
|
readPosition += length
|
|
|
|
stream, length = decodeVarint(data[readPosition:readPosition + 10])
|
|
|
|
readPosition += length
|
|
|
|
# it seems that stream is checked in network.bmproto
|
|
|
|
port, length = decodeVarint(data[readPosition:readPosition + 10])
|
|
|
|
host = protocol.checkIPAddress(data[readPosition + length:])
|
|
|
|
|
|
|
|
if not host:
|
|
|
|
return
|
|
|
|
peer = state.Peer(host, port)
|
|
|
|
with knownnodes.knownNodesLock:
|
|
|
|
knownnodes.addKnownNode(
|
|
|
|
stream, peer, is_self=state.ownAddresses.get(peer))
|
|
|
|
|
2019-05-06 10:05:21 +00:00
|
|
|
@staticmethod
|
|
|
|
def processgetpubkey(data):
|
|
|
|
"""Process getpubkey object"""
|
2017-04-04 08:44:53 +00:00
|
|
|
if len(data) > 200:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'getpubkey is abnormally long. Sanity check failed.'
|
|
|
|
' Ignoring object.')
|
2017-04-04 08:44:53 +00:00
|
|
|
return
|
2014-08-27 07:14:32 +00:00
|
|
|
readPosition = 20 # bypass the nonce, time, and object type
|
2013-11-20 06:29:37 +00:00
|
|
|
requestedAddressVersionNumber, addressVersionLength = decodeVarint(
|
|
|
|
data[readPosition:readPosition + 10])
|
|
|
|
readPosition += addressVersionLength
|
|
|
|
streamNumber, streamNumberLength = decodeVarint(
|
|
|
|
data[readPosition:readPosition + 10])
|
|
|
|
readPosition += streamNumberLength
|
|
|
|
|
|
|
|
if requestedAddressVersionNumber == 0:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.debug(
|
|
|
|
'The requestedAddressVersionNumber of the pubkey request'
|
|
|
|
' is zero. That doesn\'t make any sense. Ignoring it.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
|
|
|
elif requestedAddressVersionNumber == 1:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.debug(
|
|
|
|
'The requestedAddressVersionNumber of the pubkey request'
|
|
|
|
' is 1 which isn\'t supported anymore. Ignoring it.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
|
|
|
elif requestedAddressVersionNumber > 4:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.debug(
|
|
|
|
'The requestedAddressVersionNumber of the pubkey request'
|
|
|
|
' is too high. Can\'t understand. Ignoring it.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
myAddress = ''
|
2018-05-17 09:38:46 +00:00
|
|
|
if requestedAddressVersionNumber <= 3:
|
2013-11-20 06:29:37 +00:00
|
|
|
requestedHash = data[readPosition:readPosition + 20]
|
|
|
|
if len(requestedHash) != 20:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.debug(
|
|
|
|
'The length of the requested hash is not 20 bytes.'
|
|
|
|
' Something is wrong. Ignoring.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'the hash requested in this getpubkey request is: %s',
|
|
|
|
hexlify(requestedHash))
|
|
|
|
# if this address hash is one of mine
|
|
|
|
if requestedHash in shared.myAddressesByHash:
|
2013-11-20 06:29:37 +00:00
|
|
|
myAddress = shared.myAddressesByHash[requestedHash]
|
|
|
|
elif requestedAddressVersionNumber >= 4:
|
|
|
|
requestedTag = data[readPosition:readPosition + 32]
|
|
|
|
if len(requestedTag) != 32:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.debug(
|
|
|
|
'The length of the requested tag is not 32 bytes.'
|
|
|
|
' Something is wrong. Ignoring.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.debug(
|
|
|
|
'the tag requested in this getpubkey request is: %s',
|
|
|
|
hexlify(requestedTag))
|
2013-11-20 06:29:37 +00:00
|
|
|
if requestedTag in shared.myAddressesByTag:
|
|
|
|
myAddress = shared.myAddressesByTag[requestedTag]
|
|
|
|
|
|
|
|
if myAddress == '':
|
2014-01-17 01:10:04 +00:00
|
|
|
logger.info('This getpubkey request is not for any of my keys.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
if decodeAddress(myAddress)[1] != requestedAddressVersionNumber:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.warning(
|
|
|
|
'(Within the processgetpubkey function) Someone requested'
|
|
|
|
' one of my pubkeys but the requestedAddressVersionNumber'
|
|
|
|
' doesn\'t match my actual address version number.'
|
|
|
|
' Ignoring.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
|
|
|
if decodeAddress(myAddress)[2] != streamNumber:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.warning(
|
|
|
|
'(Within the processgetpubkey function) Someone requested'
|
|
|
|
' one of my pubkeys but the stream number on which we'
|
|
|
|
' heard this getpubkey object doesn\'t match this'
|
|
|
|
' address\' stream number. Ignoring.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
2017-01-11 13:27:19 +00:00
|
|
|
if BMConfigParser().safeGetBoolean(myAddress, 'chan'):
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'Ignoring getpubkey request because it is for one of my'
|
|
|
|
' chan addresses. The other party should already have'
|
|
|
|
' the pubkey.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
2018-10-19 07:12:48 +00:00
|
|
|
lastPubkeySendTime = BMConfigParser().safeGetInt(
|
|
|
|
myAddress, 'lastpubkeysendtime')
|
2018-05-17 09:38:46 +00:00
|
|
|
# If the last time we sent our pubkey was more recent than
|
|
|
|
# 28 days ago...
|
|
|
|
if lastPubkeySendTime > time.time() - 2419200:
|
|
|
|
logger.info(
|
|
|
|
'Found getpubkey-requested-item in my list of EC hashes'
|
|
|
|
' BUT we already sent it recently. Ignoring request.'
|
|
|
|
' The lastPubkeySendTime is: %s', lastPubkeySendTime)
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'Found getpubkey-requested-hash in my list of EC hashes.'
|
|
|
|
' Telling Worker thread to do the POW for a pubkey message'
|
|
|
|
' and send it out.')
|
2013-11-20 06:29:37 +00:00
|
|
|
if requestedAddressVersionNumber == 2:
|
2018-05-17 09:38:46 +00:00
|
|
|
queues.workerQueue.put(('doPOWForMyV2Pubkey', requestedHash))
|
2013-11-20 06:29:37 +00:00
|
|
|
elif requestedAddressVersionNumber == 3:
|
2018-05-17 09:38:46 +00:00
|
|
|
queues.workerQueue.put(('sendOutOrStoreMyV3Pubkey', requestedHash))
|
2013-11-20 06:29:37 +00:00
|
|
|
elif requestedAddressVersionNumber == 4:
|
2018-05-17 09:38:46 +00:00
|
|
|
queues.workerQueue.put(('sendOutOrStoreMyV4Pubkey', myAddress))
|
2013-11-20 06:29:37 +00:00
|
|
|
|
|
|
|
def processpubkey(self, data):
|
|
|
|
pubkeyProcessingStartTime = time.time()
|
|
|
|
shared.numberOfPubkeysProcessed += 1
|
2017-10-19 06:39:09 +00:00
|
|
|
queues.UISignalQueue.put((
|
|
|
|
'updateNumberOfPubkeysProcessed', 'no data'))
|
2014-08-27 07:14:32 +00:00
|
|
|
readPosition = 20 # bypass the nonce, time, and object type
|
2013-11-20 06:29:37 +00:00
|
|
|
addressVersion, varintLength = decodeVarint(
|
|
|
|
data[readPosition:readPosition + 10])
|
|
|
|
readPosition += varintLength
|
|
|
|
streamNumber, varintLength = decodeVarint(
|
|
|
|
data[readPosition:readPosition + 10])
|
|
|
|
readPosition += varintLength
|
|
|
|
if addressVersion == 0:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.debug(
|
|
|
|
'(Within processpubkey) addressVersion of 0 doesn\'t'
|
|
|
|
' make sense.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
|
|
|
if addressVersion > 4 or addressVersion == 1:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'This version of Bitmessage cannot handle version %s'
|
|
|
|
' addresses.', addressVersion)
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
|
|
|
if addressVersion == 2:
|
2018-05-17 09:38:46 +00:00
|
|
|
# sanity check. This is the minimum possible length.
|
|
|
|
if len(data) < 146:
|
|
|
|
logger.debug(
|
|
|
|
'(within processpubkey) payloadLength less than 146.'
|
|
|
|
' Sanity check failed.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
|
|
|
readPosition += 4
|
|
|
|
publicSigningKey = data[readPosition:readPosition + 64]
|
|
|
|
# Is it possible for a public key to be invalid such that trying to
|
2014-12-25 08:57:34 +00:00
|
|
|
# encrypt or sign with it will cause an error? If it is, it would
|
|
|
|
# be easiest to test them here.
|
2013-11-20 06:29:37 +00:00
|
|
|
readPosition += 64
|
|
|
|
publicEncryptionKey = data[readPosition:readPosition + 64]
|
|
|
|
if len(publicEncryptionKey) < 64:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.debug(
|
|
|
|
'publicEncryptionKey length less than 64. Sanity check'
|
|
|
|
' failed.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
2014-12-25 08:57:34 +00:00
|
|
|
readPosition += 64
|
2018-05-17 09:38:46 +00:00
|
|
|
# The data we'll store in the pubkeys table.
|
|
|
|
dataToStore = data[20:readPosition]
|
2013-11-20 06:29:37 +00:00
|
|
|
sha = hashlib.new('sha512')
|
|
|
|
sha.update(
|
|
|
|
'\x04' + publicSigningKey + '\x04' + publicEncryptionKey)
|
2019-01-31 15:42:22 +00:00
|
|
|
ripe = RIPEMD160Hash(sha.digest()).digest()
|
2013-11-20 06:29:37 +00:00
|
|
|
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.debug(
|
|
|
|
'within recpubkey, addressVersion: %s, streamNumber: %s'
|
|
|
|
'\nripe %s\npublicSigningKey in hex: %s'
|
|
|
|
'\npublicEncryptionKey in hex: %s',
|
|
|
|
addressVersion, streamNumber, hexlify(ripe),
|
|
|
|
hexlify(publicSigningKey), hexlify(publicEncryptionKey)
|
|
|
|
)
|
2013-11-20 06:29:37 +00:00
|
|
|
|
2015-03-09 06:35:32 +00:00
|
|
|
address = encodeAddress(addressVersion, streamNumber, ripe)
|
2018-05-02 15:29:55 +00:00
|
|
|
|
2015-03-09 06:35:32 +00:00
|
|
|
queryreturn = sqlQuery(
|
2018-05-17 09:38:46 +00:00
|
|
|
"SELECT usedpersonally FROM pubkeys WHERE address=?"
|
|
|
|
" AND usedpersonally='yes'", address)
|
|
|
|
# if this pubkey is already in our database and if we have
|
|
|
|
# used it personally:
|
|
|
|
if queryreturn != []:
|
|
|
|
logger.info(
|
|
|
|
'We HAVE used this pubkey personally. Updating time.')
|
|
|
|
t = (address, addressVersion, dataToStore,
|
|
|
|
int(time.time()), 'yes')
|
2013-11-20 06:29:37 +00:00
|
|
|
else:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'We have NOT used this pubkey personally. Inserting'
|
|
|
|
' in database.')
|
|
|
|
t = (address, addressVersion, dataToStore,
|
|
|
|
int(time.time()), 'no')
|
2013-11-20 06:29:37 +00:00
|
|
|
sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t)
|
2015-03-09 06:35:32 +00:00
|
|
|
self.possibleNewPubkey(address)
|
2013-11-20 06:29:37 +00:00
|
|
|
if addressVersion == 3:
|
|
|
|
if len(data) < 170: # sanity check.
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.warning(
|
|
|
|
'(within processpubkey) payloadLength less than 170.'
|
|
|
|
' Sanity check failed.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
|
|
|
readPosition += 4
|
|
|
|
publicSigningKey = '\x04' + data[readPosition:readPosition + 64]
|
|
|
|
readPosition += 64
|
|
|
|
publicEncryptionKey = '\x04' + data[readPosition:readPosition + 64]
|
|
|
|
readPosition += 64
|
2018-05-02 15:29:55 +00:00
|
|
|
_, specifiedNonceTrialsPerByteLength = decodeVarint(
|
2013-11-20 06:29:37 +00:00
|
|
|
data[readPosition:readPosition + 10])
|
|
|
|
readPosition += specifiedNonceTrialsPerByteLength
|
2018-05-02 15:29:55 +00:00
|
|
|
_, specifiedPayloadLengthExtraBytesLength = decodeVarint(
|
2013-11-20 06:29:37 +00:00
|
|
|
data[readPosition:readPosition + 10])
|
|
|
|
readPosition += specifiedPayloadLengthExtraBytesLength
|
|
|
|
endOfSignedDataPosition = readPosition
|
2018-05-17 09:38:46 +00:00
|
|
|
# The data we'll store in the pubkeys table.
|
|
|
|
dataToStore = data[20:readPosition]
|
2013-11-20 06:29:37 +00:00
|
|
|
signatureLength, signatureLengthLength = decodeVarint(
|
|
|
|
data[readPosition:readPosition + 10])
|
|
|
|
readPosition += signatureLengthLength
|
|
|
|
signature = data[readPosition:readPosition + signatureLength]
|
2018-05-17 09:38:46 +00:00
|
|
|
if highlevelcrypto.verify(
|
|
|
|
data[8:endOfSignedDataPosition],
|
|
|
|
signature, hexlify(publicSigningKey)):
|
2014-12-25 08:57:34 +00:00
|
|
|
logger.debug('ECDSA verify passed (within processpubkey)')
|
2014-08-27 07:14:32 +00:00
|
|
|
else:
|
2014-12-25 08:57:34 +00:00
|
|
|
logger.warning('ECDSA verify failed (within processpubkey)')
|
|
|
|
return
|
2013-11-20 06:29:37 +00:00
|
|
|
|
|
|
|
sha = hashlib.new('sha512')
|
|
|
|
sha.update(publicSigningKey + publicEncryptionKey)
|
2019-01-31 15:42:22 +00:00
|
|
|
ripe = RIPEMD160Hash(sha.digest()).digest()
|
2014-01-17 01:10:04 +00:00
|
|
|
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.debug(
|
|
|
|
'within recpubkey, addressVersion: %s, streamNumber: %s'
|
|
|
|
'\nripe %s\npublicSigningKey in hex: %s'
|
|
|
|
'\npublicEncryptionKey in hex: %s',
|
|
|
|
addressVersion, streamNumber, hexlify(ripe),
|
|
|
|
hexlify(publicSigningKey), hexlify(publicEncryptionKey)
|
|
|
|
)
|
2013-11-20 06:29:37 +00:00
|
|
|
|
2015-03-09 06:35:32 +00:00
|
|
|
address = encodeAddress(addressVersion, streamNumber, ripe)
|
2018-05-17 09:38:46 +00:00
|
|
|
queryreturn = sqlQuery(
|
|
|
|
"SELECT usedpersonally FROM pubkeys WHERE address=?"
|
|
|
|
" AND usedpersonally='yes'", address)
|
|
|
|
# if this pubkey is already in our database and if we have
|
|
|
|
# used it personally:
|
|
|
|
if queryreturn != []:
|
|
|
|
logger.info(
|
|
|
|
'We HAVE used this pubkey personally. Updating time.')
|
|
|
|
t = (address, addressVersion, dataToStore,
|
|
|
|
int(time.time()), 'yes')
|
2013-11-20 06:29:37 +00:00
|
|
|
else:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'We have NOT used this pubkey personally. Inserting'
|
|
|
|
' in database.')
|
|
|
|
t = (address, addressVersion, dataToStore,
|
|
|
|
int(time.time()), 'no')
|
2013-11-20 06:29:37 +00:00
|
|
|
sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t)
|
2015-03-09 06:35:32 +00:00
|
|
|
self.possibleNewPubkey(address)
|
2013-11-20 06:29:37 +00:00
|
|
|
|
|
|
|
if addressVersion == 4:
|
|
|
|
if len(data) < 350: # sanity check.
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.debug(
|
|
|
|
'(within processpubkey) payloadLength less than 350.'
|
|
|
|
' Sanity check failed.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
2014-08-27 07:14:32 +00:00
|
|
|
|
2013-11-20 06:29:37 +00:00
|
|
|
tag = data[readPosition:readPosition + 32]
|
2017-01-14 22:20:15 +00:00
|
|
|
if tag not in state.neededPubkeys:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'We don\'t need this v4 pubkey. We didn\'t ask for it.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
2018-05-02 15:29:55 +00:00
|
|
|
|
2014-08-27 07:14:32 +00:00
|
|
|
# Let us try to decrypt the pubkey
|
2018-05-02 15:29:55 +00:00
|
|
|
toAddress, _ = state.neededPubkeys[tag]
|
2019-01-30 09:14:42 +00:00
|
|
|
if protocol.decryptAndCheckPubkeyPayload(data, toAddress) == \
|
2018-05-17 09:38:46 +00:00
|
|
|
'successful':
|
|
|
|
# At this point we know that we have been waiting on this
|
|
|
|
# pubkey. This function will command the workerThread
|
|
|
|
# to start work on the messages that require it.
|
2015-03-09 06:35:32 +00:00
|
|
|
self.possibleNewPubkey(toAddress)
|
2013-11-20 06:29:37 +00:00
|
|
|
|
|
|
|
# Display timing data
|
|
|
|
timeRequiredToProcessPubkey = time.time(
|
|
|
|
) - pubkeyProcessingStartTime
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.debug(
|
|
|
|
'Time required to process this pubkey: %s',
|
|
|
|
timeRequiredToProcessPubkey)
|
2013-11-14 03:45:10 +00:00
|
|
|
|
|
|
|
def processmsg(self, data):
|
2013-11-20 06:29:37 +00:00
|
|
|
messageProcessingStartTime = time.time()
|
|
|
|
shared.numberOfMessagesProcessed += 1
|
2017-10-19 06:39:09 +00:00
|
|
|
queues.UISignalQueue.put((
|
|
|
|
'updateNumberOfMessagesProcessed', 'no data'))
|
2018-05-17 09:38:46 +00:00
|
|
|
readPosition = 20 # bypass the nonce, time, and object type
|
|
|
|
msgVersion, msgVersionLength = decodeVarint(
|
|
|
|
data[readPosition:readPosition + 9])
|
2014-12-25 08:57:34 +00:00
|
|
|
if msgVersion != 1:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'Cannot understand message versions other than one.'
|
|
|
|
' Ignoring message.')
|
2014-12-25 08:57:34 +00:00
|
|
|
return
|
|
|
|
readPosition += msgVersionLength
|
2018-05-02 15:29:55 +00:00
|
|
|
|
2018-05-17 09:38:46 +00:00
|
|
|
streamNumberAsClaimedByMsg, streamNumberAsClaimedByMsgLength = \
|
|
|
|
decodeVarint(data[readPosition:readPosition + 9])
|
2013-11-14 03:45:10 +00:00
|
|
|
readPosition += streamNumberAsClaimedByMsgLength
|
|
|
|
inventoryHash = calculateInventoryHash(data)
|
|
|
|
initialDecryptionSuccessful = False
|
|
|
|
|
|
|
|
# This is not an acknowledgement bound for me. See if it is a message
|
|
|
|
# bound for me by trying to decrypt it with my private keys.
|
2018-05-02 15:29:55 +00:00
|
|
|
|
2018-05-17 09:38:46 +00:00
|
|
|
for key, cryptorObject in sorted(
|
|
|
|
shared.myECCryptorObjects.items(),
|
|
|
|
key=lambda x: random.random()):
|
2013-11-14 03:45:10 +00:00
|
|
|
try:
|
2018-05-17 09:38:46 +00:00
|
|
|
# continue decryption attempts to avoid timing attacks
|
|
|
|
if initialDecryptionSuccessful:
|
2016-02-18 15:01:06 +00:00
|
|
|
cryptorObject.decrypt(data[readPosition:])
|
|
|
|
else:
|
|
|
|
decryptedData = cryptorObject.decrypt(data[readPosition:])
|
2018-05-17 09:38:46 +00:00
|
|
|
# This is the RIPE hash of my pubkeys. We need this
|
|
|
|
# below to compare to the destination_ripe included
|
|
|
|
# in the encrypted data.
|
|
|
|
toRipe = key
|
2016-02-18 15:01:06 +00:00
|
|
|
initialDecryptionSuccessful = True
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'EC decryption successful using key associated'
|
|
|
|
' with ripe hash: %s.', hexlify(key))
|
2018-05-02 15:29:55 +00:00
|
|
|
except Exception:
|
2014-12-25 08:57:34 +00:00
|
|
|
pass
|
2013-11-14 03:45:10 +00:00
|
|
|
if not initialDecryptionSuccessful:
|
|
|
|
# This is not a message bound for me.
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'Length of time program spent failing to decrypt this'
|
|
|
|
' message: %s seconds.',
|
|
|
|
time.time() - messageProcessingStartTime)
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
2013-11-14 03:45:10 +00:00
|
|
|
|
2013-11-20 06:29:37 +00:00
|
|
|
# This is a message bound for me.
|
2018-10-19 07:12:48 +00:00
|
|
|
# Look up my address based on the RIPE hash.
|
|
|
|
toAddress = shared.myAddressesByHash[toRipe]
|
2013-11-20 06:29:37 +00:00
|
|
|
readPosition = 0
|
2018-05-17 09:38:46 +00:00
|
|
|
sendersAddressVersionNumber, sendersAddressVersionNumberLength = \
|
|
|
|
decodeVarint(decryptedData[readPosition:readPosition + 10])
|
2013-11-20 06:29:37 +00:00
|
|
|
readPosition += sendersAddressVersionNumberLength
|
|
|
|
if sendersAddressVersionNumber == 0:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'Cannot understand sendersAddressVersionNumber = 0.'
|
|
|
|
' Ignoring message.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
|
|
|
if sendersAddressVersionNumber > 4:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'Sender\'s address version number %s not yet supported.'
|
|
|
|
' Ignoring message.', sendersAddressVersionNumber)
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
|
|
|
if len(decryptedData) < 170:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'Length of the unencrypted data is unreasonably short.'
|
|
|
|
' Sanity check failed. Ignoring message.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
|
|
|
sendersStreamNumber, sendersStreamNumberLength = decodeVarint(
|
|
|
|
decryptedData[readPosition:readPosition + 10])
|
|
|
|
if sendersStreamNumber == 0:
|
2014-01-17 01:10:04 +00:00
|
|
|
logger.info('sender\'s stream number is 0. Ignoring message.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
|
|
|
readPosition += sendersStreamNumberLength
|
|
|
|
readPosition += 4
|
2018-10-19 07:12:48 +00:00
|
|
|
pubSigningKey = '\x04' + decryptedData[readPosition:readPosition + 64]
|
2013-11-20 06:29:37 +00:00
|
|
|
readPosition += 64
|
2018-10-19 07:12:48 +00:00
|
|
|
pubEncryptionKey = '\x04' + decryptedData[readPosition:readPosition + 64]
|
2013-11-20 06:29:37 +00:00
|
|
|
readPosition += 64
|
|
|
|
if sendersAddressVersionNumber >= 3:
|
2018-05-17 09:38:46 +00:00
|
|
|
requiredAverageProofOfWorkNonceTrialsPerByte, varintLength = \
|
|
|
|
decodeVarint(decryptedData[readPosition:readPosition + 10])
|
2013-11-20 06:29:37 +00:00
|
|
|
readPosition += varintLength
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'sender\'s requiredAverageProofOfWorkNonceTrialsPerByte is %s',
|
|
|
|
requiredAverageProofOfWorkNonceTrialsPerByte)
|
2013-11-20 06:29:37 +00:00
|
|
|
requiredPayloadLengthExtraBytes, varintLength = decodeVarint(
|
2013-11-14 03:45:10 +00:00
|
|
|
decryptedData[readPosition:readPosition + 10])
|
2013-11-20 06:29:37 +00:00
|
|
|
readPosition += varintLength
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'sender\'s requiredPayloadLengthExtraBytes is %s',
|
|
|
|
requiredPayloadLengthExtraBytes)
|
|
|
|
# needed for when we store the pubkey in our database of pubkeys
|
|
|
|
# for later use.
|
|
|
|
endOfThePublicKeyPosition = readPosition
|
2013-11-20 06:29:37 +00:00
|
|
|
if toRipe != decryptedData[readPosition:readPosition + 20]:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'The original sender of this message did not send it to'
|
|
|
|
' you. Someone is attempting a Surreptitious Forwarding'
|
|
|
|
' Attack.\nSee: '
|
|
|
|
'http://world.std.com/~dtd/sign_encrypt/sign_encrypt7.html'
|
|
|
|
'\nyour toRipe: %s\nembedded destination toRipe: %s',
|
|
|
|
hexlify(toRipe),
|
|
|
|
hexlify(decryptedData[readPosition:readPosition + 20])
|
|
|
|
)
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
|
|
|
readPosition += 20
|
|
|
|
messageEncodingType, messageEncodingTypeLength = decodeVarint(
|
|
|
|
decryptedData[readPosition:readPosition + 10])
|
|
|
|
readPosition += messageEncodingTypeLength
|
|
|
|
messageLength, messageLengthLength = decodeVarint(
|
|
|
|
decryptedData[readPosition:readPosition + 10])
|
|
|
|
readPosition += messageLengthLength
|
|
|
|
message = decryptedData[readPosition:readPosition + messageLength]
|
|
|
|
# print 'First 150 characters of message:', repr(message[:150])
|
|
|
|
readPosition += messageLength
|
|
|
|
ackLength, ackLengthLength = decodeVarint(
|
|
|
|
decryptedData[readPosition:readPosition + 10])
|
|
|
|
readPosition += ackLengthLength
|
|
|
|
ackData = decryptedData[readPosition:readPosition + ackLength]
|
|
|
|
readPosition += ackLength
|
2018-05-17 09:38:46 +00:00
|
|
|
# needed to mark the end of what is covered by the signature
|
|
|
|
positionOfBottomOfAckData = readPosition
|
2013-11-20 06:29:37 +00:00
|
|
|
signatureLength, signatureLengthLength = decodeVarint(
|
|
|
|
decryptedData[readPosition:readPosition + 10])
|
|
|
|
readPosition += signatureLengthLength
|
|
|
|
signature = decryptedData[
|
|
|
|
readPosition:readPosition + signatureLength]
|
2018-05-17 09:38:46 +00:00
|
|
|
signedData = data[8:20] + encodeVarint(1) + encodeVarint(
|
|
|
|
streamNumberAsClaimedByMsg
|
|
|
|
) + decryptedData[:positionOfBottomOfAckData]
|
2018-05-02 15:29:55 +00:00
|
|
|
|
2018-05-17 09:38:46 +00:00
|
|
|
if not highlevelcrypto.verify(
|
|
|
|
signedData, signature, hexlify(pubSigningKey)):
|
2014-08-27 07:14:32 +00:00
|
|
|
logger.debug('ECDSA verify failed')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
2014-08-27 07:14:32 +00:00
|
|
|
logger.debug('ECDSA verify passed')
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.debug(
|
|
|
|
'As a matter of intellectual curiosity, here is the Bitcoin'
|
|
|
|
' address associated with the keys owned by the other person:'
|
|
|
|
' %s ..and here is the testnet address: %s. The other person'
|
|
|
|
' must take their private signing key from Bitmessage and'
|
|
|
|
' import it into Bitcoin (or a service like Blockchain.info)'
|
|
|
|
' for it to be of any use. Do not use this unless you know'
|
|
|
|
' what you are doing.',
|
|
|
|
helper_bitcoin.calculateBitcoinAddressFromPubkey(pubSigningKey),
|
|
|
|
helper_bitcoin.calculateTestnetAddressFromPubkey(pubSigningKey)
|
|
|
|
)
|
|
|
|
# Used to detect and ignore duplicate messages in our inbox
|
|
|
|
sigHash = hashlib.sha512(
|
|
|
|
hashlib.sha512(signature).digest()).digest()[32:]
|
2013-11-20 06:29:37 +00:00
|
|
|
|
|
|
|
# calculate the fromRipe.
|
|
|
|
sha = hashlib.new('sha512')
|
|
|
|
sha.update(pubSigningKey + pubEncryptionKey)
|
2019-01-31 15:42:22 +00:00
|
|
|
ripe = RIPEMD160Hash(sha.digest()).digest()
|
2013-11-20 06:29:37 +00:00
|
|
|
fromAddress = encodeAddress(
|
2019-01-31 15:42:22 +00:00
|
|
|
sendersAddressVersionNumber, sendersStreamNumber, ripe)
|
2018-05-02 15:29:55 +00:00
|
|
|
|
2013-11-20 06:29:37 +00:00
|
|
|
# Let's store the public key in case we want to reply to this
|
|
|
|
# person.
|
2014-12-25 08:57:34 +00:00
|
|
|
sqlExecute(
|
|
|
|
'''INSERT INTO pubkeys VALUES (?,?,?,?,?)''',
|
2015-03-09 06:35:32 +00:00
|
|
|
fromAddress,
|
2014-12-25 08:57:34 +00:00
|
|
|
sendersAddressVersionNumber,
|
|
|
|
decryptedData[:endOfThePublicKeyPosition],
|
|
|
|
int(time.time()),
|
|
|
|
'yes')
|
2018-05-02 15:29:55 +00:00
|
|
|
|
2014-12-25 08:57:34 +00:00
|
|
|
# Check to see whether we happen to be awaiting this
|
|
|
|
# pubkey in order to send a message. If we are, it will do the POW
|
|
|
|
# and send it.
|
2015-03-09 06:35:32 +00:00
|
|
|
self.possibleNewPubkey(fromAddress)
|
2018-05-02 15:29:55 +00:00
|
|
|
|
2013-11-20 06:29:37 +00:00
|
|
|
# If this message is bound for one of my version 3 addresses (or
|
|
|
|
# higher), then we must check to make sure it meets our demanded
|
2014-01-17 01:10:04 +00:00
|
|
|
# proof of work requirement. If this is bound for one of my chan
|
|
|
|
# addresses then we skip this check; the minimum network POW is
|
|
|
|
# fine.
|
2018-05-17 09:38:46 +00:00
|
|
|
# If the toAddress version number is 3 or higher and not one of
|
|
|
|
# my chan addresses:
|
|
|
|
if decodeAddress(toAddress)[1] >= 3 \
|
|
|
|
and not BMConfigParser().safeGetBoolean(toAddress, 'chan'):
|
|
|
|
# If I'm not friendly with this person:
|
|
|
|
if not shared.isAddressInMyAddressBookSubscriptionsListOrWhitelist(fromAddress):
|
2017-01-11 13:27:19 +00:00
|
|
|
requiredNonceTrialsPerByte = BMConfigParser().getint(
|
2013-11-20 06:29:37 +00:00
|
|
|
toAddress, 'noncetrialsperbyte')
|
2017-01-11 13:27:19 +00:00
|
|
|
requiredPayloadLengthExtraBytes = BMConfigParser().getint(
|
2013-11-20 06:29:37 +00:00
|
|
|
toAddress, 'payloadlengthextrabytes')
|
2018-05-17 09:38:46 +00:00
|
|
|
if not protocol.isProofOfWorkSufficient(
|
|
|
|
data, requiredNonceTrialsPerByte,
|
|
|
|
requiredPayloadLengthExtraBytes):
|
|
|
|
logger.info(
|
|
|
|
'Proof of work in msg is insufficient only because'
|
|
|
|
' it does not meet our higher requirement.')
|
2013-11-20 06:29:37 +00:00
|
|
|
return
|
2018-05-17 09:38:46 +00:00
|
|
|
# Gets set to True if the user shouldn't see the message according
|
|
|
|
# to black or white lists.
|
|
|
|
blockMessage = False
|
|
|
|
# If we are using a blacklist
|
|
|
|
if BMConfigParser().get(
|
|
|
|
'bitmessagesettings', 'blackwhitelist') == 'black':
|
2013-11-20 06:29:37 +00:00
|
|
|
queryreturn = sqlQuery(
|
2018-05-17 09:38:46 +00:00
|
|
|
"SELECT label FROM blacklist where address=? and enabled='1'",
|
2013-11-20 06:29:37 +00:00
|
|
|
fromAddress)
|
|
|
|
if queryreturn != []:
|
2014-01-17 01:10:04 +00:00
|
|
|
logger.info('Message ignored because address is in blacklist.')
|
2013-11-20 06:29:37 +00:00
|
|
|
|
|
|
|
blockMessage = True
|
|
|
|
else: # We're using a whitelist
|
|
|
|
queryreturn = sqlQuery(
|
2018-05-17 09:38:46 +00:00
|
|
|
"SELECT label FROM whitelist where address=? and enabled='1'",
|
2013-11-20 06:29:37 +00:00
|
|
|
fromAddress)
|
|
|
|
if queryreturn == []:
|
2018-05-17 09:38:46 +00:00
|
|
|
logger.info(
|
|
|
|
'Message ignored because address not in whitelist.')
|
2013-11-20 06:29:37 +00:00
|
|
|
blockMessage = True
|
2016-11-14 19:23:58 +00:00
|
|
|
|
2017-01-11 13:27:19 +00:00
|
|
|
toLabel = BMConfigParser().get(toAddress, 'label')
|
2014-07-26 17:15:28 +00:00
|
|
|
if toLabel == '':
|
|
|
|
toLabel = toAddress
|
|
|
|
|
2018-02-13 12:24:37 +00:00
|
|
|
try:
|
2018-05-17 09:38:46 +00:00
|
|
|
decodedMessage = helper_msgcoding.MsgDecode(
|
|
|
|
messageEncodingType, message)
|
2018-02-13 12:24:37 +00:00
|
|
|
except helper_msgcoding.MsgDecodeException:
|
|
|
|
return
|
2016-11-14 19:23:58 +00:00
|
|
|
subject = decodedMessage.subject
|
|
|
|
body = decodedMessage.body
|
|
|
|
|
2014-07-26 17:15:28 +00:00
|
|
|
# Let us make sure that we haven't already received this message
|
2015-02-21 02:03:20 +00:00
|
|
|
if helper_inbox.isMessageAlreadyInInbox(sigHash):
|
2014-07-26 17:15:28 +00:00
|
|
|
logger.info('This msg is already in our inbox. Ignoring it.')
|
|
|
|
blockMessage = True
|
2013-11-20 06:29:37 +00:00
|
|