import threading import shared import time from time import strftime, localtime, gmtime import random from subprocess import call # used when the API must execute an outside program from addresses import * import highlevelcrypto import proofofwork import sys import tr from debug import logger from helper_sql import * import helper_inbox from helper_generic import addDataPadding # This thread, of which there is only one, does the heavy lifting: # calculating POWs. class singleWorker(threading.Thread): def __init__(self): # QThread.__init__(self, parent) threading.Thread.__init__(self) def run(self): queryreturn = sqlQuery( '''SELECT toripe, toaddress FROM sent WHERE ((status='awaitingpubkey' OR status='doingpubkeypow') AND folder='sent')''') for row in queryreturn: toripe, toaddress = row toStatus, toAddressVersionNumber, toStreamNumber, toRipe = decodeAddress(toaddress) if toAddressVersionNumber <= 3 : shared.neededPubkeys[toripe] = 0 elif toAddressVersionNumber >= 4: doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( toAddressVersionNumber) + encodeVarint(toStreamNumber) + toRipe).digest()).digest() privEncryptionKey = doubleHashOfAddressData[:32] # Note that this is the first half of the sha512 hash. tag = doubleHashOfAddressData[32:] shared.neededPubkeys[tag] = highlevelcrypto.makeCryptor(privEncryptionKey.encode('hex')) # We'll need this for when we receive a pubkey reply: it will be encrypted and we'll need to decrypt it. # Initialize the shared.ackdataForWhichImWatching data structure using data # from the sql database. queryreturn = sqlQuery( '''SELECT ackdata FROM sent where (status='msgsent' OR status='doingmsgpow')''') for row in queryreturn: ackdata, = row print 'Watching for ackdata', ackdata.encode('hex') shared.ackdataForWhichImWatching[ackdata] = 0 queryreturn = sqlQuery( '''SELECT DISTINCT toaddress FROM sent WHERE (status='doingpubkeypow' AND folder='sent')''') for row in queryreturn: toaddress, = row self.requestPubKey(toaddress) time.sleep( 10) # give some time for the GUI to start before we start on existing POW tasks. self.sendMsg() # just in case there are any pending tasks for msg # messages that have yet to be sent. self.sendBroadcast() # just in case there are any tasks for Broadcasts # that have yet to be sent. while True: command, data = shared.workerQueue.get() if command == 'sendmessage': self.sendMsg() elif command == 'sendbroadcast': self.sendBroadcast() elif command == 'doPOWForMyV2Pubkey': self.doPOWForMyV2Pubkey(data) elif command == 'sendOutOrStoreMyV3Pubkey': self.sendOutOrStoreMyV3Pubkey(data) elif command == 'sendOutOrStoreMyV4Pubkey': self.sendOutOrStoreMyV4Pubkey(data) else: with shared.printLock: sys.stderr.write( 'Probable programming error: The command sent to the workerThread is weird. It is: %s\n' % command) shared.workerQueue.task_done() def doPOWForMyV2Pubkey(self, hash): # This function also broadcasts out the pubkey message once it is done with the POW # Look up my stream number based on my address hash """configSections = shared.config.sections() for addressInKeysFile in configSections: if addressInKeysFile <> 'bitmessagesettings': status,addressVersionNumber,streamNumber,hashFromThisParticularAddress = decodeAddress(addressInKeysFile) if hash == hashFromThisParticularAddress: myAddress = addressInKeysFile break""" myAddress = shared.myAddressesByHash[hash] status, addressVersionNumber, streamNumber, hash = decodeAddress( myAddress) embeddedTime = int(time.time() + random.randrange( -300, 300)) # the current time plus or minus five minutes payload = pack('>I', (embeddedTime)) payload += encodeVarint(addressVersionNumber) # Address version number payload += encodeVarint(streamNumber) payload += '\x00\x00\x00\x01' # bitfield of features supported by me (see the wiki). try: privSigningKeyBase58 = shared.config.get( myAddress, 'privsigningkey') privEncryptionKeyBase58 = shared.config.get( myAddress, 'privencryptionkey') except Exception as err: with shared.printLock: sys.stderr.write( 'Error within doPOWForMyV2Pubkey. Could not read the keys from the keys.dat file for a requested address. %s\n' % err) return privSigningKeyHex = shared.decodeWalletImportFormat( privSigningKeyBase58).encode('hex') privEncryptionKeyHex = shared.decodeWalletImportFormat( privEncryptionKeyBase58).encode('hex') pubSigningKey = highlevelcrypto.privToPub( privSigningKeyHex).decode('hex') pubEncryptionKey = highlevelcrypto.privToPub( privEncryptionKeyHex).decode('hex') payload += pubSigningKey[1:] payload += pubEncryptionKey[1:] # Do the POW for this pubkey message target = 2 ** 64 / ((len(payload) + shared.networkDefaultPayloadLengthExtraBytes + 8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) print '(For pubkey message) Doing proof of work...' initialHash = hashlib.sha512(payload).digest() trialValue, nonce = proofofwork.run(target, initialHash) print '(For pubkey message) Found proof of work', trialValue, 'Nonce:', nonce payload = pack('>Q', nonce) + payload inventoryHash = calculateInventoryHash(payload) objectType = 'pubkey' shared.inventory[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime,'') shared.inventorySets[streamNumber].add(inventoryHash) with shared.printLock: print 'broadcasting inv with hash:', inventoryHash.encode('hex') shared.broadcastToSendDataQueues(( streamNumber, 'advertiseobject', inventoryHash)) shared.UISignalQueue.put(('updateStatusBar', '')) try: shared.config.set( myAddress, 'lastpubkeysendtime', str(int(time.time()))) with open(shared.appdata + 'keys.dat', 'wb') as configfile: shared.config.write(configfile) except: # The user deleted the address out of the keys.dat file before this # finished. pass # If this isn't a chan address, this function assembles the pubkey data, # does the necessary POW and sends it out. If it *is* a chan then it # assembles the pubkey and stores is in the pubkey table so that we can # send messages to "ourselves". def sendOutOrStoreMyV3Pubkey(self, hash): try: myAddress = shared.myAddressesByHash[hash] except: #The address has been deleted. return if shared.safeConfigGetBoolean(myAddress, 'chan'): with shared.printLock: print 'This is a chan address. Not sending pubkey.' return status, addressVersionNumber, streamNumber, hash = decodeAddress( myAddress) embeddedTime = int(time.time() + random.randrange( -300, 300)) # the current time plus or minus five minutes payload = pack('>I', (embeddedTime)) payload += encodeVarint(addressVersionNumber) # Address version number payload += encodeVarint(streamNumber) payload += '\x00\x00\x00\x01' # bitfield of features supported by me (see the wiki). try: privSigningKeyBase58 = shared.config.get( myAddress, 'privsigningkey') privEncryptionKeyBase58 = shared.config.get( myAddress, 'privencryptionkey') except Exception as err: with shared.printLock: sys.stderr.write( 'Error within sendOutOrStoreMyV3Pubkey. Could not read the keys from the keys.dat file for a requested address. %s\n' % err) return privSigningKeyHex = shared.decodeWalletImportFormat( privSigningKeyBase58).encode('hex') privEncryptionKeyHex = shared.decodeWalletImportFormat( privEncryptionKeyBase58).encode('hex') pubSigningKey = highlevelcrypto.privToPub( privSigningKeyHex).decode('hex') pubEncryptionKey = highlevelcrypto.privToPub( privEncryptionKeyHex).decode('hex') payload += pubSigningKey[1:] payload += pubEncryptionKey[1:] payload += encodeVarint(shared.config.getint( myAddress, 'noncetrialsperbyte')) payload += encodeVarint(shared.config.getint( myAddress, 'payloadlengthextrabytes')) signature = highlevelcrypto.sign(payload, privSigningKeyHex) payload += encodeVarint(len(signature)) payload += signature # Do the POW for this pubkey message target = 2 ** 64 / ((len(payload) + shared.networkDefaultPayloadLengthExtraBytes + 8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) print '(For pubkey message) Doing proof of work...' initialHash = hashlib.sha512(payload).digest() trialValue, nonce = proofofwork.run(target, initialHash) print '(For pubkey message) Found proof of work', trialValue, 'Nonce:', nonce payload = pack('>Q', nonce) + payload inventoryHash = calculateInventoryHash(payload) objectType = 'pubkey' shared.inventory[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime,'') shared.inventorySets[streamNumber].add(inventoryHash) with shared.printLock: print 'broadcasting inv with hash:', inventoryHash.encode('hex') shared.broadcastToSendDataQueues(( streamNumber, 'advertiseobject', inventoryHash)) shared.UISignalQueue.put(('updateStatusBar', '')) try: shared.config.set( myAddress, 'lastpubkeysendtime', str(int(time.time()))) with open(shared.appdata + 'keys.dat', 'wb') as configfile: shared.config.write(configfile) except: # The user deleted the address out of the keys.dat file before this # finished. pass # If this isn't a chan address, this function assembles the pubkey data, # does the necessary POW and sends it out. def sendOutOrStoreMyV4Pubkey(self, myAddress): if not shared.config.has_section(myAddress): #The address has been deleted. return if shared.safeConfigGetBoolean(myAddress, 'chan'): with shared.printLock: print 'This is a chan address. Not sending pubkey.' return status, addressVersionNumber, streamNumber, hash = decodeAddress( myAddress) embeddedTime = int(time.time() + random.randrange( -300, 300)) # the current time plus or minus five minutes payload = pack('>Q', (embeddedTime)) payload += encodeVarint(addressVersionNumber) # Address version number payload += encodeVarint(streamNumber) dataToEncrypt = '\x00\x00\x00\x01' # bitfield of features supported by me (see the wiki). try: privSigningKeyBase58 = shared.config.get( myAddress, 'privsigningkey') privEncryptionKeyBase58 = shared.config.get( myAddress, 'privencryptionkey') except Exception as err: with shared.printLock: sys.stderr.write( 'Error within sendOutOrStoreMyV4Pubkey. Could not read the keys from the keys.dat file for a requested address. %s\n' % err) return privSigningKeyHex = shared.decodeWalletImportFormat( privSigningKeyBase58).encode('hex') privEncryptionKeyHex = shared.decodeWalletImportFormat( privEncryptionKeyBase58).encode('hex') pubSigningKey = highlevelcrypto.privToPub( privSigningKeyHex).decode('hex') pubEncryptionKey = highlevelcrypto.privToPub( privEncryptionKeyHex).decode('hex') dataToEncrypt += pubSigningKey[1:] dataToEncrypt += pubEncryptionKey[1:] dataToEncrypt += encodeVarint(shared.config.getint( myAddress, 'noncetrialsperbyte')) dataToEncrypt += encodeVarint(shared.config.getint( myAddress, 'payloadlengthextrabytes')) signature = highlevelcrypto.sign(payload + dataToEncrypt, privSigningKeyHex) dataToEncrypt += encodeVarint(len(signature)) dataToEncrypt += signature # Let us encrypt the necessary data. We will use a hash of the data # contained in an address as a decryption key. This way in order to # read the public keys in a pubkey message, a node must know the address # first. We'll also tag, 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(encodeVarint( addressVersionNumber) + encodeVarint(streamNumber) + hash).digest()).digest() payload += doubleHashOfAddressData[32:] # the tag privEncryptionKey = doubleHashOfAddressData[:32] pubEncryptionKey = highlevelcrypto.pointMult(privEncryptionKey) payload += highlevelcrypto.encrypt( dataToEncrypt, pubEncryptionKey.encode('hex')) # Do the POW for this pubkey message target = 2 ** 64 / ((len(payload) + shared.networkDefaultPayloadLengthExtraBytes + 8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) print '(For pubkey message) Doing proof of work...' initialHash = hashlib.sha512(payload).digest() trialValue, nonce = proofofwork.run(target, initialHash) print '(For pubkey message) Found proof of work', trialValue, 'Nonce:', nonce payload = pack('>Q', nonce) + payload inventoryHash = calculateInventoryHash(payload) objectType = 'pubkey' shared.inventory[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, doubleHashOfAddressData[32:]) shared.inventorySets[streamNumber].add(inventoryHash) with shared.printLock: print 'broadcasting inv with hash:', inventoryHash.encode('hex') shared.broadcastToSendDataQueues(( streamNumber, 'advertiseobject', inventoryHash)) shared.UISignalQueue.put(('updateStatusBar', '')) try: shared.config.set( myAddress, 'lastpubkeysendtime', str(int(time.time()))) with open(shared.appdata + 'keys.dat', 'wb') as configfile: shared.config.write(configfile) except Exception as err: logger.error('Error: Couldn\'t add the lastpubkeysendtime to the keys.dat file. Error message: %s' % err) def sendBroadcast(self): queryreturn = sqlQuery( '''SELECT fromaddress, subject, message, ackdata FROM sent WHERE status=? and folder='sent' ''', 'broadcastqueued') for row in queryreturn: fromaddress, subject, body, ackdata = row status, addressVersionNumber, streamNumber, ripe = decodeAddress( fromaddress) if addressVersionNumber <= 1: with shared.printLock: sys.stderr.write( 'Error: In the singleWorker thread, the sendBroadcast function doesn\'t understand the address version.\n') return # We need to convert our private keys to public keys in order # to include them. try: privSigningKeyBase58 = shared.config.get( fromaddress, 'privsigningkey') privEncryptionKeyBase58 = shared.config.get( fromaddress, 'privencryptionkey') except: shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( ackdata, tr.translateText("MainWindow", "Error! Could not find sender address (your address) in the keys.dat file.")))) continue privSigningKeyHex = shared.decodeWalletImportFormat( privSigningKeyBase58).encode('hex') privEncryptionKeyHex = shared.decodeWalletImportFormat( privEncryptionKeyBase58).encode('hex') pubSigningKey = highlevelcrypto.privToPub(privSigningKeyHex).decode( 'hex') # At this time these pubkeys are 65 bytes long because they include the encoding byte which we won't be sending in the broadcast message. pubEncryptionKey = highlevelcrypto.privToPub( privEncryptionKeyHex).decode('hex') payload = pack('>Q', (int(time.time()) + random.randrange( -300, 300))) # the current time plus or minus five minutes if addressVersionNumber <= 3: payload += encodeVarint(2) # broadcast version else: payload += encodeVarint(3) # broadcast version payload += encodeVarint(streamNumber) if addressVersionNumber >= 4: doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( addressVersionNumber) + encodeVarint(streamNumber) + ripe).digest()).digest() tag = doubleHashOfAddressData[32:] payload += tag else: tag = '' if addressVersionNumber <= 3: dataToEncrypt = encodeVarint(2) # broadcast version else: dataToEncrypt = encodeVarint(3) # broadcast version dataToEncrypt += encodeVarint(addressVersionNumber) dataToEncrypt += encodeVarint(streamNumber) dataToEncrypt += '\x00\x00\x00\x01' # behavior bitfield dataToEncrypt += pubSigningKey[1:] dataToEncrypt += pubEncryptionKey[1:] if addressVersionNumber >= 3: dataToEncrypt += encodeVarint(shared.config.getint(fromaddress,'noncetrialsperbyte')) dataToEncrypt += encodeVarint(shared.config.getint(fromaddress,'payloadlengthextrabytes')) dataToEncrypt += '\x02' # message encoding type dataToEncrypt += encodeVarint(len('Subject:' + subject + '\n' + 'Body:' + body)) #Type 2 is simple UTF-8 message encoding per the documentation on the wiki. dataToEncrypt += 'Subject:' + subject + '\n' + 'Body:' + body signature = highlevelcrypto.sign( dataToEncrypt, privSigningKeyHex) dataToEncrypt += encodeVarint(len(signature)) dataToEncrypt += signature # Encrypt the broadcast with the information contained in the broadcaster's address. Anyone who knows the address can generate # the private encryption key to decrypt the broadcast. This provides virtually no privacy; its purpose is to keep questionable # and illegal content from flowing through the Internet connections and being stored on the disk of 3rd parties. if addressVersionNumber <= 3: privEncryptionKey = hashlib.sha512(encodeVarint( addressVersionNumber) + encodeVarint(streamNumber) + ripe).digest()[:32] else: privEncryptionKey = doubleHashOfAddressData[:32] pubEncryptionKey = highlevelcrypto.pointMult(privEncryptionKey) payload += highlevelcrypto.encrypt( dataToEncrypt, pubEncryptionKey.encode('hex')) target = 2 ** 64 / ((len( payload) + shared.networkDefaultPayloadLengthExtraBytes + 8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) print '(For broadcast message) Doing proof of work...' shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( ackdata, tr.translateText("MainWindow", "Doing work necessary to send broadcast...")))) initialHash = hashlib.sha512(payload).digest() trialValue, nonce = proofofwork.run(target, initialHash) print '(For broadcast message) Found proof of work', trialValue, 'Nonce:', nonce payload = pack('>Q', nonce) + payload inventoryHash = calculateInventoryHash(payload) objectType = 'broadcast' shared.inventory[inventoryHash] = ( objectType, streamNumber, payload, int(time.time()),tag) shared.inventorySets[streamNumber].add(inventoryHash) with shared.printLock: print 'sending inv (within sendBroadcast function) for object:', inventoryHash.encode('hex') shared.broadcastToSendDataQueues(( streamNumber, 'advertiseobject', inventoryHash)) shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr.translateText("MainWindow", "Broadcast sent on %1").arg(unicode( strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(time.time()))), 'utf-8'))))) # Update the status of the message in the 'sent' table to have # a 'broadcastsent' status sqlExecute( 'UPDATE sent SET msgid=?, status=?, lastactiontime=? WHERE ackdata=?', inventoryHash, 'broadcastsent', int(time.time()), ackdata) def sendMsg(self): # Check to see if there are any messages queued to be sent queryreturn = sqlQuery( '''SELECT DISTINCT toaddress FROM sent WHERE (status='msgqueued' AND folder='sent')''') for row in queryreturn: # For each address to which we need to send a message, check to see if we have its pubkey already. toaddress, = row status, toAddressVersion, toStreamNumber, toRipe = decodeAddress(toaddress) # If we are sending a message to ourselves or a chan then we won't need an entry in the pubkeys table; we can calculate the needed pubkey using the private keys in our keys.dat file. if shared.config.has_section(toaddress): sqlExecute( '''UPDATE sent SET status='doingmsgpow' WHERE toaddress=? AND status='msgqueued' ''', toaddress) continue queryreturn = sqlQuery( '''SELECT hash FROM pubkeys WHERE hash=? AND addressversion=?''', toRipe, toAddressVersion) if queryreturn != []: # If we have the needed pubkey in the pubkey table already, set the status to doingmsgpow (we'll do it further down) sqlExecute( '''UPDATE sent SET status='doingmsgpow' WHERE toaddress=? AND status='msgqueued' ''', toaddress) else: # We don't have the needed pubkey in the pubkey table already. if toAddressVersion <= 3: toTag = '' else: toTag = hashlib.sha512(hashlib.sha512(encodeVarint(toAddressVersion)+encodeVarint(toStreamNumber)+toRipe).digest()).digest()[32:] if toRipe in shared.neededPubkeys or toTag in shared.neededPubkeys: # We already sent a request for the pubkey sqlExecute( '''UPDATE sent SET status='awaitingpubkey' WHERE toaddress=? AND status='msgqueued' ''', toaddress) shared.UISignalQueue.put(('updateSentItemStatusByHash', ( toRipe, tr.translateText("MainWindow",'Encryption key was requested earlier.')))) else: # We have not yet sent a request for the pubkey needToRequestPubkey = True if toAddressVersion >= 4: # If we are trying to send to address version >= 4 then the needed pubkey might be encrypted in the inventory. # If we have it we'll need to decrypt it and put it in the pubkeys table. queryreturn = sqlQuery( '''SELECT payload FROM inventory WHERE objecttype='pubkey' and tag=? ''', toTag) if queryreturn != []: # if there was a pubkey in our inventory with the correct tag, we need to try to decrypt it. for row in queryreturn: data, = row if shared.decryptAndCheckPubkeyPayload(data, toaddress) == 'successful': needToRequestPubkey = False print 'debug. successfully decrypted and checked pubkey from sql inventory.' #testing sqlExecute( '''UPDATE sent SET status='doingmsgpow' WHERE toaddress=? AND status='msgqueued' ''', toaddress) break else: # There was something wrong with this pubkey even though it had the correct tag- almost certainly because of malicious behavior or a badly programmed client. continue if needToRequestPubkey: # Obviously we had no success looking in the sql inventory. Let's look through the memory inventory. with shared.inventoryLock: for hash, storedValue in shared.inventory.items(): objectType, streamNumber, payload, receivedTime, tag = storedValue if objectType == 'pubkey' and tag == toTag: result = shared.decryptAndCheckPubkeyPayload(payload, toaddress) #if valid, this function also puts it in the pubkeys table. if result == 'successful': print 'debug. successfully decrypted and checked pubkey from memory inventory.' needToRequestPubkey = False sqlExecute( '''UPDATE sent SET status='doingmsgpow' WHERE toaddress=? AND status='msgqueued' ''', toaddress) break if needToRequestPubkey: sqlExecute( '''UPDATE sent SET status='doingpubkeypow' WHERE toaddress=? AND status='msgqueued' ''', toaddress) shared.UISignalQueue.put(('updateSentItemStatusByHash', ( toRipe, tr.translateText("MainWindow",'Sending a request for the recipient\'s encryption key.')))) self.requestPubKey(toaddress) # Get all messages that are ready to be sent, and also all messages # which we have sent in the last 28 days which were previously marked # as 'toodifficult'. If the user as raised the maximum acceptable # difficulty then those messages may now be sendable. queryreturn = sqlQuery( '''SELECT toaddress, toripe, fromaddress, subject, message, ackdata, status FROM sent WHERE (status='doingmsgpow' or status='forcepow' or (status='toodifficult' and lastactiontime>?)) and folder='sent' ''', int(time.time()) - 2419200) for row in queryreturn: # For each message we need to send.. toaddress, toripe, fromaddress, subject, message, ackdata, status = row toStatus, toAddressVersionNumber, toStreamNumber, toHash = decodeAddress( toaddress) fromStatus, fromAddressVersionNumber, fromStreamNumber, fromHash = decodeAddress( fromaddress) if not shared.config.has_section(toaddress): # There is a remote possibility that we may no longer have the # recipient's pubkey. Let us make sure we still have it or else the # sendMsg function will appear to freeze. This can happen if the # user sends a message but doesn't let the POW function finish, # then leaves their client off for a long time which could cause # the needed pubkey to expire and be deleted. queryreturn = sqlQuery( '''SELECT hash FROM pubkeys WHERE hash=? AND addressversion=?''', toripe, toAddressVersionNumber) if queryreturn == [] and toripe not in shared.neededPubkeys: # We no longer have the needed pubkey and we haven't requested # it. with shared.printLock: sys.stderr.write( 'For some reason, the status of a message in our outbox is \'doingmsgpow\' even though we lack the pubkey. Here is the RIPE hash of the needed pubkey: %s\n' % toripe.encode('hex')) sqlExecute( '''UPDATE sent SET status='doingpubkeypow' WHERE toaddress=? AND status='doingmsgpow' ''', toaddress) shared.UISignalQueue.put(('updateSentItemStatusByHash', ( toripe, tr.translateText("MainWindow",'Sending a request for the recipient\'s encryption key.')))) self.requestPubKey(toaddress) continue shared.ackdataForWhichImWatching[ackdata] = 0 shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( ackdata, tr.translateText("MainWindow", "Looking up the receiver\'s public key")))) with shared.printLock: print 'Sending a message. First 150 characters of message:', repr(message[:150]) # mark the pubkey as 'usedpersonally' so that we don't ever delete # it. sqlExecute( '''UPDATE pubkeys SET usedpersonally='yes' WHERE hash=? and addressversion=?''', toripe, toAddressVersionNumber) # Let us fetch the recipient's public key out of our database. If # the required proof of work difficulty is too hard then we'll # abort. queryreturn = sqlQuery( 'SELECT transmitdata FROM pubkeys WHERE hash=? and addressversion=?', toripe, toAddressVersionNumber) if queryreturn == []: with shared.printLock: sys.stderr.write( '(within sendMsg) The needed pubkey was not found. This should never happen. Aborting send.\n') return for row in queryreturn: pubkeyPayload, = row # The pubkey message is stored the way we originally received it # which means that we need to read beyond things like the nonce and # time to get to the actual public keys. if toAddressVersionNumber <= 3: readPosition = 8 # to bypass the nonce elif toAddressVersionNumber >= 4: readPosition = 0 # the nonce is not included here so we don't need to skip over it. pubkeyEmbeddedTime, = unpack( '>I', pubkeyPayload[readPosition:readPosition + 4]) # This section is used for the transition from 32 bit time to 64 # bit time in the protocol. if pubkeyEmbeddedTime == 0: pubkeyEmbeddedTime, = unpack( '>Q', pubkeyPayload[readPosition:readPosition + 8]) readPosition += 8 else: readPosition += 4 readPosition += 1 # to bypass the address version whose length is definitely 1 streamNumber, streamNumberLength = decodeVarint( pubkeyPayload[readPosition:readPosition + 10]) readPosition += streamNumberLength behaviorBitfield = pubkeyPayload[readPosition:readPosition + 4] # Mobile users may ask us to include their address's RIPE hash on a message # unencrypted. Before we actually do it the sending human must check a box # in the settings menu to allow it. if shared.isBitSetWithinBitfield(behaviorBitfield,30): # if receiver is a mobile device who expects that their address RIPE is included unencrypted on the front of the message.. if not shared.safeConfigGetBoolean('bitmessagesettings','willinglysendtomobile'): # if we are Not willing to include the receiver's RIPE hash on the message.. logger.info('The receiver is a mobile user but the sender (you) has not selected that you are willing to send to mobiles. Aborting send.') shared.UISignalQueue.put(('updateSentItemStatusByAckdata',(ackdata,tr.translateText("MainWindow",'Problem: Destination is a mobile device who requests that the destination be included in the message but this is disallowed in your settings. %1').arg(unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8'))))) # if the human changes their setting and then sends another message or restarts their client, this one will send at that time. continue readPosition += 4 # to bypass the bitfield of behaviors # pubSigningKeyBase256 = # pubkeyPayload[readPosition:readPosition+64] #We don't use this # key for anything here. readPosition += 64 pubEncryptionKeyBase256 = pubkeyPayload[ readPosition:readPosition + 64] readPosition += 64 # Let us fetch the amount of work required by the recipient. if toAddressVersionNumber == 2: requiredAverageProofOfWorkNonceTrialsPerByte = shared.networkDefaultProofOfWorkNonceTrialsPerByte requiredPayloadLengthExtraBytes = shared.networkDefaultPayloadLengthExtraBytes shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( ackdata, tr.translateText("MainWindow", "Doing work necessary to send message.\nThere is no required difficulty for version 2 addresses like this.")))) elif toAddressVersionNumber >= 3: requiredAverageProofOfWorkNonceTrialsPerByte, varintLength = decodeVarint( pubkeyPayload[readPosition:readPosition + 10]) readPosition += varintLength requiredPayloadLengthExtraBytes, varintLength = decodeVarint( pubkeyPayload[readPosition:readPosition + 10]) readPosition += varintLength if requiredAverageProofOfWorkNonceTrialsPerByte < shared.networkDefaultProofOfWorkNonceTrialsPerByte: # We still have to meet a minimum POW difficulty regardless of what they say is allowed in order to get our message to propagate through the network. requiredAverageProofOfWorkNonceTrialsPerByte = shared.networkDefaultProofOfWorkNonceTrialsPerByte if requiredPayloadLengthExtraBytes < shared.networkDefaultPayloadLengthExtraBytes: requiredPayloadLengthExtraBytes = shared.networkDefaultPayloadLengthExtraBytes shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr.translateText("MainWindow", "Doing work necessary to send message.\nReceiver\'s required difficulty: %1 and %2").arg(str(float( requiredAverageProofOfWorkNonceTrialsPerByte) / shared.networkDefaultProofOfWorkNonceTrialsPerByte)).arg(str(float(requiredPayloadLengthExtraBytes) / shared.networkDefaultPayloadLengthExtraBytes))))) if status != 'forcepow': if (requiredAverageProofOfWorkNonceTrialsPerByte > shared.config.getint('bitmessagesettings', 'maxacceptablenoncetrialsperbyte') and shared.config.getint('bitmessagesettings', 'maxacceptablenoncetrialsperbyte') != 0) or (requiredPayloadLengthExtraBytes > shared.config.getint('bitmessagesettings', 'maxacceptablepayloadlengthextrabytes') and shared.config.getint('bitmessagesettings', 'maxacceptablepayloadlengthextrabytes') != 0): # The demanded difficulty is more than we are willing # to do. sqlExecute( '''UPDATE sent SET status='toodifficult' WHERE ackdata=? ''', ackdata) shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr.translateText("MainWindow", "Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do.").arg(str(float(requiredAverageProofOfWorkNonceTrialsPerByte) / shared.networkDefaultProofOfWorkNonceTrialsPerByte)).arg(str(float( requiredPayloadLengthExtraBytes) / shared.networkDefaultPayloadLengthExtraBytes)).arg(unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(time.time()))), 'utf-8'))))) continue else: # if we are sending a message to ourselves or a chan.. with shared.printLock: print 'Sending a message. First 150 characters of message:', repr(message[:150]) behaviorBitfield = '\x00\x00\x00\x01' try: privEncryptionKeyBase58 = shared.config.get( toaddress, 'privencryptionkey') except Exception as err: shared.UISignalQueue.put(('updateSentItemStatusByAckdata',(ackdata,tr.translateText("MainWindow",'Problem: You are trying to send a message to yourself or a chan but your encryption key could not be found in the keys.dat file. Could not encrypt message. %1').arg(unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8'))))) with shared.printLock: sys.stderr.write( 'Error within sendMsg. Could not read the keys from the keys.dat file for our own address. %s\n' % err) continue privEncryptionKeyHex = shared.decodeWalletImportFormat( privEncryptionKeyBase58).encode('hex') pubEncryptionKeyBase256 = highlevelcrypto.privToPub( privEncryptionKeyHex).decode('hex')[1:] requiredAverageProofOfWorkNonceTrialsPerByte = shared.networkDefaultProofOfWorkNonceTrialsPerByte requiredPayloadLengthExtraBytes = shared.networkDefaultPayloadLengthExtraBytes shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( ackdata, tr.translateText("MainWindow", "Doing work necessary to send message.")))) embeddedTime = pack('>Q', (int(time.time()) + random.randrange( -300, 300))) # the current time plus or minus five minutes. if fromAddressVersionNumber == 2: payload = '\x01' # Message version. payload += encodeVarint(fromAddressVersionNumber) payload += encodeVarint(fromStreamNumber) payload += '\x00\x00\x00\x01' # Bitfield of features and behaviors that can be expected from me. (See https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features ) # We need to convert our private keys to public keys in order # to include them. try: privSigningKeyBase58 = shared.config.get( fromaddress, 'privsigningkey') privEncryptionKeyBase58 = shared.config.get( fromaddress, 'privencryptionkey') except: shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( ackdata, tr.translateText("MainWindow", "Error! Could not find sender address (your address) in the keys.dat file.")))) continue privSigningKeyHex = shared.decodeWalletImportFormat( privSigningKeyBase58).encode('hex') privEncryptionKeyHex = shared.decodeWalletImportFormat( privEncryptionKeyBase58).encode('hex') pubSigningKey = highlevelcrypto.privToPub( privSigningKeyHex).decode('hex') pubEncryptionKey = highlevelcrypto.privToPub( privEncryptionKeyHex).decode('hex') payload += pubSigningKey[ 1:] # The \x04 on the beginning of the public keys are not sent. This way there is only one acceptable way to encode and send a public key. payload += pubEncryptionKey[1:] payload += toHash # This hash will be checked by the receiver of the message to verify that toHash belongs to them. This prevents a Surreptitious Forwarding Attack. payload += '\x02' # Type 2 is simple UTF-8 message encoding as specified on the Protocol Specification on the Bitmessage Wiki. messageToTransmit = 'Subject:' + \ subject + '\n' + 'Body:' + message payload += encodeVarint(len(messageToTransmit)) payload += messageToTransmit fullAckPayload = self.generateFullAckMessage( ackdata, toStreamNumber) # The fullAckPayload is a normal msg protocol message with the proof of work already completed that the receiver of this message can easily send out. payload += encodeVarint(len(fullAckPayload)) payload += fullAckPayload signature = highlevelcrypto.sign(payload, privSigningKeyHex) payload += encodeVarint(len(signature)) payload += signature if fromAddressVersionNumber >= 3: payload = '\x01' # Message version. payload += encodeVarint(fromAddressVersionNumber) payload += encodeVarint(fromStreamNumber) payload += '\x00\x00\x00\x01' # Bitfield of features and behaviors that can be expected from me. (See https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features ) # We need to convert our private keys to public keys in order # to include them. try: privSigningKeyBase58 = shared.config.get( fromaddress, 'privsigningkey') privEncryptionKeyBase58 = shared.config.get( fromaddress, 'privencryptionkey') except: shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( ackdata, tr.translateText("MainWindow", "Error! Could not find sender address (your address) in the keys.dat file.")))) continue privSigningKeyHex = shared.decodeWalletImportFormat( privSigningKeyBase58).encode('hex') privEncryptionKeyHex = shared.decodeWalletImportFormat( privEncryptionKeyBase58).encode('hex') pubSigningKey = highlevelcrypto.privToPub( privSigningKeyHex).decode('hex') pubEncryptionKey = highlevelcrypto.privToPub( privEncryptionKeyHex).decode('hex') payload += pubSigningKey[ 1:] # The \x04 on the beginning of the public keys are not sent. This way there is only one acceptable way to encode and send a public key. payload += pubEncryptionKey[1:] # If the receiver of our message is in our address book, # subscriptions list, or whitelist then we will allow them to # do the network-minimum proof of work. Let us check to see if # the receiver is in any of those lists. if shared.isAddressInMyAddressBookSubscriptionsListOrWhitelist(toaddress): payload += encodeVarint( shared.networkDefaultProofOfWorkNonceTrialsPerByte) payload += encodeVarint( shared.networkDefaultPayloadLengthExtraBytes) else: payload += encodeVarint(shared.config.getint( fromaddress, 'noncetrialsperbyte')) payload += encodeVarint(shared.config.getint( fromaddress, 'payloadlengthextrabytes')) payload += toHash # This hash will be checked by the receiver of the message to verify that toHash belongs to them. This prevents a Surreptitious Forwarding Attack. payload += '\x02' # Type 2 is simple UTF-8 message encoding as specified on the Protocol Specification on the Bitmessage Wiki. messageToTransmit = 'Subject:' + \ subject + '\n' + 'Body:' + message payload += encodeVarint(len(messageToTransmit)) payload += messageToTransmit if shared.config.has_section(toaddress): with shared.printLock: print 'Not bothering to include ackdata because we are sending to ourselves or a chan.' fullAckPayload = '' elif not shared.isBitSetWithinBitfield(behaviorBitfield,31): with shared.printLock: print 'Not bothering to include ackdata because the receiver said that they won\'t relay it anyway.' fullAckPayload = '' else: fullAckPayload = self.generateFullAckMessage( ackdata, toStreamNumber) # The fullAckPayload is a normal msg protocol message with the proof of work already completed that the receiver of this message can easily send out. payload += encodeVarint(len(fullAckPayload)) payload += fullAckPayload signature = highlevelcrypto.sign(payload, privSigningKeyHex) payload += encodeVarint(len(signature)) payload += signature print 'using pubEncryptionKey:', pubEncryptionKeyBase256.encode('hex') # We have assembled the data that will be encrypted. try: encrypted = highlevelcrypto.encrypt(payload,"04"+pubEncryptionKeyBase256.encode('hex')) except: sqlExecute('''UPDATE sent SET status='badkey' WHERE ackdata=?''', ackdata) shared.UISignalQueue.put(('updateSentItemStatusByAckdata',(ackdata,tr.translateText("MainWindow",'Problem: The recipient\'s encryption key is no good. Could not encrypt message. %1').arg(unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8'))))) continue encryptedPayload = embeddedTime + encodeVarint(toStreamNumber) + encrypted target = 2**64 / ((len(encryptedPayload)+requiredPayloadLengthExtraBytes+8) * requiredAverageProofOfWorkNonceTrialsPerByte) with shared.printLock: print '(For msg message) Doing proof of work. Total required difficulty:', float(requiredAverageProofOfWorkNonceTrialsPerByte) / shared.networkDefaultProofOfWorkNonceTrialsPerByte, 'Required small message difficulty:', float(requiredPayloadLengthExtraBytes) / shared.networkDefaultPayloadLengthExtraBytes powStartTime = time.time() initialHash = hashlib.sha512(encryptedPayload).digest() trialValue, nonce = proofofwork.run(target, initialHash) with shared.printLock: print '(For msg message) Found proof of work', trialValue, 'Nonce:', nonce try: print 'POW took', int(time.time() - powStartTime), 'seconds.', nonce / (time.time() - powStartTime), 'nonce trials per second.' except: pass encryptedPayload = pack('>Q', nonce) + encryptedPayload inventoryHash = calculateInventoryHash(encryptedPayload) objectType = 'msg' shared.inventory[inventoryHash] = ( objectType, toStreamNumber, encryptedPayload, int(time.time()),'') shared.inventorySets[toStreamNumber].add(inventoryHash) if shared.config.has_section(toaddress): shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr.translateText("MainWindow", "Message sent. Sent on %1").arg(unicode( strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(time.time()))), 'utf-8'))))) else: # not sending to a chan or one of my addresses shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr.translateText("MainWindow", "Message sent. Waiting for acknowledgement. Sent on %1").arg(unicode( strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(time.time()))), 'utf-8'))))) print 'Broadcasting inv for my msg(within sendmsg function):', inventoryHash.encode('hex') shared.broadcastToSendDataQueues(( toStreamNumber, 'advertiseobject', inventoryHash)) # Update the status of the message in the 'sent' table to have a # 'msgsent' status or 'msgsentnoackexpected' status. if shared.config.has_section(toaddress): newStatus = 'msgsentnoackexpected' else: newStatus = 'msgsent' sqlExecute('''UPDATE sent SET msgid=?, status=? WHERE ackdata=?''', inventoryHash,newStatus,ackdata) # If we are sending to ourselves or a chan, let's put the message in # our own inbox. if shared.config.has_section(toaddress): t = (inventoryHash, toaddress, fromaddress, subject, int( time.time()), message, 'inbox', 2, 0) helper_inbox.insert(t) shared.UISignalQueue.put(('displayNewInboxMessage', ( inventoryHash, toaddress, fromaddress, subject, message))) # If we are behaving as an API then we might need to run an # outside command to let some program know that a new message # has arrived. if shared.safeConfigGetBoolean('bitmessagesettings', 'apienabled'): try: apiNotifyPath = shared.config.get( 'bitmessagesettings', 'apinotifypath') except: apiNotifyPath = '' if apiNotifyPath != '': call([apiNotifyPath, "newMessage"]) def requestPubKey(self, toAddress): toStatus, addressVersionNumber, streamNumber, ripe = decodeAddress( toAddress) if toStatus != 'success': with shared.printLock: sys.stderr.write('Very abnormal error occurred in requestPubKey. toAddress is: ' + repr( toAddress) + '. Please report this error to Atheros.') return if addressVersionNumber <= 3: shared.neededPubkeys[ripe] = 0 elif addressVersionNumber >= 4: privEncryptionKey = hashlib.sha512(hashlib.sha512(encodeVarint(addressVersionNumber)+encodeVarint(streamNumber)+ripe).digest()).digest()[:32] # Note that this is the first half of the sha512 hash. tag = hashlib.sha512(hashlib.sha512(encodeVarint(addressVersionNumber)+encodeVarint(streamNumber)+ripe).digest()).digest()[32:] # Note that this is the second half of the sha512 hash. shared.neededPubkeys[tag] = highlevelcrypto.makeCryptor(privEncryptionKey.encode('hex')) # We'll need this for when we receive a pubkey reply: it will be encrypted and we'll need to decrypt it. payload = pack('>Q', (int(time.time()) + random.randrange( -300, 300))) # the current time plus or minus five minutes. payload += encodeVarint(addressVersionNumber) payload += encodeVarint(streamNumber) if addressVersionNumber <= 3: payload += ripe with shared.printLock: print 'making request for pubkey with ripe:', ripe.encode('hex') else: payload += tag with shared.printLock: print 'making request for v4 pubkey with tag:', tag.encode('hex') # print 'trial value', trialValue statusbar = 'Doing the computations necessary to request the recipient\'s public key.' shared.UISignalQueue.put(('updateStatusBar', statusbar)) shared.UISignalQueue.put(('updateSentItemStatusByHash', ( ripe, tr.translateText("MainWindow",'Doing work necessary to request encryption key.')))) target = 2 ** 64 / ((len(payload) + shared.networkDefaultPayloadLengthExtraBytes + 8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) initialHash = hashlib.sha512(payload).digest() trialValue, nonce = proofofwork.run(target, initialHash) with shared.printLock: print 'Found proof of work', trialValue, 'Nonce:', nonce payload = pack('>Q', nonce) + payload inventoryHash = calculateInventoryHash(payload) objectType = 'getpubkey' shared.inventory[inventoryHash] = ( objectType, streamNumber, payload, int(time.time()),'') shared.inventorySets[streamNumber].add(inventoryHash) print 'sending inv (for the getpubkey message)' shared.broadcastToSendDataQueues(( streamNumber, 'advertiseobject', inventoryHash)) sqlExecute( '''UPDATE sent SET status='awaitingpubkey' WHERE toaddress=? AND status='doingpubkeypow' ''', toAddress) shared.UISignalQueue.put(( 'updateStatusBar', tr.translateText("MainWindow",'Broacasting the public key request. This program will auto-retry if they are offline.'))) shared.UISignalQueue.put(('updateSentItemStatusByHash', (ripe, tr.translateText("MainWindow",'Sending public key request. Waiting for reply. Requested at %1').arg(unicode( strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(time.time()))), 'utf-8'))))) def generateFullAckMessage(self, ackdata, toStreamNumber): embeddedTime = pack('>Q', (int(time.time()) + random.randrange( -300, 300))) # the current time plus or minus five minutes. payload = embeddedTime + encodeVarint(toStreamNumber) + ackdata target = 2 ** 64 / ((len(payload) + shared.networkDefaultPayloadLengthExtraBytes + 8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) with shared.printLock: print '(For ack message) Doing proof of work...' powStartTime = time.time() initialHash = hashlib.sha512(payload).digest() trialValue, nonce = proofofwork.run(target, initialHash) with shared.printLock: print '(For ack message) Found proof of work', trialValue, 'Nonce:', nonce try: print 'POW took', int(time.time() - powStartTime), 'seconds.', nonce / (time.time() - powStartTime), 'nonce trials per second.' except: pass payload = pack('>Q', nonce) + payload return shared.CreatePacket('msg', payload)