From 498928405bc673f2066682af36a28c7a22dff3ba Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Sun, 29 Sep 2013 19:24:27 -0400 Subject: [PATCH] Chan fix: initial work completed --- src/bitmessageqt/__init__.py | 9 - src/class_receiveDataThread.py | 38 +-- src/class_singleWorker.py | 406 +++++++++++++++++---------------- src/class_sqlThread.py | 24 +- src/shared.py | 4 +- 5 files changed, 249 insertions(+), 232 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 8cbea0f9..2bd2bb6b 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -1564,15 +1564,6 @@ class MyForm(QtGui.QMainWindow): "MainWindow", "Error: You must specify a From address. If you don\'t have one, go to the \'Your Identities\' tab.")) else: toAddress = addBMIfNotPresent(toAddress) - try: - shared.config.get(toAddress, 'enabled') - # The toAddress is one owned by me. - if not shared.safeConfigGetBoolean(toAddress, 'chan'): - QMessageBox.about(self, _translate("MainWindow", "Sending to your address"), _translate( - "MainWindow", "Error: One of the addresses to which you are sending a message, %1, is yours. Unfortunately the Bitmessage client cannot process its own messages. Please try running a second client on a different computer or within a VM.").arg(toAddress)) - continue - except: - pass if addressVersionNumber > 4 or addressVersionNumber <= 1: QMessageBox.about(self, _translate("MainWindow", "Address version number"), _translate( "MainWindow", "Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version.").arg(toAddress).arg(str(addressVersionNumber))) diff --git a/src/class_receiveDataThread.py b/src/class_receiveDataThread.py index f9fe403e..97edfb21 100644 --- a/src/class_receiveDataThread.py +++ b/src/class_receiveDataThread.py @@ -1,4 +1,4 @@ -doTimingAttackMitigation = True +doTimingAttackMitigation = False import time import threading @@ -514,8 +514,9 @@ class receiveDataThread(threading.Thread): # the proof of work ourselves, which this program is programmed # to not do.) sqlExecute( - '''INSERT INTO pubkeys VALUES (?,?,?,?)''', + '''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', ripe.digest(), + sendersAddressVersion, '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' + '\xFF\xFF\xFF\xFF' + data[beginningOfPubkeyPosition:endOfPubkeyPosition], int(time.time()), 'yes') @@ -655,8 +656,9 @@ class receiveDataThread(threading.Thread): # Let's store the public key in case we want to reply to this # person. - sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?)''', + sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', ripe.digest(), + sendersAddressVersion, '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' + '\xFF\xFF\xFF\xFF' + decryptedData[beginningOfPubkeyPosition:endOfPubkeyPosition], int(time.time()), 'yes') @@ -806,8 +808,9 @@ class receiveDataThread(threading.Thread): # Let's store the public key in case we want to reply to this person. sqlExecute( - '''INSERT INTO pubkeys VALUES (?,?,?,?)''', + '''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', calculatedRipe, + sendersAddressVersion, '\x00\x00\x00\x00\x00\x00\x00\x01' + decryptedData[beginningOfPubkeyPosition:endOfPubkeyPosition], int(time.time()), 'yes') @@ -1070,8 +1073,9 @@ class receiveDataThread(threading.Thread): # person. if sendersAddressVersionNumber <= 3: sqlExecute( - '''INSERT INTO pubkeys VALUES (?,?,?,?)''', + '''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', ripe.digest(), + sendersAddressVersionNumber, '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' + '\xFF\xFF\xFF\xFF' + decryptedData[messageVersionLength:endOfThePublicKeyPosition], int(time.time()), 'yes') @@ -1081,8 +1085,9 @@ class receiveDataThread(threading.Thread): self.possibleNewPubkey(ripe=ripe.digest()) elif sendersAddressVersionNumber >= 4: sqlExecute( - '''INSERT INTO pubkeys VALUES (?,?,?,?)''', + '''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', ripe.digest(), + sendersAddressVersionNumber, '\x00\x00\x00\x00\x00\x00\x00\x01' + decryptedData[messageVersionLength:endOfThePublicKeyPosition], int(time.time()), 'yes') @@ -1409,15 +1414,15 @@ class receiveDataThread(threading.Thread): queryreturn = sqlQuery( - '''SELECT usedpersonally FROM pubkeys WHERE hash=? AND usedpersonally='yes' ''', ripe) + '''SELECT usedpersonally FROM pubkeys WHERE hash=? AND addressversion=? AND usedpersonally='yes' ''', ripe, addressVersion) if queryreturn != []: # if this pubkey is already in our database and if we have used it personally: print 'We HAVE used this pubkey personally. Updating time.' - t = (ripe, data, embeddedTime, 'yes') + t = (ripe, addressVersion, data, embeddedTime, 'yes') else: print 'We have NOT used this pubkey personally. Inserting in database.' - t = (ripe, data, embeddedTime, 'no') + t = (ripe, addressVersion, data, embeddedTime, 'no') # This will also update the embeddedTime. - sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?)''', *t) + sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t) # shared.workerQueue.put(('newpubkey',(addressVersion,streamNumber,ripe))) self.possibleNewPubkey(ripe = ripe) if addressVersion == 3: @@ -1466,19 +1471,18 @@ class receiveDataThread(threading.Thread): print 'publicEncryptionKey in hex:', publicEncryptionKey.encode('hex') - queryreturn = sqlQuery('''SELECT usedpersonally FROM pubkeys WHERE hash=? AND usedpersonally='yes' ''', ripe) + queryreturn = sqlQuery('''SELECT usedpersonally FROM pubkeys WHERE hash=? AND addressversion=? AND usedpersonally='yes' ''', ripe, addressVersion) if queryreturn != []: # if this pubkey is already in our database and if we have used it personally: print 'We HAVE used this pubkey personally. Updating time.' - t = (ripe, data, embeddedTime, 'yes') + t = (ripe, addressVersion, data, embeddedTime, 'yes') else: print 'We have NOT used this pubkey personally. Inserting in database.' - t = (ripe, data, embeddedTime, 'no') + t = (ripe, addressVersion, data, embeddedTime, 'no') # This will also update the embeddedTime. - sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?)''', *t) + sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t) self.possibleNewPubkey(ripe = ripe) if addressVersion == 4: - print 'length of v4 pubkey:', len(data) if len(data) < 350: # sanity check. print '(within processpubkey) payloadLength less than 350. Sanity check failed.' return @@ -1554,8 +1558,8 @@ class receiveDataThread(threading.Thread): print 'publicSigningKey in hex:', publicSigningKey.encode('hex') print 'publicEncryptionKey in hex:', publicEncryptionKey.encode('hex') - t = (ripe, signedData, embeddedTime, 'yes') - sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?)''', *t) + t = (ripe, addressVersion, signedData, embeddedTime, 'yes') + sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t) fromAddress = encodeAddress(addressVersion, streamNumber, ripe) # That this point we know that we have been waiting on this pubkey. diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index 8a66c125..a74c2892 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -156,6 +156,10 @@ class singleWorker(threading.Thread): # send messages to "ourselves". def sendOutOrStoreMyV3Pubkey(self, hash): myAddress = shared.myAddressesByHash[hash] + 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( @@ -197,49 +201,39 @@ class singleWorker(threading.Thread): payload += encodeVarint(len(signature)) payload += signature - if not shared.safeConfigGetBoolean(myAddress, 'chan'): - # 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 + # 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) + 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') + with shared.printLock: + print 'broadcasting inv with hash:', inventoryHash.encode('hex') - shared.broadcastToSendDataQueues(( - streamNumber, 'advertiseobject', inventoryHash)) - shared.UISignalQueue.put(('updateStatusBar', '')) - # If this is a chan address then we won't send out the pubkey over the - # network but rather will only store it in our pubkeys table so that - # we can send messages to "ourselves". - if shared.safeConfigGetBoolean(myAddress, 'chan'): - payload = '\x00' * 8 + payload # Attach a fake nonce on the front - # just so that it is in the correct format. - sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?)''', - hash, - payload, - embeddedTime, - 'yes') + shared.broadcastToSendDataQueues(( + streamNumber, 'advertiseobject', inventoryHash)) + shared.UISignalQueue.put(('updateStatusBar', '')) shared.config.set( myAddress, 'lastpubkeysendtime', str(int(time.time()))) with open(shared.appdata + 'keys.dat', 'wb') as configfile: shared.config.write(configfile) # 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". + # does the necessary POW and sends it out. def sendOutOrStoreMyV4Pubkey(self, myAddress): + 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( @@ -284,52 +278,41 @@ class singleWorker(threading.Thread): dataToEncrypt += encodeVarint(len(signature)) dataToEncrypt += signature - if not shared.safeConfigGetBoolean(myAddress, 'chan'): - # 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 = pointMult(privEncryptionKey) - payload += highlevelcrypto.encrypt( - dataToEncrypt, pubEncryptionKey.encode('hex')) + # 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 = 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 + # 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) + 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') + with shared.printLock: + print 'broadcasting inv with hash:', inventoryHash.encode('hex') - shared.broadcastToSendDataQueues(( - streamNumber, 'advertiseobject', inventoryHash)) - shared.UISignalQueue.put(('updateStatusBar', '')) - # If this is a chan address then we won't send out the pubkey over the - # network but rather will only store it in our pubkeys table so that - # we can send messages to "ourselves". - if shared.safeConfigGetBoolean(myAddress, 'chan'): - - sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?)''', - hash, - dataToStoreInOurPubkeysTable, - embeddedTime, - 'yes') + shared.broadcastToSendDataQueues(( + streamNumber, 'advertiseobject', inventoryHash)) + shared.UISignalQueue.put(('updateStatusBar', '')) shared.config.set( myAddress, 'lastpubkeysendtime', str(int(time.time()))) with open(shared.appdata + 'keys.dat', 'wb') as configfile: @@ -369,8 +352,6 @@ class singleWorker(threading.Thread): pubEncryptionKey = highlevelcrypto.privToPub( privEncryptionKeyHex).decode('hex') - print 'embedding pubEncryptionKey:', pubEncryptionKey.encode('hex') - payload = pack('>Q', (int(time.time()) + random.randrange( -300, 300))) # the current time plus or minus five minutes if addressVersionNumber <= 3: @@ -382,8 +363,6 @@ class singleWorker(threading.Thread): doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( addressVersionNumber) + encodeVarint(streamNumber) + ripe).digest()).digest() payload += doubleHashOfAddressData[32:] # the tag - print 'embeddedTag is', doubleHashOfAddressData[32:].encode('hex') - print 'embeddedTag is', repr(doubleHashOfAddressData[32:]) if addressVersionNumber <= 3: dataToEncrypt = encodeVarint(2) # broadcast version @@ -458,8 +437,11 @@ class singleWorker(threading.Thread): 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): + continue queryreturn = sqlQuery( - '''SELECT hash FROM pubkeys WHERE hash=? ''', toRipe) + '''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' ''', @@ -523,130 +505,154 @@ class singleWorker(threading.Thread): int(time.time()) - 2419200) for row in queryreturn: # For each message we need to send.. toaddress, toripe, fromaddress, subject, message, ackdata, status = row - # 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=? ''', - toripe) - 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='msgqueued' 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 toStatus, toAddressVersionNumber, toStreamNumber, toHash = decodeAddress( toaddress) fromStatus, fromAddressVersionNumber, fromStreamNumber, fromHash = decodeAddress( fromaddress) - shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( - ackdata, tr.translateText("MainWindow", "Looking up the receiver\'s public key")))) - with shared.printLock: - print 'Found a message in our database that needs to be sent with this pubkey.' - print '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=?''', - toripe) - # 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=?', - toripe) - 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. + 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='msgqueued' 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 - 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: + 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.\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 - + 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. We will use this time both for our message and for the ackdata packed within our message. @@ -750,11 +756,11 @@ class singleWorker(threading.Thread): payload += messageToTransmit if shared.safeConfigGetBoolean(toaddress, 'chan'): with shared.printLock: - print 'Not bothering to generate ackdata because we are sending to a chan.' + print 'Not bothering to include ackdata because we are sending to a chan.' fullAckPayload = '' elif not shared.isBitSetWithinBitfield(behaviorBitfield,31): with shared.printLock: - print 'Not bothering to generate ackdata because the receiver said that they won\'t relay it anyway.' + print 'Not bothering to include ackdata because the receiver said that they won\'t relay it anyway.' fullAckPayload = '' else: fullAckPayload = self.generateFullAckMessage( @@ -795,11 +801,11 @@ class singleWorker(threading.Thread): shared.inventory[inventoryHash] = ( objectType, toStreamNumber, encryptedPayload, int(time.time()),'') shared.inventorySets[toStreamNumber].add(inventoryHash) - if shared.safeConfigGetBoolean(toaddress, 'chan'): + 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 + # not sending to a chan or one of my addresses shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr.translateText("MainWindow", "Message sent. Waiting on 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') @@ -808,7 +814,7 @@ class singleWorker(threading.Thread): # Update the status of the message in the 'sent' table to have a # 'msgsent' status or 'msgsentnoackexpected' status. - if shared.safeConfigGetBoolean(toaddress, 'chan'): + if not shared.config.has_section(toaddress): newStatus = 'msgsentnoackexpected' else: newStatus = 'msgsent' diff --git a/src/class_sqlThread.py b/src/class_sqlThread.py index 829b243d..2767489f 100644 --- a/src/class_sqlThread.py +++ b/src/class_sqlThread.py @@ -46,7 +46,7 @@ class sqlThread(threading.Thread): # because we may need more flexability in the future and it doesn't # take up much more space anyway. self.cur.execute( - '''CREATE TABLE pubkeys (hash blob, transmitdata blob, time int, usedpersonally text, UNIQUE(hash) ON CONFLICT REPLACE)''' ) + '''CREATE TABLE pubkeys (hash blob, addressversion int, transmitdata blob, time int, usedpersonally text, UNIQUE(hash, addressversion) ON CONFLICT REPLACE)''' ) self.cur.execute( '''CREATE TABLE inventory (hash blob, objecttype text, streamnumber int, payload blob, receivedtime integer, tag blob, UNIQUE(hash) ON CONFLICT REPLACE)''' ) self.cur.execute( @@ -57,7 +57,7 @@ class sqlThread(threading.Thread): '''INSERT INTO subscriptions VALUES('Bitmessage new releases/announcements','BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw',1)''') self.cur.execute( '''CREATE TABLE settings (key blob, value blob, UNIQUE(key) ON CONFLICT REPLACE)''' ) - self.cur.execute( '''INSERT INTO settings VALUES('version','4')''') + self.cur.execute( '''INSERT INTO settings VALUES('version','5')''') self.cur.execute( '''INSERT INTO settings VALUES('lastvacuumtime',?)''', ( int(time.time()),)) self.conn.commit() @@ -253,13 +253,29 @@ class sqlThread(threading.Thread): with open(shared.appdata + 'keys.dat', 'wb') as configfile: shared.config.write(configfile) + # Add a new column to the pubkeys table to store the address version. + # We're going to trash all of our pubkeys and let them be redownloaded. + item = '''SELECT value FROM settings WHERE key='version';''' + parameters = '' + self.cur.execute(item, parameters) + currentVersion = int(self.cur.fetchall()[0][0]) + if currentVersion == 4: + self.cur.execute( '''DROP TABLE pubkeys''') + self.cur.execute( + '''CREATE TABLE pubkeys (hash blob, addressversion int, transmitdata blob, time int, usedpersonally text, UNIQUE(hash, addressversion) ON CONFLICT REPLACE)''' ) + self.cur.execute( + '''delete from inventory where objecttype = 'pubkey';''') + item = '''update settings set value=? WHERE key='version';''' + parameters = (5,) + self.cur.execute(item, parameters) + # Are you hoping to add a new option to the keys.dat file of existing # Bitmessage users? Add it right above this line! try: testpayload = '\x00\x00' - t = ('1234', testpayload, '12345678', 'no') - self.cur.execute( '''INSERT INTO pubkeys VALUES(?,?,?,?)''', t) + t = ('1234', 1, testpayload, '12345678', 'no') + self.cur.execute( '''INSERT INTO pubkeys VALUES(?,?,?,?,?)''', t) self.conn.commit() self.cur.execute( '''SELECT transmitdata FROM pubkeys WHERE hash='1234' ''') diff --git a/src/shared.py b/src/shared.py index 1b67fef9..1d436346 100644 --- a/src/shared.py +++ b/src/shared.py @@ -469,8 +469,8 @@ def decryptAndCheckPubkeyPayload(payload, address): print 'Pubkey decryption was UNsuccessful due to RIPE mismatch. This shouldn\'t have happened.' return 'failed' - t = (ripe, signedData, int(time.time()), 'yes') - sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?)''', *t) + t = (ripe, addressVersion, signedData, int(time.time()), 'yes') + sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t) return 'successful' Peer = collections.namedtuple('Peer', ['host', 'port'])