Bitmessage Protocol Version Three

This commit is contained in:
Jonathan Warren 2014-08-27 03:14:32 -04:00
parent 111d60dde8
commit c306062282
20 changed files with 990 additions and 959 deletions

View File

@ -69,22 +69,46 @@ def encodeVarint(integer):
print 'varint cannot be >= 18446744073709551616' print 'varint cannot be >= 18446744073709551616'
raise SystemExit raise SystemExit
class varintDecodeError(Exception):
pass
def decodeVarint(data): def decodeVarint(data):
"""
Decodes an encoded varint to an integer and returns it.
Per protocol v3, the encoded value must be encoded with
the minimum amount of data possible or else it is malformed.
"""
if len(data) == 0: if len(data) == 0:
return (0,0) return (0,0)
firstByte, = unpack('>B',data[0:1]) firstByte, = unpack('>B',data[0:1])
if firstByte < 253: if firstByte < 253:
# encodes 0 to 252
return (firstByte,1) #the 1 is the length of the varint return (firstByte,1) #the 1 is the length of the varint
if firstByte == 253: if firstByte == 253:
a, = unpack('>H',data[1:3]) # encodes 253 to 65535
return (a,3) if len(data) < 3:
raise varintDecodeError('The first byte of this varint as an integer is %s but the total length is only %s. It needs to be at least 3.' % (firstByte, len(data)))
encodedValue, = unpack('>H',data[1:3])
if encodedValue < 253:
raise varintDecodeError('This varint does not encode the value with the lowest possible number of bytes.')
return (encodedValue,3)
if firstByte == 254: if firstByte == 254:
a, = unpack('>I',data[1:5]) # encodes 65536 to 4294967295
return (a,5) if len(data) < 5:
raise varintDecodeError('The first byte of this varint as an integer is %s but the total length is only %s. It needs to be at least 5.' % (firstByte, len(data)))
encodedValue, = unpack('>I',data[1:5])
if encodedValue < 65536:
raise varintDecodeError('This varint does not encode the value with the lowest possible number of bytes.')
return (encodedValue,5)
if firstByte == 255: if firstByte == 255:
a, = unpack('>Q',data[1:9]) # encodes 4294967296 to 18446744073709551615
return (a,9) if len(data) < 9:
raise varintDecodeError('The first byte of this varint as an integer is %s but the total length is only %s. It needs to be at least 9.' % (firstByte, len(data)))
encodedValue, = unpack('>Q',data[1:9])
if encodedValue < 4294967296:
raise varintDecodeError('This varint does not encode the value with the lowest possible number of bytes.')
return (encodedValue,9)
def calculateInventoryHash(data): def calculateInventoryHash(data):
@ -163,7 +187,12 @@ def decodeAddress(address):
#else: #else:
# print 'checksum PASSED' # print 'checksum PASSED'
try:
addressVersionNumber, bytesUsedByVersionNumber = decodeVarint(data[:9]) addressVersionNumber, bytesUsedByVersionNumber = decodeVarint(data[:9])
except varintDecodeError as e:
print e
status = 'varintmalformed'
return status,0,0,""
#print 'addressVersionNumber', addressVersionNumber #print 'addressVersionNumber', addressVersionNumber
#print 'bytesUsedByVersionNumber', bytesUsedByVersionNumber #print 'bytesUsedByVersionNumber', bytesUsedByVersionNumber
@ -176,32 +205,42 @@ def decodeAddress(address):
status = 'versiontoohigh' status = 'versiontoohigh'
return status,0,0,"" return status,0,0,""
try:
streamNumber, bytesUsedByStreamNumber = decodeVarint(data[bytesUsedByVersionNumber:]) streamNumber, bytesUsedByStreamNumber = decodeVarint(data[bytesUsedByVersionNumber:])
except varintDecodeError as e:
print e
status = 'varintmalformed'
return status,0,0,""
#print streamNumber #print streamNumber
status = 'success' status = 'success'
if addressVersionNumber == 1: if addressVersionNumber == 1:
return status,addressVersionNumber,streamNumber,data[-24:-4] return status,addressVersionNumber,streamNumber,data[-24:-4]
elif addressVersionNumber == 2 or addressVersionNumber == 3: elif addressVersionNumber == 2 or addressVersionNumber == 3:
if len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) == 19: embeddedRipeData = data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]
return status,addressVersionNumber,streamNumber,'\x00'+data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4] if len(embeddedRipeData) == 19:
elif len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) == 20: return status,addressVersionNumber,streamNumber,'\x00'+embeddedRipeData
return status,addressVersionNumber,streamNumber,data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4] elif len(embeddedRipeData) == 20:
elif len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) == 18: return status,addressVersionNumber,streamNumber,embeddedRipeData
return status,addressVersionNumber,streamNumber,'\x00\x00'+data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4] elif len(embeddedRipeData) == 18:
elif len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) < 18: return status,addressVersionNumber,streamNumber,'\x00\x00'+embeddedRipeData
elif len(embeddedRipeData) < 18:
return 'ripetooshort',0,0,"" return 'ripetooshort',0,0,""
elif len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) > 20: elif len(embeddedRipeData) > 20:
return 'ripetoolong',0,0,"" return 'ripetoolong',0,0,""
else: else:
return 'otherproblem',0,0,"" return 'otherproblem',0,0,""
elif addressVersionNumber == 4: elif addressVersionNumber == 4:
if len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) > 20: embeddedRipeData = data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]
if embeddedRipeData[0:1] == '\x00':
# In order to enforce address non-malleability, encoded RIPE data must have NULL bytes removed from the front
return 'encodingproblem',0,0,""
elif len(embeddedRipeData) > 20:
return 'ripetoolong',0,0,"" return 'ripetoolong',0,0,""
elif len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) < 4: elif len(embeddedRipeData) < 4:
return 'ripetooshort',0,0,"" return 'ripetooshort',0,0,""
else: else:
x00string = '\x00' * (20 - len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4])) x00string = '\x00' * (20 - len(embeddedRipeData))
return status,addressVersionNumber,streamNumber,x00string+data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4] return status,addressVersionNumber,streamNumber,x00string+embeddedRipeData
def addBMIfNotPresent(address): def addBMIfNotPresent(address):
address = str(address).strip() address = str(address).strip()

View File

@ -17,7 +17,7 @@ import json
import shared import shared
import time import time
from addresses import decodeAddress,addBMIfNotPresent,decodeVarint,calculateInventoryHash from addresses import decodeAddress,addBMIfNotPresent,decodeVarint,calculateInventoryHash,varintDecodeError
import helper_inbox import helper_inbox
import helper_sent import helper_sent
import hashlib import hashlib
@ -139,6 +139,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
raise APIError(9, 'Invalid characters in address: ' + address) raise APIError(9, 'Invalid characters in address: ' + address)
if status == 'versiontoohigh': if status == 'versiontoohigh':
raise APIError(10, 'Address version number too high (or zero) in address: ' + address) raise APIError(10, 'Address version number too high (or zero) in address: ' + address)
if status == 'varintmalformed':
raise APIError(26, 'Malformed varint in address: ' + address)
raise APIError(7, 'Could not decode address: ' + address + ' : ' + status) raise APIError(7, 'Could not decode address: ' + address + ' : ' + status)
if addressVersionNumber < 2 or addressVersionNumber > 4: if addressVersionNumber < 2 or addressVersionNumber > 4:
raise APIError(11, 'The address version number currently must be 2, 3 or 4. Others aren\'t supported. Check the address.') raise APIError(11, 'The address version number currently must be 2, 3 or 4. Others aren\'t supported. Check the address.')
@ -777,9 +779,10 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
encryptedPayload = pack('>Q', nonce) + encryptedPayload encryptedPayload = pack('>Q', nonce) + encryptedPayload
toStreamNumber = decodeVarint(encryptedPayload[16:26])[0] toStreamNumber = decodeVarint(encryptedPayload[16:26])[0]
inventoryHash = calculateInventoryHash(encryptedPayload) inventoryHash = calculateInventoryHash(encryptedPayload)
objectType = 'msg' objectType = 2
TTL = 2.5 * 24 * 60 * 60
shared.inventory[inventoryHash] = ( shared.inventory[inventoryHash] = (
objectType, toStreamNumber, encryptedPayload, int(time.time()),'') objectType, toStreamNumber, encryptedPayload, int(time.time()) + TTL,'')
shared.inventorySets[toStreamNumber].add(inventoryHash) shared.inventorySets[toStreamNumber].add(inventoryHash)
with shared.printLock: with shared.printLock:
print 'Broadcasting inv for msg(API disseminatePreEncryptedMsg command):', inventoryHash.encode('hex') print 'Broadcasting inv for msg(API disseminatePreEncryptedMsg command):', inventoryHash.encode('hex')
@ -814,10 +817,11 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
pubkeyReadPosition += addressVersionLength pubkeyReadPosition += addressVersionLength
pubkeyStreamNumber = decodeVarint(payload[pubkeyReadPosition:pubkeyReadPosition+10])[0] pubkeyStreamNumber = decodeVarint(payload[pubkeyReadPosition:pubkeyReadPosition+10])[0]
inventoryHash = calculateInventoryHash(payload) inventoryHash = calculateInventoryHash(payload)
objectType = 'pubkey' objectType = 1
#todo: support v4 pubkeys #todo: support v4 pubkeys
TTL = 28 * 24 * 60 * 60
shared.inventory[inventoryHash] = ( shared.inventory[inventoryHash] = (
objectType, pubkeyStreamNumber, payload, int(time.time()),'') objectType, pubkeyStreamNumber, payload, int(time.time()) + TTL,'')
shared.inventorySets[pubkeyStreamNumber].add(inventoryHash) shared.inventorySets[pubkeyStreamNumber].add(inventoryHash)
with shared.printLock: with shared.printLock:
print 'broadcasting inv within API command disseminatePubkey with hash:', inventoryHash.encode('hex') print 'broadcasting inv within API command disseminatePubkey with hash:', inventoryHash.encode('hex')
@ -839,7 +843,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
# use it we'll need to fill out a field in our inventory database # use it we'll need to fill out a field in our inventory database
# which is blank by default (first20bytesofencryptedmessage). # which is blank by default (first20bytesofencryptedmessage).
queryreturn = sqlQuery( queryreturn = sqlQuery(
'''SELECT hash, payload FROM inventory WHERE tag = '' and objecttype = 'msg' ; ''') '''SELECT hash, payload FROM inventory WHERE tag = '' and objecttype = 2 ; ''')
with SqlBulkExecute() as sql: with SqlBulkExecute() as sql:
for row in queryreturn: for row in queryreturn:
hash01, payload = row hash01, payload = row
@ -906,6 +910,9 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
return self._handle_request(method, params) return self._handle_request(method, params)
except APIError as e: except APIError as e:
return str(e) return str(e)
except varintDecodeError as e:
logger.error(e)
return "Data contains a malformed varint. Some details: %s" % e
except Exception as e: except Exception as e:
logger.exception(e) logger.exception(e)
return "API Error 0021: Unexpected API Failure - %s" % str(e) return "API Error 0021: Unexpected API Failure - %s" % str(e)

View File

@ -742,6 +742,8 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F
err += "Some data encoded in the address is too short. There might be something wrong with the software of your acquaintance." err += "Some data encoded in the address is too short. There might be something wrong with the software of your acquaintance."
elif status == "ripetoolong": elif status == "ripetoolong":
err += "Some data encoded in the address is too long. There might be something wrong with the software of your acquaintance." err += "Some data encoded in the address is too long. There might be something wrong with the software of your acquaintance."
elif status == "varintmalformed":
err += "Some data encoded in the address is malformed. There might be something wrong with the software of your acquaintance."
else: else:
err += "It is unknown what is wrong with the address." err += "It is unknown what is wrong with the address."
d.scrollbox(unicode(err), exit_label="Continue") d.scrollbox(unicode(err), exit_label="Continue")

View File

@ -42,6 +42,8 @@ from api import MySimpleXMLRPCRequestHandler
from helper_startup import isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections from helper_startup import isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections
import shared import shared
import helper_startup
helper_startup.loadConfig()
from helper_sql import sqlQuery from helper_sql import sqlQuery
import threading import threading
@ -154,9 +156,9 @@ selfInitiatedConnections = {}
if shared.useVeryEasyProofOfWorkForTesting: if shared.useVeryEasyProofOfWorkForTesting:
shared.networkDefaultProofOfWorkNonceTrialsPerByte = int( shared.networkDefaultProofOfWorkNonceTrialsPerByte = int(
shared.networkDefaultProofOfWorkNonceTrialsPerByte / 16) shared.networkDefaultProofOfWorkNonceTrialsPerByte / 100)
shared.networkDefaultPayloadLengthExtraBytes = int( shared.networkDefaultPayloadLengthExtraBytes = int(
shared.networkDefaultPayloadLengthExtraBytes / 7000) shared.networkDefaultPayloadLengthExtraBytes / 100)
class Main: class Main:
def start(self, daemon=False): def start(self, daemon=False):

View File

@ -1842,6 +1842,17 @@ class MyForm(QtGui.QMainWindow):
subject = str(self.ui.lineEditSubject.text().toUtf8()) subject = str(self.ui.lineEditSubject.text().toUtf8())
message = str( message = str(
self.ui.textEditMessage.document().toPlainText().toUtf8()) self.ui.textEditMessage.document().toPlainText().toUtf8())
"""
The whole network message must fit in 2^18 bytes. Let's assume 500
bytes of overhead. If someone wants to get that too an exact
number you are welcome to but I think that it would be a better
use of time to support message continuation so that users can
send messages of any length.
"""
if len(message) > (2 ** 18 - 500):
QMessageBox.about(self, _translate("MainWindow", "Message too long"), _translate(
"MainWindow", "The message that you are trying to send is too long by %1 bytes. (The maximum is 261644 bytes). Please cut it down before sending.").arg(len(message) - (2 ** 18 - 500)))
return
if self.ui.radioButtonSpecific.isChecked(): # To send a message to specific people (rather than broadcast) if self.ui.radioButtonSpecific.isChecked(): # To send a message to specific people (rather than broadcast)
toAddressesList = [s.strip() toAddressesList = [s.strip()
for s in toAddresses.replace(',', ';').split(';')] for s in toAddresses.replace(',', ';').split(';')]
@ -1873,6 +1884,9 @@ class MyForm(QtGui.QMainWindow):
elif status == 'ripetoolong': elif status == 'ripetoolong':
self.statusBar().showMessage(_translate( self.statusBar().showMessage(_translate(
"MainWindow", "Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance.").arg(toAddress)) "MainWindow", "Error: Some data encoded in the address %1 is too long. There might be something wrong with the software of your acquaintance.").arg(toAddress))
elif status == 'varintmalformed':
self.statusBar().showMessage(_translate(
"MainWindow", "Error: Some data encoded in the address %1 is malformed. There might be something wrong with the software of your acquaintance.").arg(toAddress))
else: else:
self.statusBar().showMessage(_translate( self.statusBar().showMessage(_translate(
"MainWindow", "Error: Something is wrong with the address %1.").arg(toAddress)) "MainWindow", "Error: Something is wrong with the address %1.").arg(toAddress))
@ -2211,10 +2225,10 @@ class MyForm(QtGui.QMainWindow):
addressVersion) + encodeVarint(streamNumber) + ripe).digest()).digest() addressVersion) + encodeVarint(streamNumber) + ripe).digest()).digest()
tag = doubleHashOfAddressData[32:] tag = doubleHashOfAddressData[32:]
queryreturn = sqlQuery( queryreturn = sqlQuery(
'''select payload from inventory where objecttype='broadcast' and tag=?''', tag) '''select payload from inventory where objecttype=3 and tag=?''', tag)
for row in queryreturn: for row in queryreturn:
payload, = row payload, = row
objectType = 'broadcast' objectType = 3
with shared.objectProcessorQueueSizeLock: with shared.objectProcessorQueueSizeLock:
shared.objectProcessorQueueSize += len(payload) shared.objectProcessorQueueSize += len(payload)
shared.objectProcessorQueue.put((objectType,payload)) shared.objectProcessorQueue.put((objectType,payload))
@ -3620,6 +3634,9 @@ class AddAddressDialog(QtGui.QDialog):
elif status == 'ripetoolong': elif status == 'ripetoolong':
self.ui.labelAddressCheck.setText(_translate( self.ui.labelAddressCheck.setText(_translate(
"MainWindow", "Some data encoded in the address is too long.")) "MainWindow", "Some data encoded in the address is too long."))
elif status == 'varintmalformed':
self.ui.labelAddressCheck.setText(_translate(
"MainWindow", "Some data encoded in the address is malformed."))
elif status == 'success': elif status == 'success':
self.ui.labelAddressCheck.setText( self.ui.labelAddressCheck.setText(
_translate("MainWindow", "Address is valid.")) _translate("MainWindow", "Address is valid."))
@ -3658,6 +3675,9 @@ class NewSubscriptionDialog(QtGui.QDialog):
elif status == 'ripetoolong': elif status == 'ripetoolong':
self.ui.labelAddressCheck.setText(_translate( self.ui.labelAddressCheck.setText(_translate(
"MainWindow", "Some data encoded in the address is too long.")) "MainWindow", "Some data encoded in the address is too long."))
elif status == 'varintmalformed':
self.ui.labelAddressCheck.setText(_translate(
"MainWindow", "Some data encoded in the address is malformed."))
elif status == 'success': elif status == 'success':
self.ui.labelAddressCheck.setText( self.ui.labelAddressCheck.setText(
_translate("MainWindow", "Address is valid.")) _translate("MainWindow", "Address is valid."))
@ -3670,7 +3690,7 @@ class NewSubscriptionDialog(QtGui.QDialog):
addressVersion) + encodeVarint(streamNumber) + ripe).digest()).digest() addressVersion) + encodeVarint(streamNumber) + ripe).digest()).digest()
tag = doubleHashOfAddressData[32:] tag = doubleHashOfAddressData[32:]
queryreturn = sqlQuery( queryreturn = sqlQuery(
'''select hash from inventory where objecttype='broadcast' and tag=?''', tag) '''select hash from inventory where objecttype=3 and tag=?''', tag)
if len(queryreturn) == 0: if len(queryreturn) == 0:
self.ui.checkBoxDisplayMessagesAlreadyInInventory.setText( self.ui.checkBoxDisplayMessagesAlreadyInInventory.setText(
_translate("MainWindow", "There are no recent broadcasts from this address to display.")) _translate("MainWindow", "There are no recent broadcasts from this address to display."))

View File

@ -1,7 +1,7 @@
from setuptools import setup from setuptools import setup
name = "Bitmessage" name = "Bitmessage"
version = "0.4.2" version = "0.4.4"
mainscript = ["bitmessagemain.py"] mainscript = ["bitmessagemain.py"]
setup( setup(

View File

@ -7,8 +7,9 @@ from struct import unpack, pack
import sys import sys
import string import string
from subprocess import call # used when the API must execute an outside program from subprocess import call # used when the API must execute an outside program
from pyelliptic.openssl import OpenSSL import traceback
from pyelliptic.openssl import OpenSSL
import highlevelcrypto import highlevelcrypto
from addresses import * from addresses import *
import helper_generic import helper_generic
@ -51,18 +52,23 @@ class objectProcessor(threading.Thread):
while True: while True:
objectType, data = shared.objectProcessorQueue.get() objectType, data = shared.objectProcessorQueue.get()
if objectType == 'getpubkey': try:
if objectType == 0: # getpubkey
self.processgetpubkey(data) self.processgetpubkey(data)
elif objectType == 'pubkey': elif objectType == 1: #pubkey
self.processpubkey(data) self.processpubkey(data)
elif objectType == 'msg': elif objectType == 2: #msg
self.processmsg(data) self.processmsg(data)
elif objectType == 'broadcast': elif objectType == 3: #broadcast
self.processbroadcast(data) self.processbroadcast(data)
elif objectType == 'checkShutdownVariable': # 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': # 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.
pass pass
else: else:
logger.critical('Error! Bug! The class_objectProcessor was passed an object type it doesn\'t recognize: %s' % str(objectType)) logger.critical('Error! Bug! The class_objectProcessor was passed an object type it doesn\'t recognize: %s' % str(objectType))
except varintDecodeError as e:
logger.debug("There was a problem with a varint while processing an object. Some details: %s" % e)
except Exception as e:
logger.critical("Critical error within objectProcessorThread: \n%s" % traceback.format_exc())
with shared.objectProcessorQueueSizeLock: with shared.objectProcessorQueueSizeLock:
shared.objectProcessorQueueSize -= len(data) # We maintain objectProcessorQueueSize so that we will slow down requesting objects if too much data accumulates in the queue. shared.objectProcessorQueueSize -= len(data) # We maintain objectProcessorQueueSize so that we will slow down requesting objects if too much data accumulates in the queue.
@ -83,17 +89,7 @@ class objectProcessor(threading.Thread):
break break
def processgetpubkey(self, data): def processgetpubkey(self, data):
readPosition = 8 # bypass the nonce readPosition = 20 # bypass the nonce, time, and object type
embeddedTime, = unpack('>I', data[readPosition:readPosition + 4])
# This section is used for the transition from 32 bit time to 64 bit
# time in the protocol.
if embeddedTime == 0:
embeddedTime, = unpack('>Q', data[readPosition:readPosition + 8])
readPosition += 8
else:
readPosition += 4
requestedAddressVersionNumber, addressVersionLength = decodeVarint( requestedAddressVersionNumber, addressVersionLength = decodeVarint(
data[readPosition:readPosition + 10]) data[readPosition:readPosition + 10])
readPosition += addressVersionLength readPosition += addressVersionLength
@ -147,7 +143,7 @@ class objectProcessor(threading.Thread):
myAddress, 'lastpubkeysendtime')) myAddress, 'lastpubkeysendtime'))
except: except:
lastPubkeySendTime = 0 lastPubkeySendTime = 0
if lastPubkeySendTime > time.time() - shared.lengthOfTimeToHoldOnToAllPubkeys: # If the last time we sent our pubkey was more recent than 28 days ago... if lastPubkeySendTime > time.time() - 2419200: # If the last time we sent our pubkey was more recent than 28 days ago...
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) 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)
return return
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.') 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.')
@ -166,17 +162,8 @@ class objectProcessor(threading.Thread):
shared.numberOfPubkeysProcessed += 1 shared.numberOfPubkeysProcessed += 1
shared.UISignalQueue.put(( shared.UISignalQueue.put((
'updateNumberOfPubkeysProcessed', 'no data')) 'updateNumberOfPubkeysProcessed', 'no data'))
readPosition = 8 # bypass the nonce embeddedTime, = unpack('>Q', data[8:16])
embeddedTime, = unpack('>I', data[readPosition:readPosition + 4]) readPosition = 20 # bypass the nonce, time, and object type
# This section is used for the transition from 32 bit time to 64 bit
# time in the protocol.
if embeddedTime == 0:
embeddedTime, = unpack('>Q', data[readPosition:readPosition + 8])
readPosition += 8
else:
readPosition += 4
addressVersion, varintLength = decodeVarint( addressVersion, varintLength = decodeVarint(
data[readPosition:readPosition + 10]) data[readPosition:readPosition + 10])
readPosition += varintLength readPosition += varintLength
@ -225,15 +212,25 @@ class objectProcessor(threading.Thread):
queryreturn = sqlQuery( queryreturn = sqlQuery(
'''SELECT usedpersonally FROM pubkeys WHERE hash=? AND addressversion=? AND usedpersonally='yes' ''', ripe, addressVersion) '''SELECT usedpersonally FROM pubkeys WHERE hash=? AND addressversion=? AND usedpersonally='yes' ''', ripe, addressVersion)
"""
With the changes in protocol v3, we have to be careful to store pubkey data
in the database the same way we did before to maintain backwards compatibility
with what is in people's databases already. This means that for v2 keys, we
must store the nonce, the time, and then everything else starting with the
address version.
"""
dataToStore = '\x00' * 8 # fake nonce
dataToStore += data[8:16] # the time
dataToStore += data[20:] # everything else
if queryreturn != []: # if this pubkey is already in our database and if we have used it personally: if queryreturn != []: # if this pubkey is already in our database and if we have used it personally:
logger.info('We HAVE used this pubkey personally. Updating time.') logger.info('We HAVE used this pubkey personally. Updating time.')
t = (ripe, addressVersion, data, embeddedTime, 'yes') t = (ripe, addressVersion, dataToStore, int(time.time()), 'yes')
else: else:
logger.info('We have NOT used this pubkey personally. Inserting in database.') logger.info('We have NOT used this pubkey personally. Inserting in database.')
t = (ripe, addressVersion, data, embeddedTime, 'no') t = (ripe, addressVersion, dataToStore, int(time.time()), '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) self.possibleNewPubkey(ripe = ripe)
if addressVersion == 3: if addressVersion == 3:
if len(data) < 170: # sanity check. if len(data) < 170: # sanity check.
@ -242,9 +239,6 @@ class objectProcessor(threading.Thread):
bitfieldBehaviors = data[readPosition:readPosition + 4] bitfieldBehaviors = data[readPosition:readPosition + 4]
readPosition += 4 readPosition += 4
publicSigningKey = '\x04' + data[readPosition:readPosition + 64] publicSigningKey = '\x04' + data[readPosition:readPosition + 64]
# Is it possible for a public key to be invalid such that trying to
# encrypt or sign with it will cause an error? If it is, we should
# probably test these keys here.
readPosition += 64 readPosition += 64
publicEncryptionKey = '\x04' + data[readPosition:readPosition + 64] publicEncryptionKey = '\x04' + data[readPosition:readPosition + 64]
readPosition += 64 readPosition += 64
@ -259,13 +253,31 @@ class objectProcessor(threading.Thread):
data[readPosition:readPosition + 10]) data[readPosition:readPosition + 10])
readPosition += signatureLengthLength readPosition += signatureLengthLength
signature = data[readPosition:readPosition + signatureLength] signature = data[readPosition:readPosition + signatureLength]
try: """
if not highlevelcrypto.verify(data[8:endOfSignedDataPosition], signature, publicSigningKey.encode('hex')): With the changes in protocol v3, to maintain backwards compatibility, signatures will be sent
logger.warning('ECDSA verify failed (within processpubkey)') the 'old' way during an upgrade period and then a 'new' simpler way after that. We will therefore
return check the sig both ways.
logger.info('ECDSA verify passed (within processpubkey)') Old way:
except Exception as err: signedData = timePubkeyWasSigned(4 bytes) + addressVersion through extra_bytes
logger.warning('ECDSA verify failed (within processpubkey) %s' % err) New way:
signedData = all of the payload data, from the time down through the extra_bytes
The timePubkeyWasSigned will be calculated by subtracting 28 days form the embedded expiresTime.
"""
expiresTime, = unpack('>Q', data[8:16])
TTL = 28 * 24 * 60 * 60
signedData = pack('>I', (expiresTime - TTL)) # the time that the pubkey was signed. 4 bytes.
signedData += data[20:endOfSignedDataPosition] # the address version down through the payloadLengthExtraBytes
if highlevelcrypto.verify(signedData, signature, publicSigningKey.encode('hex')):
logger.info('ECDSA verify passed (within processpubkey, old method)')
else:
logger.warning('ECDSA verify failed (within processpubkey, old method)')
# let us try the newer signature method
if highlevelcrypto.verify(data[8:endOfSignedDataPosition], signature, publicSigningKey.encode('hex')):
logger.info('ECDSA verify passed (within processpubkey, new method)')
else:
logger.warning('ECDSA verify failed (within processpubkey, new method)')
return return
sha = hashlib.new('sha512') sha = hashlib.new('sha512')
@ -286,109 +298,45 @@ class objectProcessor(threading.Thread):
) )
) )
"""
With the changes in protocol v3, we have to be careful to store pubkey data
in the database the same way we did before to maintain backwards compatibility
with what is in people's databases already. This means that for v3 keys, we
must store the nonce, the time, and then everything else starting with the
address version.
"""
dataToStore = '\x00' * 8 # fake nonce
dataToStore += data[8:16] # the time
dataToStore += data[20:] # everything else
queryreturn = sqlQuery('''SELECT usedpersonally FROM pubkeys WHERE hash=? AND addressversion=? AND usedpersonally='yes' ''', ripe, addressVersion) 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: if queryreturn != []: # if this pubkey is already in our database and if we have used it personally:
logger.info('We HAVE used this pubkey personally. Updating time.') logger.info('We HAVE used this pubkey personally. Updating time.')
t = (ripe, addressVersion, data, embeddedTime, 'yes') t = (ripe, addressVersion, dataToStore, int(time.time()), 'yes')
else: else:
logger.info('We have NOT used this pubkey personally. Inserting in database.') logger.info('We have NOT used this pubkey personally. Inserting in database.')
t = (ripe, addressVersion, data, embeddedTime, 'no') t = (ripe, addressVersion, dataToStore, int(time.time()), 'no')
# This will also update the embeddedTime.
sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t) sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t)
self.possibleNewPubkey(ripe = ripe) self.possibleNewPubkey(ripe = ripe)
if addressVersion == 4: if addressVersion == 4:
"""
There exist a function: shared.decryptAndCheckPubkeyPayload which does something almost
the same as this section of code. There are differences, however; one being that
decryptAndCheckPubkeyPayload requires that a cryptor object be created each time it is
run which is an expensive operation. This, on the other hand, keeps them saved in
the shared.neededPubkeys dictionary so that if an attacker sends us many
incorrectly-tagged pubkeys, which would force us to try to decrypt them, this code
would run and handle that event quite quickly.
"""
if len(data) < 350: # sanity check. if len(data) < 350: # sanity check.
logger.debug('(within processpubkey) payloadLength less than 350. Sanity check failed.') logger.debug('(within processpubkey) payloadLength less than 350. Sanity check failed.')
return return
signedData = data[8:readPosition] # Some of the signed data is not encrypted so let's keep it for now.
tag = data[readPosition:readPosition + 32] tag = data[readPosition:readPosition + 32]
readPosition += 32
encryptedData = data[readPosition:]
if tag not in shared.neededPubkeys: if tag not in shared.neededPubkeys:
logger.info('We don\'t need this v4 pubkey. We didn\'t ask for it.') logger.info('We don\'t need this v4 pubkey. We didn\'t ask for it.')
return return
# Let us try to decrypt the pubkey # Let us try to decrypt the pubkey
cryptorObject = shared.neededPubkeys[tag] toAddress, cryptorObject = shared.neededPubkeys[tag]
try: if shared.decryptAndCheckPubkeyPayload(data, toAddress) == 'successful':
decryptedData = cryptorObject.decrypt(encryptedData) # At this point we know that we have been waiting on this pubkey.
except:
# Someone must have encrypted some data with a different key
# but tagged it with a tag for which we are watching.
logger.info('Pubkey decryption was unsuccessful.')
return
readPosition = 0
bitfieldBehaviors = decryptedData[readPosition:readPosition + 4]
readPosition += 4
publicSigningKey = '\x04' + decryptedData[readPosition:readPosition + 64]
# Is it possible for a public key to be invalid such that trying to
# encrypt or check a sig with it will cause an error? If it is, we
# should probably test these keys here.
readPosition += 64
publicEncryptionKey = '\x04' + decryptedData[readPosition:readPosition + 64]
readPosition += 64
specifiedNonceTrialsPerByte, specifiedNonceTrialsPerByteLength = decodeVarint(
decryptedData[readPosition:readPosition + 10])
readPosition += specifiedNonceTrialsPerByteLength
specifiedPayloadLengthExtraBytes, specifiedPayloadLengthExtraBytesLength = decodeVarint(
decryptedData[readPosition:readPosition + 10])
readPosition += specifiedPayloadLengthExtraBytesLength
signedData += decryptedData[:readPosition]
signatureLength, signatureLengthLength = decodeVarint(
decryptedData[readPosition:readPosition + 10])
readPosition += signatureLengthLength
signature = decryptedData[readPosition:readPosition + signatureLength]
try:
if not highlevelcrypto.verify(signedData, signature, publicSigningKey.encode('hex')):
logger.info('ECDSA verify failed (within processpubkey)')
return
logger.info('ECDSA verify passed (within processpubkey)')
except Exception as err:
logger.info('ECDSA verify failed (within processpubkey) %s' % err)
return
sha = hashlib.new('sha512')
sha.update(publicSigningKey + publicEncryptionKey)
ripeHasher = hashlib.new('ripemd160')
ripeHasher.update(sha.digest())
ripe = ripeHasher.digest()
# We need to make sure that the tag on the outside of the encryption
# is the one generated from hashing these particular keys.
if tag != hashlib.sha512(hashlib.sha512(encodeVarint(addressVersion) + encodeVarint(streamNumber) + ripe).digest()).digest()[32:]:
logger.info('Someone was trying to act malicious: tag doesn\'t match the keys in this pubkey message. Ignoring it.')
return
logger.info('within recpubkey, addressVersion: %s, streamNumber: %s \n\
ripe %s\n\
publicSigningKey in hex: %s\n\
publicEncryptionKey in hex: %s' % (addressVersion,
streamNumber,
ripe.encode('hex'),
publicSigningKey.encode('hex'),
publicEncryptionKey.encode('hex')
)
)
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.
# This function will command the workerThread to start work on # This function will command the workerThread to start work on
# the messages that require it. # the messages that require it.
self.possibleNewPubkey(address = fromAddress) self.possibleNewPubkey(address=toAddress)
# Display timing data # Display timing data
timeRequiredToProcessPubkey = time.time( timeRequiredToProcessPubkey = time.time(
@ -401,28 +349,28 @@ class objectProcessor(threading.Thread):
shared.numberOfMessagesProcessed += 1 shared.numberOfMessagesProcessed += 1
shared.UISignalQueue.put(( shared.UISignalQueue.put((
'updateNumberOfMessagesProcessed', 'no data')) 'updateNumberOfMessagesProcessed', 'no data'))
readPosition = 8 # bypass the nonce readPosition = 20 # bypass the nonce, time, and object type
embeddedTime, = unpack('>I', data[readPosition:readPosition + 4])
"""
In protocol v2, the next byte(s) was the streamNumber. But starting after
the protocol v3 upgrade period, the next byte(s) will be a msg version
number followed by the streamNumber.
"""
#msgVersionOutsideEncryption, msgVersionOutsideEncryptionLength = decodeVarint(data[readPosition:readPosition + 9])
#readPosition += msgVersionOutsideEncryptionLength
# This section is used for the transition from 32 bit time to 64 bit
# time in the protocol.
if embeddedTime == 0:
embeddedTime, = unpack('>Q', data[readPosition:readPosition + 8])
readPosition += 8
else:
readPosition += 4
streamNumberAsClaimedByMsg, streamNumberAsClaimedByMsgLength = decodeVarint( streamNumberAsClaimedByMsg, streamNumberAsClaimedByMsgLength = decodeVarint(
data[readPosition:readPosition + 9]) data[readPosition:readPosition + 9])
readPosition += streamNumberAsClaimedByMsgLength readPosition += streamNumberAsClaimedByMsgLength
inventoryHash = calculateInventoryHash(data) inventoryHash = calculateInventoryHash(data)
initialDecryptionSuccessful = False initialDecryptionSuccessful = False
# Let's check whether this is a message acknowledgement bound for us. # Let's check whether this is a message acknowledgement bound for us.
if data[readPosition:] in shared.ackdataForWhichImWatching: if data[-32:] in shared.ackdataForWhichImWatching:
logger.info('This msg IS an acknowledgement bound for me.') logger.info('This msg IS an acknowledgement bound for me.')
del shared.ackdataForWhichImWatching[data[readPosition:]] del shared.ackdataForWhichImWatching[data[-32:]]
sqlExecute('UPDATE sent SET status=? WHERE ackdata=?', sqlExecute('UPDATE sent SET status=? WHERE ackdata=?',
'ackreceived', data[readPosition:]) 'ackreceived', data[-32:])
shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (data[readPosition:], tr.translateText("MainWindow",'Acknowledgement of the message received. %1').arg(l10n.formatTimestamp())))) shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (data[-32:], tr.translateText("MainWindow",'Acknowledgement of the message received. %1').arg(l10n.formatTimestamp()))))
return return
else: else:
logger.info('This was NOT an acknowledgement bound for me.') logger.info('This was NOT an acknowledgement bound for me.')
@ -430,17 +378,35 @@ class objectProcessor(threading.Thread):
# This is not an acknowledgement bound for me. See if it is a message # 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. # bound for me by trying to decrypt it with my private keys.
# This can be simplified quite a bit after 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT
for key, cryptorObject in shared.myECCryptorObjects.items(): for key, cryptorObject in shared.myECCryptorObjects.items():
try: try:
decryptedData = cryptorObject.decrypt( decryptedData = cryptorObject.decrypt(data[readPosition:])
data[readPosition:])
toRipe = key # 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 # This is the RIPE hash of my pubkeys. We need this below to compare to the destination_ripe included in the encrypted data.
initialDecryptionSuccessful = True initialDecryptionSuccessful = True
logger.info('EC decryption successful using key associated with ripe hash: %s' % key.encode('hex')) logger.info('EC decryption successful using key associated with ripe hash: %s. msg did NOT specify version.' % key.encode('hex'))
# We didn't bypass a msg version above as it is commented out.
# But the decryption was successful. Which means that there
# wasn't a msg version byte include in this msg.
msgObjectContainedVersion = False
break
except Exception as err:
# What if a client sent us a msg with
# a msg version included? We didn't bypass it above. So
# let's try to decrypt the msg assuming that it is present.
try:
decryptedData = cryptorObject.decrypt(data[readPosition+1:]) # notice that we offset by 1 byte compared to the attempt above.
toRipe = key # This is the RIPE hash of my pubkeys. We need this below to compare to the destination_ripe included in the encrypted data.
initialDecryptionSuccessful = True
logger.info('EC decryption successful using key associated with ripe hash: %s. msg DID specifiy version.' % key.encode('hex'))
# There IS a msg version byte include in this msg.
msgObjectContainedVersion = True
break break
except Exception as err: except Exception as err:
pass pass
# print 'cryptorObject.decrypt Exception:', err
if not initialDecryptionSuccessful: if not initialDecryptionSuccessful:
# This is not a message bound for me. # This is not a message bound for me.
logger.info('Length of time program spent failing to decrypt this message: %s seconds.' % (time.time() - messageProcessingStartTime,)) logger.info('Length of time program spent failing to decrypt this message: %s seconds.' % (time.time() - messageProcessingStartTime,))
@ -450,12 +416,15 @@ class objectProcessor(threading.Thread):
toAddress = shared.myAddressesByHash[ toAddress = shared.myAddressesByHash[
toRipe] # Look up my address based on the RIPE hash. toRipe] # Look up my address based on the RIPE hash.
readPosition = 0 readPosition = 0
messageVersion, messageVersionLength = decodeVarint( if not msgObjectContainedVersion: # by which I mean "if the msg object didn't have the msg version outside of the encryption". This confusingness will be removed after the protocol v3 upgrade period.
messageVersionWithinEncryption, messageVersionWithinEncryptionLength = decodeVarint(
decryptedData[readPosition:readPosition + 10]) decryptedData[readPosition:readPosition + 10])
readPosition += messageVersionLength readPosition += messageVersionWithinEncryptionLength
if messageVersion != 1: if messageVersionWithinEncryption != 1:
logger.info('Cannot understand message versions other than one. Ignoring message.') logger.info('Cannot understand message versions other than one. Ignoring message.')
return return
else:
messageVersionWithinEncryptionLength = 0 # This variable can disappear after the protocol v3 upgrade period is complete.
sendersAddressVersionNumber, sendersAddressVersionNumberLength = decodeVarint( sendersAddressVersionNumber, sendersAddressVersionNumberLength = decodeVarint(
decryptedData[readPosition:readPosition + 10]) decryptedData[readPosition:readPosition + 10])
readPosition += sendersAddressVersionNumberLength readPosition += sendersAddressVersionNumberLength
@ -520,14 +489,17 @@ class objectProcessor(threading.Thread):
readPosition += signatureLengthLength readPosition += signatureLengthLength
signature = decryptedData[ signature = decryptedData[
readPosition:readPosition + signatureLength] readPosition:readPosition + signatureLength]
try: if not msgObjectContainedVersion:
if not highlevelcrypto.verify(decryptedData[:positionOfBottomOfAckData], signature, pubSigningKey.encode('hex')): # protocol v2. This can be removed after the end of the protocol v3 upgrade period.
signedData = decryptedData[:positionOfBottomOfAckData]
else:
# protocol v3
signedData = data[8:20] + encodeVarint(1) + encodeVarint(streamNumberAsClaimedByMsg) + decryptedData[:positionOfBottomOfAckData]
if not highlevelcrypto.verify(signedData, signature, pubSigningKey.encode('hex')):
logger.debug('ECDSA verify failed') logger.debug('ECDSA verify failed')
return return
logger.debug('ECDSA verify passed') logger.debug('ECDSA verify passed')
except Exception as err:
logger.debug('ECDSA verify failed %s' % err)
return
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.' % 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)) (helper_bitcoin.calculateBitcoinAddressFromPubkey(pubSigningKey), helper_bitcoin.calculateTestnetAddressFromPubkey(pubSigningKey))
) )
@ -546,7 +518,7 @@ class objectProcessor(threading.Thread):
'''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', '''INSERT INTO pubkeys VALUES (?,?,?,?,?)''',
ripe.digest(), ripe.digest(),
sendersAddressVersionNumber, sendersAddressVersionNumber,
'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' + '\xFF\xFF\xFF\xFF' + decryptedData[messageVersionLength:endOfThePublicKeyPosition], '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' + '\xFF\xFF\xFF\xFF' + decryptedData[messageVersionWithinEncryptionLength:endOfThePublicKeyPosition],
int(time.time()), int(time.time()),
'yes') 'yes')
# This will check to see whether we happen to be awaiting this # This will check to see whether we happen to be awaiting this
@ -558,7 +530,7 @@ class objectProcessor(threading.Thread):
'''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', '''INSERT INTO pubkeys VALUES (?,?,?,?,?)''',
ripe.digest(), ripe.digest(),
sendersAddressVersionNumber, sendersAddressVersionNumber,
'\x00\x00\x00\x00\x00\x00\x00\x01' + decryptedData[messageVersionLength:endOfThePublicKeyPosition], '\x00\x00\x00\x00\x00\x00\x00\x01' + decryptedData[messageVersionWithinEncryptionLength:endOfThePublicKeyPosition],
int(time.time()), int(time.time()),
'yes') 'yes')
# This will check to see whether we happen to be awaiting this # This will check to see whether we happen to be awaiting this
@ -577,7 +549,7 @@ class objectProcessor(threading.Thread):
requiredPayloadLengthExtraBytes = shared.config.getint( requiredPayloadLengthExtraBytes = shared.config.getint(
toAddress, 'payloadlengthextrabytes') toAddress, 'payloadlengthextrabytes')
if not shared.isProofOfWorkSufficient(data, requiredNonceTrialsPerByte, requiredPayloadLengthExtraBytes): if not shared.isProofOfWorkSufficient(data, requiredNonceTrialsPerByte, requiredPayloadLengthExtraBytes):
print 'Proof of work in msg message insufficient only because it does not meet our higher requirement.' logger.info('Proof of work in msg is insufficient only because it does not meet our higher requirement.')
return return
blockMessage = False # Gets set to True if the user shouldn't see the message according to black or white lists. blockMessage = False # Gets set to True if the user shouldn't see the message according to black or white lists.
if shared.config.get('bitmessagesettings', 'blackwhitelist') == 'black': # If we are using a blacklist if shared.config.get('bitmessagesettings', 'blackwhitelist') == 'black': # If we are using a blacklist
@ -667,14 +639,7 @@ class objectProcessor(threading.Thread):
shared.workerQueue.put(('sendbroadcast', '')) shared.workerQueue.put(('sendbroadcast', ''))
if self.ackDataHasAVaildHeader(ackData): if self.ackDataHasAVaildHeader(ackData):
if ackData[4:16] == addDataPadding('getpubkey'): shared.checkAndShareObjectWithPeers(ackData[24:])
shared.checkAndSharegetpubkeyWithPeers(ackData[24:])
elif ackData[4:16] == addDataPadding('pubkey'):
shared.checkAndSharePubkeyWithPeers(ackData[24:])
elif ackData[4:16] == addDataPadding('msg'):
shared.checkAndShareMsgWithPeers(ackData[24:])
elif ackData[4:16] == addDataPadding('broadcast'):
shared.checkAndShareBroadcastWithPeers(ackData[24:])
# Display timing data # Display timing data
timeRequiredToAttemptToDecryptMessage = time.time( timeRequiredToAttemptToDecryptMessage = time.time(
@ -696,155 +661,26 @@ class objectProcessor(threading.Thread):
shared.UISignalQueue.put(( shared.UISignalQueue.put((
'updateNumberOfBroadcastsProcessed', 'no data')) 'updateNumberOfBroadcastsProcessed', 'no data'))
inventoryHash = calculateInventoryHash(data) inventoryHash = calculateInventoryHash(data)
readPosition = 8 # bypass the nonce readPosition = 20 # bypass the nonce, time, and object type
embeddedTime, = unpack('>I', data[readPosition:readPosition + 4])
# This section is used for the transition from 32 bit time to 64 bit
# time in the protocol.
if embeddedTime == 0:
embeddedTime, = unpack('>Q', data[readPosition:readPosition + 8])
readPosition += 8
else:
readPosition += 4
broadcastVersion, broadcastVersionLength = decodeVarint( broadcastVersion, broadcastVersionLength = decodeVarint(
data[readPosition:readPosition + 9]) data[readPosition:readPosition + 9])
readPosition += broadcastVersionLength readPosition += broadcastVersionLength
if broadcastVersion < 1 or broadcastVersion > 3: if broadcastVersion < 1 or broadcastVersion > 5:
logger.debug('Cannot decode incoming broadcast versions higher than 3. Assuming the sender isn\'t being silly, you should upgrade Bitmessage because this message shall be ignored.') logger.info('Cannot decode incoming broadcast versions higher than 5. Assuming the sender isn\'t being silly, you should upgrade Bitmessage because this message shall be ignored.')
return return
if broadcastVersion == 1: if broadcastVersion == 1:
beginningOfPubkeyPosition = readPosition # used when we add the pubkey to our pubkey table logger.info('Version 1 broadcasts are no longer supported. Not processing it at all.')
sendersAddressVersion, sendersAddressVersionLength = decodeVarint( if broadcastVersion in [2,4]:
data[readPosition:readPosition + 9]) """
if sendersAddressVersion <= 1 or sendersAddressVersion >= 3: v2 or v4 broadcasts are encrypted the same way the msgs were encrypted. To see if we are interested in a
# Cannot decode senderAddressVersion higher than 2. Assuming v2 broadcast, we try to decrypt it. This was replaced with v3 (and later v5) broadcasts which include a tag which
# the sender isn\'t being silly, you should upgrade Bitmessage we check instead, just like we do with v4 pubkeys.
# because this message shall be ignored. v2 and v3 broadcasts should be completely obsolete after the protocol v3 upgrade period and some code can be simplified.
return """
readPosition += sendersAddressVersionLength
if sendersAddressVersion == 2:
sendersStream, sendersStreamLength = decodeVarint(
data[readPosition:readPosition + 9])
readPosition += sendersStreamLength
behaviorBitfield = data[readPosition:readPosition + 4]
readPosition += 4
sendersPubSigningKey = '\x04' + \
data[readPosition:readPosition + 64]
readPosition += 64
sendersPubEncryptionKey = '\x04' + \
data[readPosition:readPosition + 64]
readPosition += 64
endOfPubkeyPosition = readPosition
sendersHash = data[readPosition:readPosition + 20]
if sendersHash not in shared.broadcastSendersForWhichImWatching:
# Display timing data
logger.debug('Time spent deciding that we are not interested in this v1 broadcast: %s' % (time.time() - messageProcessingStartTime,))
return
# At this point, this message claims to be from sendersHash and
# we are interested in it. We still have to hash the public key
# to make sure it is truly the key that matches the hash, and
# also check the signiture.
readPosition += 20
sha = hashlib.new('sha512')
sha.update(sendersPubSigningKey + sendersPubEncryptionKey)
ripe = hashlib.new('ripemd160')
ripe.update(sha.digest())
if ripe.digest() != sendersHash:
# The sender of this message lied.
return
messageEncodingType, messageEncodingTypeLength = decodeVarint(
data[readPosition:readPosition + 9])
if messageEncodingType == 0:
return
readPosition += messageEncodingTypeLength
messageLength, messageLengthLength = decodeVarint(
data[readPosition:readPosition + 9])
readPosition += messageLengthLength
message = data[readPosition:readPosition + messageLength]
readPosition += messageLength
readPositionAtBottomOfMessage = readPosition
signatureLength, signatureLengthLength = decodeVarint(
data[readPosition:readPosition + 9])
readPosition += signatureLengthLength
signature = data[readPosition:readPosition + signatureLength]
try:
if not highlevelcrypto.verify(data[12:readPositionAtBottomOfMessage], signature, sendersPubSigningKey.encode('hex')):
logger.debug('ECDSA verify failed')
return
logger.debug('ECDSA verify passed')
except Exception as err:
logger.debug('ECDSA verify failed %s' % err)
return
# verify passed
fromAddress = encodeAddress(
sendersAddressVersion, sendersStream, ripe.digest())
logger.debug('fromAddress: %s' % fromAddress)
# Let's store the public key in case we want to reply to this person.
# We don't have the correct nonce or time (which would let us
# send out a pubkey message) so we'll just fill it with 1's. We
# won't be able to send this pubkey to others (without doing
# the proof of work ourselves, which this program is programmed
# to not do.)
sqlExecute(
'''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')
# This will 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.
self.possibleNewPubkey(ripe=ripe.digest())
if messageEncodingType == 2:
subject, body = decodeType2Message(message)
logger.info('Broadcast subject (first 100 characters): %s' % repr(subject)[:100])
elif messageEncodingType == 1:
body = message
subject = ''
elif messageEncodingType == 0:
logger.debug('messageEncodingType == 0. Doing nothing with the message.')
else:
body = 'Unknown encoding type.\n\n' + repr(message)
subject = ''
toAddress = '[Broadcast subscribers]'
if messageEncodingType != 0:
# Let us make sure that we haven't already received this message
if helper_inbox.isMessageAlreadyInInbox(toAddress, fromAddress, subject, body, messageEncodingType):
logger.info('This broadcast is already in our inbox. Ignoring it.')
else:
t = (inventoryHash, toAddress, fromAddress, subject, int(
time.time()), body, 'inbox', messageEncodingType, 0)
helper_inbox.insert(t)
shared.UISignalQueue.put(('displayNewInboxMessage', (
inventoryHash, toAddress, fromAddress, subject, body)))
# 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, "newBroadcast"])
# Display timing data
logger.debug('Time spent processing this interesting broadcast: %s' % (time.time() - messageProcessingStartTime,))
if broadcastVersion == 2:
cleartextStreamNumber, cleartextStreamNumberLength = decodeVarint( cleartextStreamNumber, cleartextStreamNumberLength = decodeVarint(
data[readPosition:readPosition + 10]) data[readPosition:readPosition + 10])
readPosition += cleartextStreamNumberLength readPosition += cleartextStreamNumberLength
signedData = data[8:readPosition] # This doesn't end up being used if the broadcastVersion is 2
initialDecryptionSuccessful = False initialDecryptionSuccessful = False
for key, cryptorObject in shared.MyECSubscriptionCryptorObjects.items(): for key, cryptorObject in shared.MyECSubscriptionCryptorObjects.items():
try: try:
@ -862,9 +698,13 @@ class objectProcessor(threading.Thread):
return return
# At this point this is a broadcast I have decrypted and thus am # At this point this is a broadcast I have decrypted and thus am
# interested in. # interested in.
signedBroadcastVersion, readPosition = decodeVarint( readPosition = 0
if broadcastVersion == 2:
signedBroadcastVersion, signedBroadcastVersionLength = decodeVarint(
decryptedData[:10]) decryptedData[:10])
beginningOfPubkeyPosition = readPosition # used when we add the pubkey to our pubkey table readPosition += signedBroadcastVersionLength
beginningOfPubkeyPosition = readPosition # used when we add the pubkey to our pubkey table. This variable can be disposed of after the protocol v3 upgrade period because it will necessarily be at the beginning of the decryptedData; ie it will definitely equal 0
sendersAddressVersion, sendersAddressVersionLength = decodeVarint( sendersAddressVersion, sendersAddressVersionLength = decodeVarint(
decryptedData[readPosition:readPosition + 9]) decryptedData[readPosition:readPosition + 9])
if sendersAddressVersion < 2 or sendersAddressVersion > 3: if sendersAddressVersion < 2 or sendersAddressVersion > 3:
@ -920,14 +760,14 @@ class objectProcessor(threading.Thread):
readPosition += signatureLengthLength readPosition += signatureLengthLength
signature = decryptedData[ signature = decryptedData[
readPosition:readPosition + signatureLength] readPosition:readPosition + signatureLength]
try: if broadcastVersion == 2: # this can be removed after the protocol v3 upgrade period
if not highlevelcrypto.verify(decryptedData[:readPositionAtBottomOfMessage], signature, sendersPubSigningKey.encode('hex')): signedData = decryptedData[:readPositionAtBottomOfMessage]
else:
signedData += decryptedData[:readPositionAtBottomOfMessage]
if not highlevelcrypto.verify(signedData, signature, sendersPubSigningKey.encode('hex')):
logger.debug('ECDSA verify failed') logger.debug('ECDSA verify failed')
return return
logger.debug('ECDSA verify passed') logger.debug('ECDSA verify passed')
except Exception as err:
logger.debug('ECDSA verify failed %s' % err)
return
# verify passed # verify passed
# Let's store the public key in case we want to reply to this # Let's store the public key in case we want to reply to this
@ -988,7 +828,8 @@ class objectProcessor(threading.Thread):
# Display timing data # Display timing data
logger.info('Time spent processing this interesting broadcast: %s' % (time.time() - messageProcessingStartTime,)) logger.info('Time spent processing this interesting broadcast: %s' % (time.time() - messageProcessingStartTime,))
if broadcastVersion == 3: if broadcastVersion in [3,5]:
# broadcast version 3 should be completely obsolete after the end of the protocol v3 upgrade period
cleartextStreamNumber, cleartextStreamNumberLength = decodeVarint( cleartextStreamNumber, cleartextStreamNumberLength = decodeVarint(
data[readPosition:readPosition + 10]) data[readPosition:readPosition + 10])
readPosition += cleartextStreamNumberLength readPosition += cleartextStreamNumberLength
@ -998,21 +839,28 @@ class objectProcessor(threading.Thread):
logger.debug('We\'re not interested in this broadcast.') logger.debug('We\'re not interested in this broadcast.')
return return
# We are interested in this broadcast because of its tag. # We are interested in this broadcast because of its tag.
signedData = data[8:readPosition] # We're going to add some more data which is signed further down.
cryptorObject = shared.MyECSubscriptionCryptorObjects[embeddedTag] cryptorObject = shared.MyECSubscriptionCryptorObjects[embeddedTag]
try: try:
decryptedData = cryptorObject.decrypt(data[readPosition:]) decryptedData = cryptorObject.decrypt(data[readPosition:])
logger.debug('EC decryption successful') logger.debug('EC decryption successful')
except Exception as err: except Exception as err:
logger.debug('Broadcast version 3 decryption Unsuccessful.') logger.debug('Broadcast version %s decryption Unsuccessful.' % broadcastVersion)
return return
signedBroadcastVersion, readPosition = decodeVarint( # broadcast version 3 includes the broadcast version at the beginning
# of the decryptedData. Broadcast version 4 doesn't.
readPosition = 0
if broadcastVersion == 3:
signedBroadcastVersion, signedBroadcastVersionLength = decodeVarint(
decryptedData[:10]) decryptedData[:10])
readPosition += signedBroadcastVersionLength
beginningOfPubkeyPosition = readPosition # used when we add the pubkey to our pubkey table beginningOfPubkeyPosition = readPosition # used when we add the pubkey to our pubkey table
sendersAddressVersion, sendersAddressVersionLength = decodeVarint( sendersAddressVersion, sendersAddressVersionLength = decodeVarint(
decryptedData[readPosition:readPosition + 9]) decryptedData[readPosition:readPosition + 9])
if sendersAddressVersion < 4: if sendersAddressVersion < 4:
logger.info('Cannot decode senderAddressVersion less than 4 for broadcast version number 3. Assuming the sender isn\'t being silly, you should upgrade Bitmessage because this message shall be ignored.') logger.info('Cannot decode senderAddressVersion less than 4 for broadcast version number 3 or 4. Assuming the sender isn\'t being silly, you should upgrade Bitmessage because this message shall be ignored.')
return return
readPosition += sendersAddressVersionLength readPosition += sendersAddressVersionLength
sendersStream, sendersStreamLength = decodeVarint( sendersStream, sendersStreamLength = decodeVarint(
@ -1067,14 +915,14 @@ class objectProcessor(threading.Thread):
readPosition += signatureLengthLength readPosition += signatureLengthLength
signature = decryptedData[ signature = decryptedData[
readPosition:readPosition + signatureLength] readPosition:readPosition + signatureLength]
try: if broadcastVersion == 3: # broadcastVersion 3 should be completely unused after the end of the protocol v3 upgrade period
if not highlevelcrypto.verify(decryptedData[:readPositionAtBottomOfMessage], signature, sendersPubSigningKey.encode('hex')): signedData = decryptedData[:readPositionAtBottomOfMessage]
elif broadcastVersion == 5:
signedData += decryptedData[:readPositionAtBottomOfMessage]
if not highlevelcrypto.verify(signedData, signature, sendersPubSigningKey.encode('hex')):
logger.debug('ECDSA verify failed') logger.debug('ECDSA verify failed')
return return
logger.debug('ECDSA verify passed') logger.debug('ECDSA verify passed')
except Exception as err:
logger.debug('ECDSA verify failed %s' % err)
return
# verify passed # verify passed
fromAddress = encodeAddress( fromAddress = encodeAddress(
@ -1168,23 +1016,21 @@ class objectProcessor(threading.Thread):
logger.info('The length of ackData is unreasonably short. Not sending ackData.') logger.info('The length of ackData is unreasonably short. Not sending ackData.')
return False return False
magic,command,payload_length,checksum = shared.Header.unpack(ackData[:shared.Header.size]) magic,command,payloadLength,checksum = shared.Header.unpack(ackData[:shared.Header.size])
if magic != 0xE9BEB4D9: if magic != 0xE9BEB4D9:
logger.info('Ackdata magic bytes were wrong. Not sending ackData.') logger.info('Ackdata magic bytes were wrong. Not sending ackData.')
return False return False
payload = ackData[shared.Header.size:] payload = ackData[shared.Header.size:]
if len(payload) != payload_length: if len(payload) != payloadLength:
logger.info('ackData payload length doesn\'t match the payload length specified in the header. Not sending ackdata.') logger.info('ackData payload length doesn\'t match the payload length specified in the header. Not sending ackdata.')
return False return False
if payload_length > 180000000: # If the size of the message is greater than 180MB, ignore it. if payloadLength > 2 ** 18: # 256 KiB
return False return False
if checksum != hashlib.sha512(payload).digest()[0:4]: # test the checksum in the message. if checksum != hashlib.sha512(payload).digest()[0:4]: # test the checksum in the message.
logger.info('ackdata checksum wrong. Not sending ackdata.') logger.info('ackdata checksum wrong. Not sending ackdata.')
return False return False
if (command != addDataPadding('getpubkey') and command = command.rstrip('\x00')
command != addDataPadding('pubkey') and if command != 'object':
command != addDataPadding('msg') and
command != addDataPadding('broadcast')):
return False return False
return True return True

View File

@ -8,6 +8,7 @@ import socket
import random import random
from struct import unpack, pack from struct import unpack, pack
import sys import sys
import traceback
#import string #import string
#from subprocess import call # used when the API must execute an outside program #from subprocess import call # used when the API must execute an outside program
#from pyelliptic.openssl import OpenSSL #from pyelliptic.openssl import OpenSSL
@ -21,7 +22,6 @@ from helper_generic import addDataPadding, isHostInPrivateIPRange
from helper_sql import * from helper_sql import *
#import tr #import tr
from debug import logger from debug import logger
#from bitmessagemain import shared.lengthOfTimeToLeaveObjectsInInventory, shared.lengthOfTimeToHoldOnToAllPubkeys, shared.maximumAgeOfAnObjectThatIAmWillingToAccept, shared.maximumAgeOfObjectsThatIAdvertiseToOthers, shared.maximumAgeOfNodesThatIAdvertiseToOthers, shared.numberOfObjectsThatWeHaveYetToGetPerPeer, shared.neededPubkeys
# This thread is created either by the synSenderThread(for outgoing # This thread is created either by the synSenderThread(for outgoing
# connections) or the singleListenerThread(for incoming connections). # connections) or the singleListenerThread(for incoming connections).
@ -117,7 +117,7 @@ class receiveDataThread(threading.Thread):
if magic != 0xE9BEB4D9: if magic != 0xE9BEB4D9:
self.data = "" self.data = ""
return return
if payloadLength > 20000000: if payloadLength > 2 ** 18: # 256 KiB
logger.info('The incoming message, which we have not yet download, is too large. Ignoring it. (unfortunately there is no way to tell the other node to stop sending it except to disconnect.) Message size: %s' % payloadLength) logger.info('The incoming message, which we have not yet download, is too large. Ignoring it. (unfortunately there is no way to tell the other node to stop sending it except to disconnect.) Message size: %s' % payloadLength)
self.data = self.data[payloadLength + shared.Header.size:] self.data = self.data[payloadLength + shared.Header.size:]
del magic,command,payloadLength,checksum # we don't need these anymore and better to clean them now before the recursive call rather than after del magic,command,payloadLength,checksum # we don't need these anymore and better to clean them now before the recursive call rather than after
@ -146,6 +146,7 @@ class receiveDataThread(threading.Thread):
with shared.printLock: with shared.printLock:
print 'remoteCommand', repr(command), ' from', self.peer print 'remoteCommand', repr(command), ' from', self.peer
try:
#TODO: Use a dispatcher here #TODO: Use a dispatcher here
if not self.connectionIsOrWasFullyEstablished: if not self.connectionIsOrWasFullyEstablished:
if command == 'version': if command == 'version':
@ -155,24 +156,20 @@ class receiveDataThread(threading.Thread):
else: else:
if command == 'addr': if command == 'addr':
self.recaddr(payload) self.recaddr(payload)
elif command == 'getpubkey':
shared.checkAndSharegetpubkeyWithPeers(payload)
elif command == 'pubkey':
self.recpubkey(payload)
elif command == 'inv': elif command == 'inv':
self.recinv(payload) self.recinv(payload)
elif command == 'getdata': elif command == 'getdata':
self.recgetdata(payload) self.recgetdata(payload)
elif command == 'msg': elif command == 'object':
self.recmsg(payload) self.recobject(payload)
elif command == 'broadcast':
self.recbroadcast(payload)
elif command == 'ping': elif command == 'ping':
self.sendpong(payload) self.sendpong(payload)
#elif command == 'pong': #elif command == 'pong':
# pass # pass
#elif command == 'alert': except varintDecodeError as e:
# pass logger.debug("There was a problem with a varint while processing a message from the wire. Some details: %s" % e)
except Exception as e:
logger.critical("Critical error in a receiveDataThread: \n%s" % traceback.format_exc())
del payload del payload
self.data = self.data[payloadLength + shared.Header.size:] # take this message out and then process the next message self.data = self.data[payloadLength + shared.Header.size:] # take this message out and then process the next message
@ -273,12 +270,10 @@ class receiveDataThread(threading.Thread):
self.sendBigInv() self.sendBigInv()
def sendBigInv(self): def sendBigInv(self):
# Select all hashes which are younger than two days old and in this # Select all hashes for objects in this stream.
# stream.
queryreturn = sqlQuery( queryreturn = sqlQuery(
'''SELECT hash FROM inventory WHERE ((receivedtime>? and objecttype<>'pubkey') or (receivedtime>? and objecttype='pubkey')) and streamnumber=?''', '''SELECT hash FROM inventory WHERE expirestime>? and streamnumber=?''',
int(time.time()) - shared.maximumAgeOfObjectsThatIAdvertiseToOthers, int(time.time()),
int(time.time()) - shared.lengthOfTimeToHoldOnToAllPubkeys,
self.streamNumber) self.streamNumber)
bigInvList = {} bigInvList = {}
for row in queryreturn: for row in queryreturn:
@ -290,8 +285,8 @@ class receiveDataThread(threading.Thread):
with shared.inventoryLock: with shared.inventoryLock:
for hash, storedValue in shared.inventory.items(): for hash, storedValue in shared.inventory.items():
if hash not in self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware: if hash not in self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware:
objectType, streamNumber, payload, receivedTime, tag = storedValue objectType, streamNumber, payload, expiresTime, tag = storedValue
if streamNumber == self.streamNumber and receivedTime > int(time.time()) - shared.maximumAgeOfObjectsThatIAdvertiseToOthers: if streamNumber == self.streamNumber and expiresTime > int(time.time()):
bigInvList[hash] = 0 bigInvList[hash] = 0
numberOfObjectsInInvMessage = 0 numberOfObjectsInInvMessage = 0
payload = '' payload = ''
@ -327,78 +322,27 @@ class receiveDataThread(threading.Thread):
print 'Timing attack mitigation: Sleeping for', sleepTime, 'seconds.' print 'Timing attack mitigation: Sleeping for', sleepTime, 'seconds.'
time.sleep(sleepTime) time.sleep(sleepTime)
# We have received a broadcast message
def recbroadcast(self, data):
self.messageProcessingStartTime = time.time()
shared.checkAndShareBroadcastWithPeers(data) def recobject(self, data):
self.messageProcessingStartTime = time.time()
lengthOfTimeWeShouldUseToProcessThisMessage = shared.checkAndShareObjectWithPeers(data)
""" """
Let us now set lengthOfTimeWeShouldUseToProcessThisMessage. Sleeping Sleeping will help guarantee that we can process messages faster than a
will help guarantee that we can process messages faster than a remote remote node can send them. If we fall behind, the attacker could observe
node can send them. If we fall behind, the attacker could observe that that we are are slowing down the rate at which we request objects from the
we are are slowing down the rate at which we request objects from the
network which would indicate that we own a particular address (whichever network which would indicate that we own a particular address (whichever
one to which they are sending all of their attack messages). Note one to which they are sending all of their attack messages). Note
that if an attacker connects to a target with many connections, this that if an attacker connects to a target with many connections, this
mitigation mechanism might not be sufficient. mitigation mechanism might not be sufficient.
""" """
if len(data) > 100000000: # Size is greater than 100 megabytes sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - (time.time() - self.messageProcessingStartTime)
lengthOfTimeWeShouldUseToProcessThisMessage = 100 # seconds.
elif len(data) > 10000000: # Between 100 and 10 megabytes
lengthOfTimeWeShouldUseToProcessThisMessage = 20 # seconds.
elif len(data) > 1000000: # Between 10 and 1 megabyte
lengthOfTimeWeShouldUseToProcessThisMessage = 3 # seconds.
else: # Less than 1 megabyte
lengthOfTimeWeShouldUseToProcessThisMessage = .6 # seconds.
sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - \
(time.time() - self.messageProcessingStartTime)
self._sleepForTimingAttackMitigation(sleepTime) self._sleepForTimingAttackMitigation(sleepTime)
# We have received a msg message.
def recmsg(self, data):
self.messageProcessingStartTime = time.time()
shared.checkAndShareMsgWithPeers(data)
"""
Let us now set lengthOfTimeWeShouldUseToProcessThisMessage. Sleeping
will help guarantee that we can process messages faster than a remote
node can send them. If we fall behind, the attacker could observe that
we are are slowing down the rate at which we request objects from the
network which would indicate that we own a particular address (whichever
one to which they are sending all of their attack messages). Note
that if an attacker connects to a target with many connections, this
mitigation mechanism might not be sufficient.
"""
if len(data) > 100000000: # Size is greater than 100 megabytes
lengthOfTimeWeShouldUseToProcessThisMessage = 100 # seconds. Actual length of time it took my computer to decrypt and verify the signature of a 100 MB message: 3.7 seconds.
elif len(data) > 10000000: # Between 100 and 10 megabytes
lengthOfTimeWeShouldUseToProcessThisMessage = 20 # seconds. Actual length of time it took my computer to decrypt and verify the signature of a 10 MB message: 0.53 seconds. Actual length of time it takes in practice when processing a real message: 1.44 seconds.
elif len(data) > 1000000: # Between 10 and 1 megabyte
lengthOfTimeWeShouldUseToProcessThisMessage = 3 # seconds. Actual length of time it took my computer to decrypt and verify the signature of a 1 MB message: 0.18 seconds. Actual length of time it takes in practice when processing a real message: 0.30 seconds.
else: # Less than 1 megabyte
lengthOfTimeWeShouldUseToProcessThisMessage = .6 # seconds. Actual length of time it took my computer to decrypt and verify the signature of a 100 KB message: 0.15 seconds. Actual length of time it takes in practice when processing a real message: 0.25 seconds.
sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - \
(time.time() - self.messageProcessingStartTime)
self._sleepForTimingAttackMitigation(sleepTime)
# We have received a pubkey
def recpubkey(self, data):
self.pubkeyProcessingStartTime = time.time()
shared.checkAndSharePubkeyWithPeers(data)
lengthOfTimeWeShouldUseToProcessThisMessage = .1
sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - \
(time.time() - self.pubkeyProcessingStartTime)
self._sleepForTimingAttackMitigation(sleepTime)
# We have received an inv message # We have received an inv message
def recinv(self, data): def recinv(self, data):
totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers = 0 # this counts duplicates seperately because they take up memory totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers = 0 # this counts duplicates separately because they take up memory
if len(shared.numberOfObjectsThatWeHaveYetToGetPerPeer) > 0: if len(shared.numberOfObjectsThatWeHaveYetToGetPerPeer) > 0:
for key, value in shared.numberOfObjectsThatWeHaveYetToGetPerPeer.items(): for key, value in shared.numberOfObjectsThatWeHaveYetToGetPerPeer.items():
totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers += value totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers += value
@ -474,34 +418,27 @@ class receiveDataThread(threading.Thread):
shared.numberOfInventoryLookupsPerformed += 1 shared.numberOfInventoryLookupsPerformed += 1
shared.inventoryLock.acquire() shared.inventoryLock.acquire()
if hash in shared.inventory: if hash in shared.inventory:
objectType, streamNumber, payload, receivedTime, tag = shared.inventory[ objectType, streamNumber, payload, expiresTime, tag = shared.inventory[hash]
hash]
shared.inventoryLock.release() shared.inventoryLock.release()
self.sendData(objectType, payload) self.sendObject(payload)
else: else:
shared.inventoryLock.release() shared.inventoryLock.release()
queryreturn = sqlQuery( queryreturn = sqlQuery(
'''select objecttype, payload from inventory where hash=?''', '''select payload from inventory where hash=? and expirestime>=?''',
hash) hash,
int(time.time()))
if queryreturn != []: if queryreturn != []:
for row in queryreturn: for row in queryreturn:
objectType, payload = row payload, = row
self.sendData(objectType, payload) self.sendObject(payload)
else: else:
print 'Someone asked for an object with a getdata which is not in either our memory inventory or our SQL inventory. That shouldn\'t have happened.' logger.warning('%s asked for an object with a getdata which is not in either our memory inventory or our SQL inventory. We probably cleaned it out after advertising it but before they got around to asking for it.' % self.peer)
# Our peer has requested (in a getdata message) that we send an object. # Our peer has requested (in a getdata message) that we send an object.
def sendData(self, objectType, payload): def sendObject(self, payload):
if (objectType != 'pubkey' and
objectType != 'getpubkey' and
objectType != 'msg' and
objectType != 'broadcast'):
sys.stderr.write(
'Error: sendData has been asked to send a strange objectType: %s\n' % str(objectType))
return
with shared.printLock: with shared.printLock:
print 'sending', objectType print 'sending an object.'
self.sendDataThreadQueue.put((0, 'sendRawData', shared.CreatePacket(objectType, payload))) self.sendDataThreadQueue.put((0, 'sendRawData', shared.CreatePacket('object',payload)))
def _checkIPv4Address(self, host, hostFromAddrMessage): def _checkIPv4Address(self, host, hostFromAddrMessage):
@ -730,10 +667,10 @@ class receiveDataThread(threading.Thread):
""" """
return return
self.remoteProtocolVersion, = unpack('>L', data[:4]) self.remoteProtocolVersion, = unpack('>L', data[:4])
if self.remoteProtocolVersion <= 1: if self.remoteProtocolVersion < 3:
self.sendDataThreadQueue.put((0, 'shutdown','no data')) self.sendDataThreadQueue.put((0, 'shutdown','no data'))
with shared.printLock: with shared.printLock:
print 'Closing connection to old protocol version 1 node: ', self.peer print 'Closing connection to old protocol version', self.remoteProtocolVersion, 'node: ', self.peer
return return
# print 'remoteProtocolVersion', self.remoteProtocolVersion # print 'remoteProtocolVersion', self.remoteProtocolVersion
self.myExternalIP = socket.inet_ntoa(data[40:44]) self.myExternalIP = socket.inet_ntoa(data[40:44])
@ -760,7 +697,7 @@ class receiveDataThread(threading.Thread):
return return
shared.connectedHostsList[ shared.connectedHostsList[
self.peer.host] = 1 # We use this data structure to not only keep track of what hosts we are connected to so that we don't try to connect to them again, but also to list the connections count on the Network Status tab. self.peer.host] = 1 # We use this data structure to not only keep track of what hosts we are connected to so that we don't try to connect to them again, but also to list the connections count on the Network Status tab.
# If this was an incoming connection, then the sendData thread # If this was an incoming connection, then the sendDataThread
# doesn't know the stream. We have to set it. # doesn't know the stream. We have to set it.
if not self.initiatedConnection: if not self.initiatedConnection:
self.sendDataThreadQueue.put((0, 'setStreamNumber', self.streamNumber)) self.sendDataThreadQueue.put((0, 'setStreamNumber', self.streamNumber))

View File

@ -9,20 +9,23 @@ import tr#anslate
from helper_sql import * from helper_sql import *
from debug import logger from debug import logger
'''The singleCleaner class is a timer-driven thread that cleans data structures to free memory, resends messages when a remote node doesn't respond, and sends pong messages to keep connections alive if the network isn't busy. """
The singleCleaner class is a timer-driven thread that cleans data structures
to free memory, resends messages when a remote node doesn't respond, and
sends pong messages to keep connections alive if the network isn't busy.
It cleans these data structures in memory: It cleans these data structures in memory:
inventory (moves data to the on-disk sql database) inventory (moves data to the on-disk sql database)
inventorySets (clears then reloads data out of sql database) inventorySets (clears then reloads data out of sql database)
It cleans these tables on the disk: It cleans these tables on the disk:
inventory (clears data more than 2 days and 12 hours old) inventory (clears expired objects)
pubkeys (clears pubkeys older than 4 weeks old which we have not used personally) pubkeys (clears pubkeys older than 4 weeks old which we have not used personally)
It resends messages when there has been no response: It resends messages when there has been no response:
resends getpubkey messages in 5 days (then 10 days, then 20 days, etc...) resends getpubkey messages in 5 days (then 10 days, then 20 days, etc...)
resends msg messages in 5 days (then 10 days, then 20 days, etc...) resends msg messages in 5 days (then 10 days, then 20 days, etc...)
''' """
class singleCleaner(threading.Thread): class singleCleaner(threading.Thread):
@ -41,22 +44,21 @@ class singleCleaner(threading.Thread):
while True: while True:
shared.UISignalQueue.put(( shared.UISignalQueue.put((
'updateStatusBar', 'Doing housekeeping (Flushing inventory in memory to disk...)')) 'updateStatusBar', 'Doing housekeeping (Flushing inventory in memory to disk...)'))
with shared.inventoryLock: # If you use both the inventoryLock and the sqlLock, always use the inventoryLock OUTSIDE of the sqlLock. with shared.inventoryLock: # If you use both the inventoryLock and the sqlLock, always use the inventoryLock OUTSIDE of the sqlLock.
with SqlBulkExecute() as sql: with SqlBulkExecute() as sql:
for hash, storedValue in shared.inventory.items(): for hash, storedValue in shared.inventory.items():
objectType, streamNumber, payload, receivedTime, tag = storedValue objectType, streamNumber, payload, expiresTime, tag = storedValue
if int(time.time()) - 3600 > receivedTime:
sql.execute( sql.execute(
'''INSERT INTO inventory VALUES (?,?,?,?,?,?)''', '''INSERT INTO inventory VALUES (?,?,?,?,?,?)''',
hash, hash,
objectType, objectType,
streamNumber, streamNumber,
payload, payload,
receivedTime, expiresTime,
tag) tag)
del shared.inventory[hash] del shared.inventory[hash]
shared.UISignalQueue.put(('updateStatusBar', '')) shared.UISignalQueue.put(('updateStatusBar', ''))
shared.broadcastToSendDataQueues(( shared.broadcastToSendDataQueues((
0, 'pong', 'no data')) # commands the sendData threads to send out a pong message if they haven't sent anything else in the last five minutes. The socket timeout-time is 10 minutes. 0, 'pong', 'no data')) # commands the sendData threads to send out a pong message if they haven't sent anything else in the last five minutes. The socket timeout-time is 10 minutes.
# If we are running as a daemon then we are going to fill up the UI # If we are running as a daemon then we are going to fill up the UI
@ -66,15 +68,9 @@ class singleCleaner(threading.Thread):
shared.UISignalQueue.queue.clear() shared.UISignalQueue.queue.clear()
if timeWeLastClearedInventoryAndPubkeysTables < int(time.time()) - 7380: if timeWeLastClearedInventoryAndPubkeysTables < int(time.time()) - 7380:
timeWeLastClearedInventoryAndPubkeysTables = int(time.time()) timeWeLastClearedInventoryAndPubkeysTables = int(time.time())
# inventory (moves data from the inventory data structure to
# the on-disk sql database)
# inventory (clears pubkeys after 28 days and everything else
# after 2 days and 12 hours)
sqlExecute( sqlExecute(
'''DELETE FROM inventory WHERE (receivedtime<? AND objecttype<>'pubkey') OR (receivedtime<? AND objecttype='pubkey') ''', '''DELETE FROM inventory WHERE expirestime<? ''',
int(time.time()) - shared.lengthOfTimeToLeaveObjectsInInventory, int(time.time()) - (60 * 60 * 3))
int(time.time()) - shared.lengthOfTimeToHoldOnToAllPubkeys)
# pubkeys # pubkeys
sqlExecute( sqlExecute(
'''DELETE FROM pubkeys WHERE time<? AND usedpersonally='no' ''', '''DELETE FROM pubkeys WHERE time<? AND usedpersonally='no' ''',
@ -89,7 +85,6 @@ class singleCleaner(threading.Thread):
sys.stderr.write( sys.stderr.write(
'Something went wrong in the singleCleaner thread: a query did not return the requested fields. ' + repr(row)) 'Something went wrong in the singleCleaner thread: a query did not return the requested fields. ' + repr(row))
time.sleep(3) time.sleep(3)
break break
toaddress, toripe, fromaddress, subject, message, ackdata, lastactiontime, status, pubkeyretrynumber, msgretrynumber = row toaddress, toripe, fromaddress, subject, message, ackdata, lastactiontime, status, pubkeyretrynumber, msgretrynumber = row
if status == 'awaitingpubkey': if status == 'awaitingpubkey':
@ -108,8 +103,9 @@ class singleCleaner(threading.Thread):
shared.inventorySets[streamNumber].add(row[0]) shared.inventorySets[streamNumber].add(row[0])
with shared.inventoryLock: with shared.inventoryLock:
for hash, storedValue in shared.inventory.items(): for hash, storedValue in shared.inventory.items():
objectType, streamNumber, payload, receivedTime, tag = storedValue objectType, streamNumber, payload, expiresTime, tag = storedValue
if streamNumber in shared.inventorySets: if not streamNumber in shared.inventorySets:
shared.inventorySets[streamNumber] = set()
shared.inventorySets[streamNumber].add(hash) shared.inventorySets[streamNumber].add(hash)
# Let us write out the knowNodes to disk if there is anything new to write out. # Let us write out the knowNodes to disk if there is anything new to write out.

View File

@ -88,7 +88,7 @@ class singleListener(threading.Thread):
time.sleep(10) time.sleep(10)
while True: while True:
a, sockaddr = sock.accept() socketObject, sockaddr = sock.accept()
(HOST, PORT) = sockaddr[0:2] (HOST, PORT) = sockaddr[0:2]
# If the address is an IPv4-mapped IPv6 address then # If the address is an IPv4-mapped IPv6 address then
@ -103,7 +103,7 @@ class singleListener(threading.Thread):
# share the same external IP. This is here to prevent # share the same external IP. This is here to prevent
# connection flooding. # connection flooding.
if HOST in shared.connectedHostsList: if HOST in shared.connectedHostsList:
a.close() socketObject.close()
with shared.printLock: with shared.printLock:
print 'We are already connected to', HOST + '. Ignoring connection.' print 'We are already connected to', HOST + '. Ignoring connection.'
else: else:
@ -111,17 +111,17 @@ class singleListener(threading.Thread):
someObjectsOfWhichThisRemoteNodeIsAlreadyAware = {} # This is not necessairly a complete list; we clear it from time to time to save memory. someObjectsOfWhichThisRemoteNodeIsAlreadyAware = {} # This is not necessairly a complete list; we clear it from time to time to save memory.
sendDataThreadQueue = Queue.Queue() # Used to submit information to the send data thread for this connection. sendDataThreadQueue = Queue.Queue() # Used to submit information to the send data thread for this connection.
a.settimeout(20) socketObject.settimeout(20)
sd = sendDataThread(sendDataThreadQueue) sd = sendDataThread(sendDataThreadQueue)
sd.setup( sd.setup(
a, HOST, PORT, -1, someObjectsOfWhichThisRemoteNodeIsAlreadyAware) socketObject, HOST, PORT, -1, someObjectsOfWhichThisRemoteNodeIsAlreadyAware)
sd.start() sd.start()
rd = receiveDataThread() rd = receiveDataThread()
rd.daemon = True # close the main program even if there are threads left rd.daemon = True # close the main program even if there are threads left
rd.setup( rd.setup(
a, HOST, PORT, -1, someObjectsOfWhichThisRemoteNodeIsAlreadyAware, self.selfInitiatedConnections, sendDataThreadQueue) socketObject, HOST, PORT, -1, someObjectsOfWhichThisRemoteNodeIsAlreadyAware, self.selfInitiatedConnections, sendDataThreadQueue)
rd.start() rd.start()
with shared.printLock: with shared.printLock:

View File

@ -27,21 +27,20 @@ class singleWorker(threading.Thread):
def run(self): def run(self):
queryreturn = sqlQuery( queryreturn = sqlQuery(
'''SELECT toripe, toaddress FROM sent WHERE ((status='awaitingpubkey' OR status='doingpubkeypow') AND folder='sent')''') '''SELECT DISTINCT toaddress FROM sent WHERE (status='awaitingpubkey' AND folder='sent')''')
for row in queryreturn: for row in queryreturn:
toripe, toaddress = row toAddress, = row
toStatus, toAddressVersionNumber, toStreamNumber, toRipe = decodeAddress(toaddress) toStatus, toAddressVersionNumber, toStreamNumber, toRipe = decodeAddress(toAddress)
if toAddressVersionNumber <= 3 : if toAddressVersionNumber <= 3 :
shared.neededPubkeys[toripe] = 0 shared.neededPubkeys[toRipe] = 0
elif toAddressVersionNumber >= 4: elif toAddressVersionNumber >= 4:
doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint(
toAddressVersionNumber) + encodeVarint(toStreamNumber) + toRipe).digest()).digest() toAddressVersionNumber) + encodeVarint(toStreamNumber) + toRipe).digest()).digest()
privEncryptionKey = doubleHashOfAddressData[:32] # Note that this is the first half of the sha512 hash. privEncryptionKey = doubleHashOfAddressData[:32] # Note that this is the first half of the sha512 hash.
tag = doubleHashOfAddressData[32:] 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. shared.neededPubkeys[tag] = (toAddress, 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 # Initialize the shared.ackdataForWhichImWatching data structure
# from the sql database.
queryreturn = sqlQuery( queryreturn = sqlQuery(
'''SELECT ackdata FROM sent where (status='msgsent' OR status='doingmsgpow')''') '''SELECT ackdata FROM sent where (status='msgsent' OR status='doingmsgpow')''')
for row in queryreturn: for row in queryreturn:
@ -96,9 +95,11 @@ class singleWorker(threading.Thread):
myAddress = shared.myAddressesByHash[hash] myAddress = shared.myAddressesByHash[hash]
status, addressVersionNumber, streamNumber, hash = decodeAddress( status, addressVersionNumber, streamNumber, hash = decodeAddress(
myAddress) myAddress)
embeddedTime = int(time.time() + random.randrange(
-300, 300)) # the current time plus or minus five minutes TTL = 28 * 24 * 60 * 60 # 28 days
payload = pack('>I', (embeddedTime)) embeddedTime = int(time.time() + random.randrange(-300, 300) + TTL) # 28 days from now plus or minus five minutes
payload = pack('>Q', (embeddedTime))
payload += '\x00\x00\x00\x01' # object type: pubkey
payload += encodeVarint(addressVersionNumber) # Address version number payload += encodeVarint(addressVersionNumber) # Address version number
payload += encodeVarint(streamNumber) payload += encodeVarint(streamNumber)
payload += '\x00\x00\x00\x01' # bitfield of features supported by me (see the wiki). payload += '\x00\x00\x00\x01' # bitfield of features supported by me (see the wiki).
@ -112,7 +113,6 @@ class singleWorker(threading.Thread):
with shared.printLock: with shared.printLock:
sys.stderr.write( sys.stderr.write(
'Error within doPOWForMyV2Pubkey. Could not read the keys from the keys.dat file for a requested address. %s\n' % err) 'Error within doPOWForMyV2Pubkey. Could not read the keys from the keys.dat file for a requested address. %s\n' % err)
return return
privSigningKeyHex = shared.decodeWalletImportFormat( privSigningKeyHex = shared.decodeWalletImportFormat(
@ -128,8 +128,8 @@ class singleWorker(threading.Thread):
payload += pubEncryptionKey[1:] payload += pubEncryptionKey[1:]
# Do the POW for this pubkey message # Do the POW for this pubkey message
target = 2 ** 64 / ((len(payload) + shared.networkDefaultPayloadLengthExtraBytes + TTL = 2.5 * 24 * 60 * 60 # 2.5 days for now; user specifyable later.
8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) target = 2 ** 64 / (shared.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + shared.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+shared.networkDefaultPayloadLengthExtraBytes))/(2 ** 16))))
print '(For pubkey message) Doing proof of work...' print '(For pubkey message) Doing proof of work...'
initialHash = hashlib.sha512(payload).digest() initialHash = hashlib.sha512(payload).digest()
trialValue, nonce = proofofwork.run(target, initialHash) trialValue, nonce = proofofwork.run(target, initialHash)
@ -137,7 +137,7 @@ class singleWorker(threading.Thread):
payload = pack('>Q', nonce) + payload payload = pack('>Q', nonce) + payload
inventoryHash = calculateInventoryHash(payload) inventoryHash = calculateInventoryHash(payload)
objectType = 'pubkey' objectType = 1
shared.inventory[inventoryHash] = ( shared.inventory[inventoryHash] = (
objectType, streamNumber, payload, embeddedTime,'') objectType, streamNumber, payload, embeddedTime,'')
shared.inventorySets[streamNumber].add(inventoryHash) shared.inventorySets[streamNumber].add(inventoryHash)
@ -174,9 +174,19 @@ class singleWorker(threading.Thread):
return return
status, addressVersionNumber, streamNumber, hash = decodeAddress( status, addressVersionNumber, streamNumber, hash = decodeAddress(
myAddress) myAddress)
embeddedTime = int(time.time() + random.randrange(
-300, 300)) # the current time plus or minus five minutes TTL = 28 * 24 * 60 * 60 # 28 days
payload = pack('>I', (embeddedTime)) embeddedTime = int(time.time() + random.randrange(-300, 300) + TTL) # 28 days from now plus or minus five minutes
signedTimeForProtocolV2 = embeddedTime - TTL
"""
According to the protocol specification, the expiresTime along with the pubkey information is
signed. But to be backwards compatible during the upgrade period, we shall sign not the
expiresTime but rather the current time. There must be precisely a 28 day difference
between the two. After the upgrade period we'll switch to signing the whole payload with the
expiresTime time.
"""
payload = pack('>Q', (embeddedTime))
payload += '\x00\x00\x00\x01' # object type: pubkey
payload += encodeVarint(addressVersionNumber) # Address version number payload += encodeVarint(addressVersionNumber) # Address version number
payload += encodeVarint(streamNumber) payload += encodeVarint(streamNumber)
payload += '\x00\x00\x00\x01' # bitfield of features supported by me (see the wiki). payload += '\x00\x00\x00\x01' # bitfield of features supported by me (see the wiki).
@ -209,13 +219,18 @@ class singleWorker(threading.Thread):
myAddress, 'noncetrialsperbyte')) myAddress, 'noncetrialsperbyte'))
payload += encodeVarint(shared.config.getint( payload += encodeVarint(shared.config.getint(
myAddress, 'payloadlengthextrabytes')) myAddress, 'payloadlengthextrabytes'))
signature = highlevelcrypto.sign(payload, privSigningKeyHex)
if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT
signedData = pack('>Q', signedTimeForProtocolV2) + payload[12:]
else:
signedData = payload
signature = highlevelcrypto.sign(signedData, privSigningKeyHex)
payload += encodeVarint(len(signature)) payload += encodeVarint(len(signature))
payload += signature payload += signature
# Do the POW for this pubkey message # Do the POW for this pubkey message
target = 2 ** 64 / ((len(payload) + shared.networkDefaultPayloadLengthExtraBytes + target = 2 ** 64 / (shared.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + shared.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+shared.networkDefaultPayloadLengthExtraBytes))/(2 ** 16))))
8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte)
print '(For pubkey message) Doing proof of work...' print '(For pubkey message) Doing proof of work...'
initialHash = hashlib.sha512(payload).digest() initialHash = hashlib.sha512(payload).digest()
trialValue, nonce = proofofwork.run(target, initialHash) trialValue, nonce = proofofwork.run(target, initialHash)
@ -223,7 +238,7 @@ class singleWorker(threading.Thread):
payload = pack('>Q', nonce) + payload payload = pack('>Q', nonce) + payload
inventoryHash = calculateInventoryHash(payload) inventoryHash = calculateInventoryHash(payload)
objectType = 'pubkey' objectType = 1
shared.inventory[inventoryHash] = ( shared.inventory[inventoryHash] = (
objectType, streamNumber, payload, embeddedTime,'') objectType, streamNumber, payload, embeddedTime,'')
shared.inventorySets[streamNumber].add(inventoryHash) shared.inventorySets[streamNumber].add(inventoryHash)
@ -256,9 +271,11 @@ class singleWorker(threading.Thread):
return return
status, addressVersionNumber, streamNumber, hash = decodeAddress( status, addressVersionNumber, streamNumber, hash = decodeAddress(
myAddress) myAddress)
embeddedTime = int(time.time() + random.randrange(
-300, 300)) # the current time plus or minus five minutes TTL = 28 * 24 * 60 * 60 # 28 days
embeddedTime = int(time.time() + random.randrange(-300, 300) + TTL) # 28 days from now plus or minus five minutes
payload = pack('>Q', (embeddedTime)) payload = pack('>Q', (embeddedTime))
payload += '\x00\x00\x00\x01' # object type: pubkey
payload += encodeVarint(addressVersionNumber) # Address version number payload += encodeVarint(addressVersionNumber) # Address version number
payload += encodeVarint(streamNumber) payload += encodeVarint(streamNumber)
@ -291,11 +308,9 @@ class singleWorker(threading.Thread):
dataToEncrypt += encodeVarint(shared.config.getint( dataToEncrypt += encodeVarint(shared.config.getint(
myAddress, 'payloadlengthextrabytes')) 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
# When we encrypt, we'll use a hash of the data
# contained in an address as a decryption key. This way in order to # 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 # 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 # first. We'll also tag, unencrypted, the pubkey with part of the hash
@ -304,14 +319,33 @@ class singleWorker(threading.Thread):
doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint(
addressVersionNumber) + encodeVarint(streamNumber) + hash).digest()).digest() addressVersionNumber) + encodeVarint(streamNumber) + hash).digest()).digest()
payload += doubleHashOfAddressData[32:] # the tag payload += doubleHashOfAddressData[32:] # the tag
"""
According to the protocol specification, the expiresTime along with the pubkey information is
signed. But to be backwards compatible during the upgrade period, we shall sign not the
expiresTime but rather the current time. There must be precisely a 28 day difference
between the two. After the upgrade period we'll switch to signing the whole payload from
above appended with dataToEncrypt.
"""
if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT
dataToSign = pack('>Q', (embeddedTime - TTL))
dataToSign += encodeVarint(addressVersionNumber) # Address version number
dataToSign += encodeVarint(streamNumber)
dataToSign += dataToEncrypt
else:
dataToSign = payload + dataToEncrypt
signature = highlevelcrypto.sign(dataToSign, privSigningKeyHex)
dataToEncrypt += encodeVarint(len(signature))
dataToEncrypt += signature
privEncryptionKey = doubleHashOfAddressData[:32] privEncryptionKey = doubleHashOfAddressData[:32]
pubEncryptionKey = highlevelcrypto.pointMult(privEncryptionKey) pubEncryptionKey = highlevelcrypto.pointMult(privEncryptionKey)
payload += highlevelcrypto.encrypt( payload += highlevelcrypto.encrypt(
dataToEncrypt, pubEncryptionKey.encode('hex')) dataToEncrypt, pubEncryptionKey.encode('hex'))
# Do the POW for this pubkey message # Do the POW for this pubkey message
target = 2 ** 64 / ((len(payload) + shared.networkDefaultPayloadLengthExtraBytes + target = 2 ** 64 / (shared.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + shared.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+shared.networkDefaultPayloadLengthExtraBytes))/(2 ** 16))))
8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte)
print '(For pubkey message) Doing proof of work...' print '(For pubkey message) Doing proof of work...'
initialHash = hashlib.sha512(payload).digest() initialHash = hashlib.sha512(payload).digest()
trialValue, nonce = proofofwork.run(target, initialHash) trialValue, nonce = proofofwork.run(target, initialHash)
@ -319,7 +353,7 @@ class singleWorker(threading.Thread):
payload = pack('>Q', nonce) + payload payload = pack('>Q', nonce) + payload
inventoryHash = calculateInventoryHash(payload) inventoryHash = calculateInventoryHash(payload)
objectType = 'pubkey' objectType = 1
shared.inventory[inventoryHash] = ( shared.inventory[inventoryHash] = (
objectType, streamNumber, payload, embeddedTime, doubleHashOfAddressData[32:]) objectType, streamNumber, payload, embeddedTime, doubleHashOfAddressData[32:])
shared.inventorySets[streamNumber].add(inventoryHash) shared.inventorySets[streamNumber].add(inventoryHash)
@ -372,12 +406,22 @@ class singleWorker(threading.Thread):
pubEncryptionKey = highlevelcrypto.privToPub( pubEncryptionKey = highlevelcrypto.privToPub(
privEncryptionKeyHex).decode('hex') privEncryptionKeyHex).decode('hex')
payload = pack('>Q', (int(time.time()) + random.randrange( TTL = 2.5 * 24 * 60 * 60 # 2.5 days
-300, 300))) # the current time plus or minus five minutes embeddedTime = int(time.time() + random.randrange(-300, 300) + TTL)
payload = pack('>Q', embeddedTime)
payload += '\x00\x00\x00\x03' # object type: broadcast
if int(time.time()) < 1416175200: # Before Sun, 16 Nov 2014 22:00:00 GMT
if addressVersionNumber <= 3: if addressVersionNumber <= 3:
payload += encodeVarint(2) # broadcast version payload += encodeVarint(2) # broadcast version
else: else:
payload += encodeVarint(3) # broadcast version payload += encodeVarint(3) # broadcast version
else: # After Sun, 16 Nov 2014 22:00:00 GMT
if addressVersionNumber <= 3:
payload += encodeVarint(4) # broadcast version
else:
payload += encodeVarint(5) # broadcast version
payload += encodeVarint(streamNumber) payload += encodeVarint(streamNumber)
if addressVersionNumber >= 4: if addressVersionNumber >= 4:
doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint(
@ -387,10 +431,13 @@ class singleWorker(threading.Thread):
else: else:
tag = '' tag = ''
dataToEncrypt = ""
# the broadcast version is not included here after the end of the protocol v3 upgrade period
if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT
if addressVersionNumber <= 3: if addressVersionNumber <= 3:
dataToEncrypt = encodeVarint(2) # broadcast version dataToEncrypt += encodeVarint(2) # broadcast version
else: else:
dataToEncrypt = encodeVarint(3) # broadcast version dataToEncrypt += encodeVarint(3) # broadcast version
dataToEncrypt += encodeVarint(addressVersionNumber) dataToEncrypt += encodeVarint(addressVersionNumber)
dataToEncrypt += encodeVarint(streamNumber) dataToEncrypt += encodeVarint(streamNumber)
dataToEncrypt += '\x00\x00\x00\x01' # behavior bitfield dataToEncrypt += '\x00\x00\x00\x01' # behavior bitfield
@ -402,8 +449,13 @@ class singleWorker(threading.Thread):
dataToEncrypt += '\x02' # message encoding type 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 += 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 dataToEncrypt += 'Subject:' + subject + '\n' + 'Body:' + body
if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT
dataToSign = dataToEncrypt
else:
dataToSign = payload + dataToEncrypt
signature = highlevelcrypto.sign( signature = highlevelcrypto.sign(
dataToEncrypt, privSigningKeyHex) dataToSign, privSigningKeyHex)
dataToEncrypt += encodeVarint(len(signature)) dataToEncrypt += encodeVarint(len(signature))
dataToEncrypt += signature dataToEncrypt += signature
@ -420,8 +472,8 @@ class singleWorker(threading.Thread):
payload += highlevelcrypto.encrypt( payload += highlevelcrypto.encrypt(
dataToEncrypt, pubEncryptionKey.encode('hex')) dataToEncrypt, pubEncryptionKey.encode('hex'))
target = 2 ** 64 / ((len( TTL = 2.5 * 24 * 60 * 60 # 2.5 days for now; user specifyable later.
payload) + shared.networkDefaultPayloadLengthExtraBytes + 8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) target = 2 ** 64 / (shared.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + shared.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+shared.networkDefaultPayloadLengthExtraBytes))/(2 ** 16))))
print '(For broadcast message) Doing proof of work...' print '(For broadcast message) Doing proof of work...'
shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (
ackdata, tr.translateText("MainWindow", "Doing work necessary to send broadcast...")))) ackdata, tr.translateText("MainWindow", "Doing work necessary to send broadcast..."))))
@ -431,10 +483,17 @@ class singleWorker(threading.Thread):
payload = pack('>Q', nonce) + payload payload = pack('>Q', nonce) + payload
# Sanity check. The payload size should never be larger than 256 KiB. There should
# be checks elsewhere in the code to not let the user try to send a message this large
# until we implement message continuation.
if len(payload) > 2 ** 18: # 256 KiB
logger.critical('This broadcast object is too large to send. This should never happen. Object size: %s' % len(payload))
continue
inventoryHash = calculateInventoryHash(payload) inventoryHash = calculateInventoryHash(payload)
objectType = 'broadcast' objectType = 3
shared.inventory[inventoryHash] = ( shared.inventory[inventoryHash] = (
objectType, streamNumber, payload, int(time.time()),tag) objectType, streamNumber, payload, embeddedTime, tag)
shared.inventorySets[streamNumber].add(inventoryHash) shared.inventorySets[streamNumber].add(inventoryHash)
with shared.printLock: with shared.printLock:
print 'sending inv (within sendBroadcast function) for object:', inventoryHash.encode('hex') print 'sending inv (within sendBroadcast function) for object:', inventoryHash.encode('hex')
@ -454,67 +513,112 @@ class singleWorker(threading.Thread):
def sendMsg(self): def sendMsg(self):
# Check to see if there are any messages queued to be sent while True: # while we have a msg that needs some work
# Select just one msg that needs work. We'll get a msg
# which is ready to be sent or a msg which we have sent in
# the last 28 days which were previously marked
# as 'toodifficult'. If the user has raised the maximum acceptable
# difficulty then those msgs may now be sendable.
queryreturn = sqlQuery( queryreturn = sqlQuery(
'''SELECT DISTINCT toaddress FROM sent WHERE (status='msgqueued' AND folder='sent')''') '''SELECT toaddress, toripe, fromaddress, subject, message, ackdata, status FROM sent WHERE (status='msgqueued' or status='doingmsgpow' or status='forcepow' or (status='toodifficult' and lastactiontime>?)) and folder='sent' LIMIT 1''',
for row in queryreturn: # For each address to which we need to send a message, check to see if we have its pubkey already. int(time.time()) - 2419200)
toaddress, = row if len(queryreturn) == 0: # if there is no work to do then
status, toAddressVersion, toStreamNumber, toRipe = decodeAddress(toaddress) break # break out of this sendMsg loop and
# wait for something to get put in the shared.workerQueue.
row = queryreturn[0]
toaddress, toripe, fromaddress, subject, message, ackdata, status = row
toStatus, toAddressVersionNumber, toStreamNumber, toRipe = decodeAddress(
toaddress)
fromStatus, fromAddressVersionNumber, fromStreamNumber, fromRipe = decodeAddress(
fromaddress)
# We may or may not already have the pubkey for this toAddress. Let's check.
if status == 'forcepow':
# if the status of this msg is 'forcepow' then clearly we have the pubkey already
# because the user could not have overridden the message about the POW being
# too difficult without knowing the required difficulty.
pass
# 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 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): elif shared.config.has_section(toaddress):
sqlExecute( sqlExecute(
'''UPDATE sent SET status='doingmsgpow' WHERE toaddress=? AND status='msgqueued' ''', '''UPDATE sent SET status='doingmsgpow' WHERE toaddress=? AND status='msgqueued' ''',
toaddress) toaddress)
continue status='doingmsgpow'
else:
# Let's see if we already have the pubkey in our pubkeys table
queryreturn = sqlQuery( queryreturn = sqlQuery(
'''SELECT hash FROM pubkeys WHERE hash=? AND addressversion=?''', toRipe, toAddressVersion) '''SELECT hash FROM pubkeys WHERE hash=? AND addressversion=?''', toRipe, toAddressVersionNumber)
if queryreturn != []: # If we have the needed pubkey in the pubkey table already, set the status to doingmsgpow (we'll do it further down) if queryreturn != []: # If we have the needed pubkey in the pubkey table already,
# set the status of this msg to doingmsgpow
sqlExecute( sqlExecute(
'''UPDATE sent SET status='doingmsgpow' WHERE toaddress=? AND status='msgqueued' ''', '''UPDATE sent SET status='doingmsgpow' WHERE toaddress=? AND status='msgqueued' ''',
toaddress) toaddress)
else: # We don't have the needed pubkey in the pubkey table already. status = 'doingmsgpow'
if toAddressVersion <= 3: # mark the pubkey as 'usedpersonally' so that we don't delete it later
sqlExecute(
'''UPDATE pubkeys SET usedpersonally='yes' WHERE hash=? and addressversion=?''',
toRipe,
toAddressVersionNumber)
else: # We don't have the needed pubkey in the pubkeys table already.
if toAddressVersionNumber <= 3:
toTag = '' toTag = ''
else: else:
toTag = hashlib.sha512(hashlib.sha512(encodeVarint(toAddressVersion)+encodeVarint(toStreamNumber)+toRipe).digest()).digest()[32:] toTag = hashlib.sha512(hashlib.sha512(encodeVarint(toAddressVersionNumber)+encodeVarint(toStreamNumber)+toRipe).digest()).digest()[32:]
if toRipe in shared.neededPubkeys or toTag in shared.neededPubkeys: if toRipe in shared.neededPubkeys or toTag in shared.neededPubkeys:
# We already sent a request for the pubkey # We already sent a request for the pubkey
sqlExecute( sqlExecute(
'''UPDATE sent SET status='awaitingpubkey' WHERE toaddress=? AND status='msgqueued' ''', toaddress) '''UPDATE sent SET status='awaitingpubkey' WHERE toaddress=? AND status='msgqueued' ''', toaddress)
shared.UISignalQueue.put(('updateSentItemStatusByHash', ( shared.UISignalQueue.put(('updateSentItemStatusByHash', (
toRipe, tr.translateText("MainWindow",'Encryption key was requested earlier.')))) toRipe, tr.translateText("MainWindow",'Encryption key was requested earlier.'))))
continue #on with the next msg on which we can do some work
else: else:
# We have not yet sent a request for the pubkey # We have not yet sent a request for the pubkey
needToRequestPubkey = True 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 toAddressVersionNumber >= 4: # If we are trying to send to address version >= 4 then
# If we have it we'll need to decrypt it and put it in the pubkeys table. # 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.
# The decryptAndCheckPubkeyPayload function expects that the shared.neededPubkeys
# dictionary already contains the toAddress and cryptor object associated with
# the tag for this toAddress.
doubleHashOfToAddressData = hashlib.sha512(hashlib.sha512(encodeVarint(
toAddressVersionNumber) + encodeVarint(toStreamNumber) + toRipe).digest()).digest()
privEncryptionKey = doubleHashOfToAddressData[:32] # The first half of the sha512 hash.
tag = doubleHashOfToAddressData[32:] # The second half of the sha512 hash.
shared.neededPubkeys[tag] = (toaddress, highlevelcrypto.makeCryptor(privEncryptionKey.encode('hex')))
queryreturn = sqlQuery( queryreturn = sqlQuery(
'''SELECT payload FROM inventory WHERE objecttype='pubkey' and tag=? ''', toTag) '''SELECT payload FROM inventory WHERE objecttype=1 and tag=? ''', toTag)
if queryreturn != []: # if there was a pubkey in our inventory with the correct tag, we need to try to decrypt it. if queryreturn != []: # if there are any pubkeys in our inventory with the correct tag..
for row in queryreturn: for row in queryreturn:
data, = row payload, = row
if shared.decryptAndCheckPubkeyPayload(data, toaddress) == 'successful': if shared.decryptAndCheckPubkeyPayload(payload, toaddress) == 'successful':
needToRequestPubkey = False needToRequestPubkey = False
print 'debug. successfully decrypted and checked pubkey from sql inventory.' #testing
sqlExecute( sqlExecute(
'''UPDATE sent SET status='doingmsgpow' WHERE toaddress=? AND status='msgqueued' ''', '''UPDATE sent SET status='doingmsgpow' WHERE toaddress=? AND (status='msgqueued' or status='awaitingpubkey' or status='doingpubkeypow')''',
toaddress) toaddress)
break del shared.neededPubkeys[tag]
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 # We'll start back at the beginning, pick up this msg, mark the pubkey as 'usedpersonally', and then send the msg.
continue #else: # There was something wrong with this pubkey object even
# though it had the correct tag- almost certainly because
# of malicious behavior or a badly programmed client. If
# there are any other pubkeys in our inventory with the correct
# tag then we'll try to decrypt those.
if needToRequestPubkey: # Obviously we had no success looking in the sql inventory. Let's look through the memory inventory. if needToRequestPubkey: # Obviously we had no success looking in the sql inventory. Let's look through the memory inventory.
with shared.inventoryLock: with shared.inventoryLock:
for hash, storedValue in shared.inventory.items(): for hash, storedValue in shared.inventory.items():
objectType, streamNumber, payload, receivedTime, tag = storedValue objectType, streamNumber, payload, expiresTime, tag = storedValue
if objectType == 'pubkey' and tag == toTag: if objectType == 1 and tag == toTag:
result = shared.decryptAndCheckPubkeyPayload(payload, toaddress) #if valid, this function also puts it in the pubkeys table. if shared.decryptAndCheckPubkeyPayload(payload, toaddress) == 'successful': #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 needToRequestPubkey = False
sqlExecute( sqlExecute(
'''UPDATE sent SET status='doingmsgpow' WHERE toaddress=? AND status='msgqueued' ''', '''UPDATE sent SET status='doingmsgpow' WHERE toaddress=? AND (status='msgqueued' or status='awaitingpubkey' or status='doingpubkeypow')''',
toaddress) toaddress)
break del shared.neededPubkeys[tag]
continue # We'll start back at the beginning, pick up this msg, mark the pubkey as 'usedpersonally', and then send the msg.
if needToRequestPubkey: if needToRequestPubkey:
sqlExecute( sqlExecute(
'''UPDATE sent SET status='doingpubkeypow' WHERE toaddress=? AND status='msgqueued' ''', '''UPDATE sent SET status='doingpubkeypow' WHERE toaddress=? AND status='msgqueued' ''',
@ -522,73 +626,31 @@ class singleWorker(threading.Thread):
shared.UISignalQueue.put(('updateSentItemStatusByHash', ( shared.UISignalQueue.put(('updateSentItemStatusByHash', (
toRipe, tr.translateText("MainWindow",'Sending a request for the recipient\'s encryption key.')))) toRipe, tr.translateText("MainWindow",'Sending a request for the recipient\'s encryption key.'))))
self.requestPubKey(toaddress) self.requestPubKey(toaddress)
# Get all messages that are ready to be sent, and also all messages continue #on with the next msg on which we can do some work
# 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): # At this point we know that we have the necessary pubkey in the pubkeys table.
# There is a remote possibility that we may no longer have the TTL = 2.5 * 24 * 60 * 60 # 2.5 days
# recipient's pubkey. Let us make sure we still have it or else the embeddedTime = int(time.time() + random.randrange(-300, 300) + TTL) # 2.5 days from now plus or minus five minutes
# sendMsg function will appear to freeze. This can happen if the
# user sends a message but doesn't let the POW function finish, if not shared.config.has_section(toaddress): # if we aren't sending this to ourselves or a chan
# 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.ackdataForWhichImWatching[ackdata] = 0
shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (
ackdata, tr.translateText("MainWindow", "Looking up the receiver\'s public key")))) ackdata, tr.translateText("MainWindow", "Looking up the receiver\'s public key"))))
with shared.printLock: with shared.printLock:
print 'Sending a message. First 150 characters of message:', repr(message[:150]) 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 # 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 # the required proof of work difficulty is too hard then we'll
# abort. # abort.
queryreturn = sqlQuery( queryreturn = sqlQuery(
'SELECT transmitdata FROM pubkeys WHERE hash=? and addressversion=?', 'SELECT transmitdata FROM pubkeys WHERE hash=? and addressversion=?',
toripe, toRipe,
toAddressVersionNumber) 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: for row in queryreturn:
pubkeyPayload, = row pubkeyPayload, = row
# The pubkey message is stored the way we originally received it # The pubkey message is stored the way we originally received it
# under protocol version 2
# which means that we need to read beyond things like the nonce and # which means that we need to read beyond things like the nonce and
# time to get to the actual public keys. # time to get to the actual public keys.
if toAddressVersionNumber <= 3: if toAddressVersionNumber <= 3:
@ -645,6 +707,7 @@ class singleWorker(threading.Thread):
requiredAverageProofOfWorkNonceTrialsPerByte = shared.networkDefaultProofOfWorkNonceTrialsPerByte requiredAverageProofOfWorkNonceTrialsPerByte = shared.networkDefaultProofOfWorkNonceTrialsPerByte
if requiredPayloadLengthExtraBytes < shared.networkDefaultPayloadLengthExtraBytes: if requiredPayloadLengthExtraBytes < shared.networkDefaultPayloadLengthExtraBytes:
requiredPayloadLengthExtraBytes = shared.networkDefaultPayloadLengthExtraBytes requiredPayloadLengthExtraBytes = shared.networkDefaultPayloadLengthExtraBytes
logger.debug('Using averageProofOfWorkNonceTrialsPerByte: %s and payloadLengthExtraBytes: %s.' % (requiredAverageProofOfWorkNonceTrialsPerByte, requiredPayloadLengthExtraBytes))
shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr.translateText("MainWindow", "Doing work necessary to send message.\nReceiver\'s required difficulty: %1 and %2").arg(str(float( 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))))) requiredAverageProofOfWorkNonceTrialsPerByte) / shared.networkDefaultProofOfWorkNonceTrialsPerByte)).arg(str(float(requiredPayloadLengthExtraBytes) / shared.networkDefaultPayloadLengthExtraBytes)))))
if status != 'forcepow': if status != 'forcepow':
@ -680,10 +743,10 @@ class singleWorker(threading.Thread):
shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (
ackdata, tr.translateText("MainWindow", "Doing work necessary to send message.")))) 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: if fromAddressVersionNumber == 2:
payload = '\x01' # Message version. payload = ""
if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT
payload += '\x01' # Message version.
payload += encodeVarint(fromAddressVersionNumber) payload += encodeVarint(fromAddressVersionNumber)
payload += encodeVarint(fromStreamNumber) 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 ) 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 )
@ -714,7 +777,7 @@ class singleWorker(threading.Thread):
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. 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 += 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 += toRipe # This hash will be checked by the receiver of the message to verify that toRipe 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. payload += '\x02' # Type 2 is simple UTF-8 message encoding as specified on the Protocol Specification on the Bitmessage Wiki.
messageToTransmit = 'Subject:' + \ messageToTransmit = 'Subject:' + \
subject + '\n' + 'Body:' + message subject + '\n' + 'Body:' + message
@ -724,12 +787,18 @@ class singleWorker(threading.Thread):
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. 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 += encodeVarint(len(fullAckPayload))
payload += fullAckPayload payload += fullAckPayload
signature = highlevelcrypto.sign(payload, privSigningKeyHex) if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT
dataToSign = payload
else:
dataToSign = encodeVarint(1) + encodeVarint(toStreamNumber) + payload
signature = highlevelcrypto.sign(dataToSign, privSigningKeyHex)
payload += encodeVarint(len(signature)) payload += encodeVarint(len(signature))
payload += signature payload += signature
if fromAddressVersionNumber >= 3: if fromAddressVersionNumber >= 3:
payload = '\x01' # Message version. payload = ""
if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT
payload += '\x01' # Message version.
payload += encodeVarint(fromAddressVersionNumber) payload += encodeVarint(fromAddressVersionNumber)
payload += encodeVarint(fromStreamNumber) 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 ) 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 )
@ -774,7 +843,7 @@ class singleWorker(threading.Thread):
payload += encodeVarint(shared.config.getint( payload += encodeVarint(shared.config.getint(
fromaddress, 'payloadlengthextrabytes')) 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 += toRipe # This hash will be checked by the receiver of the message to verify that toRipe 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. payload += '\x02' # Type 2 is simple UTF-8 message encoding as specified on the Protocol Specification on the Bitmessage Wiki.
messageToTransmit = 'Subject:' + \ messageToTransmit = 'Subject:' + \
subject + '\n' + 'Body:' + message subject + '\n' + 'Body:' + message
@ -793,7 +862,11 @@ class singleWorker(threading.Thread):
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. 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 += encodeVarint(len(fullAckPayload))
payload += fullAckPayload payload += fullAckPayload
signature = highlevelcrypto.sign(payload, privSigningKeyHex) if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT
dataToSign = payload
else:
dataToSign = pack('>Q', embeddedTime) + '\x00\x00\x00\x02' + encodeVarint(1) + encodeVarint(toStreamNumber) + payload
signature = highlevelcrypto.sign(dataToSign, privSigningKeyHex)
payload += encodeVarint(len(signature)) payload += encodeVarint(len(signature))
payload += signature payload += signature
@ -805,8 +878,13 @@ class singleWorker(threading.Thread):
sqlExecute('''UPDATE sent SET status='badkey' WHERE ackdata=?''', ackdata) 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(l10n.formatTimestamp())))) shared.UISignalQueue.put(('updateSentItemStatusByAckdata',(ackdata,tr.translateText("MainWindow",'Problem: The recipient\'s encryption key is no good. Could not encrypt message. %1').arg(l10n.formatTimestamp()))))
continue continue
encryptedPayload = embeddedTime + encodeVarint(toStreamNumber) + encrypted
target = 2**64 / ((len(encryptedPayload)+requiredPayloadLengthExtraBytes+8) * requiredAverageProofOfWorkNonceTrialsPerByte) encryptedPayload = pack('>Q', embeddedTime)
encryptedPayload += '\x00\x00\x00\x02' # object type: msg
if int(time.time()) >= 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT
encryptedPayload += encodeVarint(1) # msg version
encryptedPayload += encodeVarint(toStreamNumber) + encrypted
target = 2 ** 64 / (requiredAverageProofOfWorkNonceTrialsPerByte*(len(encryptedPayload) + 8 + requiredPayloadLengthExtraBytes + ((TTL*(len(encryptedPayload)+8+requiredPayloadLengthExtraBytes))/(2 ** 16))))
with shared.printLock: 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 print '(For msg message) Doing proof of work. Total required difficulty:', float(requiredAverageProofOfWorkNonceTrialsPerByte) / shared.networkDefaultProofOfWorkNonceTrialsPerByte, 'Required small message difficulty:', float(requiredPayloadLengthExtraBytes) / shared.networkDefaultPayloadLengthExtraBytes
@ -822,10 +900,17 @@ class singleWorker(threading.Thread):
encryptedPayload = pack('>Q', nonce) + encryptedPayload encryptedPayload = pack('>Q', nonce) + encryptedPayload
# Sanity check. The encryptedPayload size should never be larger than 256 KiB. There should
# be checks elsewhere in the code to not let the user try to send a message this large
# until we implement message continuation.
if len(encryptedPayload) > 2 ** 18: # 256 KiB
logger.critical('This msg object is too large to send. This should never happen. Object size: %s' % len(encryptedPayload))
continue
inventoryHash = calculateInventoryHash(encryptedPayload) inventoryHash = calculateInventoryHash(encryptedPayload)
objectType = 'msg' objectType = 2
shared.inventory[inventoryHash] = ( shared.inventory[inventoryHash] = (
objectType, toStreamNumber, encryptedPayload, int(time.time()),'') objectType, toStreamNumber, encryptedPayload, embeddedTime, '')
shared.inventorySets[toStreamNumber].add(inventoryHash) shared.inventorySets[toStreamNumber].add(inventoryHash)
if shared.config.has_section(toaddress): if shared.config.has_section(toaddress):
shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr.translateText("MainWindow", "Message sent. Sent on %1").arg(l10n.formatTimestamp())))) shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr.translateText("MainWindow", "Message sent. Sent on %1").arg(l10n.formatTimestamp()))))
@ -837,7 +922,7 @@ class singleWorker(threading.Thread):
toStreamNumber, 'advertiseobject', inventoryHash)) toStreamNumber, 'advertiseobject', inventoryHash))
# Update the status of the message in the 'sent' table to have a # Update the status of the message in the 'sent' table to have a
# 'msgsent' status or 'msgsentnoackexpected' status. # 'msgsent' or 'msgsentnoackexpected' status.
if shared.config.has_section(toaddress): if shared.config.has_section(toaddress):
newStatus = 'msgsentnoackexpected' newStatus = 'msgsentnoackexpected'
else: else:
@ -879,11 +964,18 @@ class singleWorker(threading.Thread):
if addressVersionNumber <= 3: if addressVersionNumber <= 3:
shared.neededPubkeys[ripe] = 0 shared.neededPubkeys[ripe] = 0
elif addressVersionNumber >= 4: elif addressVersionNumber >= 4:
# If the user just clicked 'send' then the tag (and other information) will already
# be in the neededPubkeys dictionary. But if we are recovering from a restart
# of the client then we have to put it in now.
privEncryptionKey = hashlib.sha512(hashlib.sha512(encodeVarint(addressVersionNumber)+encodeVarint(streamNumber)+ripe).digest()).digest()[:32] # Note that this is the first half of the sha512 hash. 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. 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. if tag not in shared.neededPubkeys:
payload = pack('>Q', (int(time.time()) + random.randrange( shared.neededPubkeys[tag] = (toAddress, 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.
-300, 300))) # the current time plus or minus five minutes.
TTL = 2.5 * 24 * 60 * 60 # 2.5 days
embeddedTime = int(time.time() + random.randrange(-300, 300) + TTL) # 2.5 days from now plus or minus five minutes
payload = pack('>Q', embeddedTime)
payload += '\x00\x00\x00\x00' # object type: getpubkey
payload += encodeVarint(addressVersionNumber) payload += encodeVarint(addressVersionNumber)
payload += encodeVarint(streamNumber) payload += encodeVarint(streamNumber)
if addressVersionNumber <= 3: if addressVersionNumber <= 3:
@ -900,19 +992,19 @@ class singleWorker(threading.Thread):
shared.UISignalQueue.put(('updateStatusBar', statusbar)) shared.UISignalQueue.put(('updateStatusBar', statusbar))
shared.UISignalQueue.put(('updateSentItemStatusByHash', ( shared.UISignalQueue.put(('updateSentItemStatusByHash', (
ripe, tr.translateText("MainWindow",'Doing work necessary to request encryption key.')))) ripe, tr.translateText("MainWindow",'Doing work necessary to request encryption key.'))))
target = 2 ** 64 / ((len(payload) + shared.networkDefaultPayloadLengthExtraBytes +
8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) TTL = 2.5 * 24 * 60 * 60 # 2.5 days
target = 2 ** 64 / (shared.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + shared.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+shared.networkDefaultPayloadLengthExtraBytes))/(2 ** 16))))
initialHash = hashlib.sha512(payload).digest() initialHash = hashlib.sha512(payload).digest()
trialValue, nonce = proofofwork.run(target, initialHash) trialValue, nonce = proofofwork.run(target, initialHash)
with shared.printLock: with shared.printLock:
print 'Found proof of work', trialValue, 'Nonce:', nonce print 'Found proof of work', trialValue, 'Nonce:', nonce
payload = pack('>Q', nonce) + payload payload = pack('>Q', nonce) + payload
inventoryHash = calculateInventoryHash(payload) inventoryHash = calculateInventoryHash(payload)
objectType = 'getpubkey' objectType = 1
shared.inventory[inventoryHash] = ( shared.inventory[inventoryHash] = (
objectType, streamNumber, payload, int(time.time()),'') objectType, streamNumber, payload, embeddedTime, '')
shared.inventorySets[streamNumber].add(inventoryHash) shared.inventorySets[streamNumber].add(inventoryHash)
print 'sending inv (for the getpubkey message)' print 'sending inv (for the getpubkey message)'
shared.broadcastToSendDataQueues(( shared.broadcastToSendDataQueues((
@ -927,11 +1019,14 @@ class singleWorker(threading.Thread):
shared.UISignalQueue.put(('updateSentItemStatusByHash', (ripe, tr.translateText("MainWindow",'Sending public key request. Waiting for reply. Requested at %1').arg(l10n.formatTimestamp())))) shared.UISignalQueue.put(('updateSentItemStatusByHash', (ripe, tr.translateText("MainWindow",'Sending public key request. Waiting for reply. Requested at %1').arg(l10n.formatTimestamp()))))
def generateFullAckMessage(self, ackdata, toStreamNumber): def generateFullAckMessage(self, ackdata, toStreamNumber):
embeddedTime = pack('>Q', (int(time.time()) + random.randrange( embeddedTime = int(time.time() + random.randrange(-300, 300)) # the current time plus or minus five minutes.
-300, 300))) # the current time plus or minus five minutes. payload = pack('>Q', (embeddedTime))
payload = embeddedTime + encodeVarint(toStreamNumber) + ackdata payload += '\x00\x00\x00\x02' # object type: msg
target = 2 ** 64 / ((len(payload) + shared.networkDefaultPayloadLengthExtraBytes + if int(time.time()) >= 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT
8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) payload += encodeVarint(1) # msg version
payload += encodeVarint(toStreamNumber) + ackdata
TTL = 2.5 * 24 * 60 * 60 # 2.5 days
target = 2 ** 64 / (shared.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + shared.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+shared.networkDefaultPayloadLengthExtraBytes))/(2 ** 16))))
with shared.printLock: with shared.printLock:
print '(For ack message) Doing proof of work...' print '(For ack message) Doing proof of work...'
@ -946,4 +1041,4 @@ class singleWorker(threading.Thread):
pass pass
payload = pack('>Q', nonce) + payload payload = pack('>Q', nonce) + payload
return shared.CreatePacket('msg', payload) return shared.CreatePacket('object', payload)

View File

@ -39,28 +39,33 @@ class sqlThread(threading.Thread):
'''CREATE TABLE blacklist (label text, address text, enabled bool)''' ) '''CREATE TABLE blacklist (label text, address text, enabled bool)''' )
self.cur.execute( self.cur.execute(
'''CREATE TABLE whitelist (label text, address text, enabled bool)''' ) '''CREATE TABLE whitelist (label text, address text, enabled bool)''' )
# Explanation of what is in the pubkeys table: """
# The hash is the RIPEMD160 hash that is encoded in the Bitmessage address. Explanation of what is in the pubkeys table:
# transmitdata is literally the data that was included in the Bitmessage pubkey message when it arrived, except for the 24 byte protocol header- ie, it starts with the POW nonce. The hash is the RIPEMD160 hash that is encoded in the Bitmessage address.
# time is the time that the pubkey was broadcast on the network same as with every other type of Bitmessage object.
# usedpersonally is set to "yes" if we have used the key transmitdata /was/ literally the data that was included in the Bitmessage pubkey message when it arrived,
# personally. This keeps us from deleting it because we may want to except for the 24 byte protocol header- ie, it started with the POW nonce. Since protocol v3, to maintain
# reply to a message in the future. This field is not a bool backwards compability, the data format of the data on disk is staying the same even though the wire format has changed.
# because we may need more flexability in the future and it doesn't
# take up much more space anyway. time is the time that the pubkey was broadcast on the network same as with every other type of Bitmessage object.
usedpersonally is set to "yes" if we have used the key personally. This keeps us from deleting it because we may want to
reply to a message in the future. This field is not a bool because we may need more flexability in the future and it doesn't
take up much more space anyway.
"""
self.cur.execute( self.cur.execute(
'''CREATE TABLE pubkeys (hash blob, addressversion int, transmitdata blob, time int, usedpersonally text, UNIQUE(hash, addressversion) 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( self.cur.execute(
'''CREATE TABLE inventory (hash blob, objecttype text, streamnumber int, payload blob, receivedtime integer, tag blob, UNIQUE(hash) ON CONFLICT REPLACE)''' ) '''CREATE TABLE inventory (hash blob, objecttype int, streamnumber int, payload blob, expirestime integer, tag blob, UNIQUE(hash) ON CONFLICT REPLACE)''' )
self.cur.execute( self.cur.execute(
'''INSERT INTO subscriptions VALUES('Bitmessage new releases/announcements','BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw',1)''') '''INSERT INTO subscriptions VALUES('Bitmessage new releases/announcements','BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw',1)''')
self.cur.execute( self.cur.execute(
'''CREATE TABLE settings (key blob, value blob, UNIQUE(key) ON CONFLICT REPLACE)''' ) '''CREATE TABLE settings (key blob, value blob, UNIQUE(key) ON CONFLICT REPLACE)''' )
self.cur.execute( '''INSERT INTO settings VALUES('version','6')''') self.cur.execute( '''INSERT INTO settings VALUES('version','7')''')
self.cur.execute( '''INSERT INTO settings VALUES('lastvacuumtime',?)''', ( self.cur.execute( '''INSERT INTO settings VALUES('lastvacuumtime',?)''', (
int(time.time()),)) int(time.time()),))
self.cur.execute( self.cur.execute(
'''CREATE TABLE objectprocessorqueue (objecttype text, data blob, UNIQUE(objecttype, data) ON CONFLICT REPLACE)''' ) '''CREATE TABLE objectprocessorqueue (objecttype int, data blob, UNIQUE(objecttype, data) ON CONFLICT REPLACE)''' )
self.conn.commit() self.conn.commit()
logger.info('Created messages database file') logger.info('Created messages database file')
except Exception as err: except Exception as err:
@ -247,9 +252,11 @@ class sqlThread(threading.Thread):
shared.config.set('bitmessagesettings', 'sendoutgoingconnections', 'True') shared.config.set('bitmessagesettings', 'sendoutgoingconnections', 'True')
# Raise the default required difficulty from 1 to 2 # Raise the default required difficulty from 1 to 2
# With the change to protocol v3, this is obsolete.
if shared.config.getint('bitmessagesettings', 'settingsversion') == 6: if shared.config.getint('bitmessagesettings', 'settingsversion') == 6:
if int(shared.config.get('bitmessagesettings','defaultnoncetrialsperbyte')) == shared.networkDefaultProofOfWorkNonceTrialsPerByte: """if int(shared.config.get('bitmessagesettings','defaultnoncetrialsperbyte')) == shared.networkDefaultProofOfWorkNonceTrialsPerByte:
shared.config.set('bitmessagesettings','defaultnoncetrialsperbyte', str(shared.networkDefaultProofOfWorkNonceTrialsPerByte * 2)) shared.config.set('bitmessagesettings','defaultnoncetrialsperbyte', str(shared.networkDefaultProofOfWorkNonceTrialsPerByte * 2))
"""
shared.config.set('bitmessagesettings', 'settingsversion', '7') shared.config.set('bitmessagesettings', 'settingsversion', '7')
with open(shared.appdata + 'keys.dat', 'wb') as configfile: with open(shared.appdata + 'keys.dat', 'wb') as configfile:
shared.config.write(configfile) shared.config.write(configfile)
@ -278,7 +285,7 @@ class sqlThread(threading.Thread):
with open(shared.appdata + 'keys.dat', 'wb') as configfile: with open(shared.appdata + 'keys.dat', 'wb') as configfile:
shared.config.write(configfile) shared.config.write(configfile)
#Adjusting time period to stop sending messages #Add settings to support no longer resending messages after a certain period of time even if we never get an ack
if shared.config.getint('bitmessagesettings', 'settingsversion') == 7: if shared.config.getint('bitmessagesettings', 'settingsversion') == 7:
shared.config.set( shared.config.set(
'bitmessagesettings', 'stopresendingafterxdays', '') 'bitmessagesettings', 'stopresendingafterxdays', '')
@ -303,8 +310,38 @@ class sqlThread(threading.Thread):
parameters = (6,) parameters = (6,)
self.cur.execute(item, parameters) self.cur.execute(item, parameters)
# changes related to protocol v3
# In table inventory and objectprocessorqueue, objecttype is now an integer (it was a human-friendly string previously)
item = '''SELECT value FROM settings WHERE key='version';'''
parameters = ''
self.cur.execute(item, parameters)
currentVersion = int(self.cur.fetchall()[0][0])
if currentVersion == 6:
logger.debug('In messages.dat database, dropping and recreating the inventory table.')
self.cur.execute( '''DROP TABLE inventory''')
self.cur.execute( '''CREATE TABLE inventory (hash blob, objecttype int, streamnumber int, payload blob, expirestime integer, tag blob, UNIQUE(hash) ON CONFLICT REPLACE)''' )
self.cur.execute( '''DROP TABLE objectprocessorqueue''')
self.cur.execute( '''CREATE TABLE objectprocessorqueue (objecttype int, data blob, UNIQUE(objecttype, data) ON CONFLICT REPLACE)''' )
item = '''update settings set value=? WHERE key='version';'''
parameters = (7,)
self.cur.execute(item, parameters)
logger.debug('Finished dropping and recreating the inventory table.')
# With the change to protocol version 3, reset the user-settable difficulties to 1
if shared.config.getint('bitmessagesettings', 'settingsversion') == 8:
shared.config.set('bitmessagesettings','defaultnoncetrialsperbyte', str(shared.networkDefaultProofOfWorkNonceTrialsPerByte))
shared.config.set('bitmessagesettings','defaultpayloadlengthextrabytes', str(shared.networkDefaultPayloadLengthExtraBytes))
previousTotalDifficulty = int(shared.config.getint('bitmessagesettings', 'maxacceptablenoncetrialsperbyte')) / 320
previousSmallMessageDifficulty = int(shared.config.getint('bitmessagesettings', 'maxacceptablepayloadlengthextrabytes')) / 1400
shared.config.set('bitmessagesettings','maxacceptablenoncetrialsperbyte', str(previousTotalDifficulty * 1000))
shared.config.set('bitmessagesettings','maxacceptablepayloadlengthextrabytes', str(previousSmallMessageDifficulty * 1000))
shared.config.set('bitmessagesettings', 'settingsversion', '9')
with open(shared.appdata + 'keys.dat', 'wb') as configfile:
shared.config.write(configfile)
# Are you hoping to add a new option to the keys.dat file of existing # Are you hoping to add a new option to the keys.dat file of existing
# Bitmessage users? Add it right above this line! # Bitmessage users or modify the SQLite database? Add it right above this line!
try: try:
testpayload = '\x00\x00' testpayload = '\x00\x00'

View File

@ -12,15 +12,8 @@ def createDefaultKnownNodes(appdata):
stream1 = {} stream1 = {}
#stream1[shared.Peer('2604:2000:1380:9f:82e:148b:2746:d0c7', 8080)] = int(time.time()) #stream1[shared.Peer('2604:2000:1380:9f:82e:148b:2746:d0c7', 8080)] = int(time.time())
stream1[shared.Peer('68.33.0.104', 8444)] = int(time.time()) stream1[shared.Peer('158.222.211.81', 8080)] = int(time.time())
stream1[shared.Peer('97.77.34.35', 8444)] = int(time.time()) stream1[shared.Peer('85.214.194.88', 8444)] = int(time.time())
stream1[shared.Peer('71.232.195.131', 8444)] = int(time.time())
stream1[shared.Peer('192.241.231.39', 8444)] = int(time.time())
stream1[shared.Peer('75.66.0.116', 8444)] = int(time.time())
stream1[shared.Peer('182.169.23.102', 8444)] = int(time.time())
stream1[shared.Peer('75.95.134.9', 8444)] = int(time.time())
stream1[shared.Peer('46.236.100.108', 48444)] = int(time.time())
stream1[shared.Peer('66.108.53.42', 8080)] = int(time.time())
############# Stream 2 ################# ############# Stream 2 #################
stream2 = {} stream2 = {}

View File

@ -25,7 +25,7 @@ def knownNodes():
shared.knownNodes[stream][peer] = time shared.knownNodes[stream][peer] = time
except: except:
shared.knownNodes = defaultKnownNodes.createDefaultKnownNodes(shared.appdata) shared.knownNodes = defaultKnownNodes.createDefaultKnownNodes(shared.appdata)
if shared.config.getint('bitmessagesettings', 'settingsversion') > 8: if shared.config.getint('bitmessagesettings', 'settingsversion') > 9:
print 'Bitmessage cannot read future versions of the keys file (keys.dat). Run the newer version of Bitmessage.' print 'Bitmessage cannot read future versions of the keys file (keys.dat). Run the newer version of Bitmessage.'
raise SystemExit raise SystemExit

View File

@ -57,7 +57,7 @@ def loadConfig():
# This appears to be the first time running the program; there is # This appears to be the first time running the program; there is
# no config file (or it cannot be accessed). Create config file. # no config file (or it cannot be accessed). Create config file.
shared.config.add_section('bitmessagesettings') shared.config.add_section('bitmessagesettings')
shared.config.set('bitmessagesettings', 'settingsversion', '8') shared.config.set('bitmessagesettings', 'settingsversion', '9')
shared.config.set('bitmessagesettings', 'port', '8444') shared.config.set('bitmessagesettings', 'port', '8444')
shared.config.set( shared.config.set(
'bitmessagesettings', 'timeformat', '%%a, %%d %%b %%Y %%I:%%M %%p') 'bitmessagesettings', 'timeformat', '%%a, %%d %%b %%Y %%I:%%M %%p')
@ -89,7 +89,7 @@ def loadConfig():
shared.config.set( shared.config.set(
'bitmessagesettings', 'messagesencrypted', 'false') 'bitmessagesettings', 'messagesencrypted', 'false')
shared.config.set('bitmessagesettings', 'defaultnoncetrialsperbyte', str( shared.config.set('bitmessagesettings', 'defaultnoncetrialsperbyte', str(
shared.networkDefaultProofOfWorkNonceTrialsPerByte * 2)) shared.networkDefaultProofOfWorkNonceTrialsPerByte))
shared.config.set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str( shared.config.set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str(
shared.networkDefaultPayloadLengthExtraBytes)) shared.networkDefaultPayloadLengthExtraBytes))
shared.config.set('bitmessagesettings', 'minimizeonclose', 'false') shared.config.set('bitmessagesettings', 'minimizeonclose', 'false')

View File

@ -33,7 +33,10 @@ def sign(msg,hexPrivkey):
return makeCryptor(hexPrivkey).sign(msg) return makeCryptor(hexPrivkey).sign(msg)
# Verifies with hex public key # Verifies with hex public key
def verify(msg,sig,hexPubkey): def verify(msg,sig,hexPubkey):
try:
return makePubCryptor(hexPubkey).verify(sig,msg) return makePubCryptor(hexPubkey).verify(sig,msg)
except:
return False
# Does an EC point multiplication; turns a private key into a public key. # Does an EC point multiplication; turns a private key into a public key.
def pointMult(secret): def pointMult(secret):

View File

@ -54,13 +54,13 @@ def readPubkeys():
def readInventory(): def readInventory():
print 'Printing everything in inventory table:' print 'Printing everything in inventory table:'
item = '''select hash, objecttype, streamnumber, payload, receivedtime from inventory''' item = '''select hash, objecttype, streamnumber, payload, expirestime from inventory'''
parameters = '' parameters = ''
cur.execute(item, parameters) cur.execute(item, parameters)
output = cur.fetchall() output = cur.fetchall()
for row in output: for row in output:
hash, objecttype, streamnumber, payload, receivedtime = row hash, objecttype, streamnumber, payload, expirestime = row
print 'Hash:', hash.encode('hex'), objecttype, streamnumber, '\t', payload.encode('hex'), '\t', unicode(strftime('%a, %d %b %Y %I:%M %p',localtime(receivedtime)),'utf-8') print 'Hash:', hash.encode('hex'), objecttype, streamnumber, '\t', payload.encode('hex'), '\t', unicode(strftime('%a, %d %b %Y %I:%M %p',localtime(expirestime)),'utf-8')
def takeInboxMessagesOutOfTrash(): def takeInboxMessagesOutOfTrash():

View File

@ -5,6 +5,7 @@ import hashlib
from struct import unpack, pack from struct import unpack, pack
import sys import sys
from shared import config, frozen from shared import config, frozen
import shared
#import os #import os
def _set_idle(): def _set_idle():
@ -39,7 +40,6 @@ def _doSafePoW(target, initialHash):
return [trialValue, nonce] return [trialValue, nonce]
def _doFastPoW(target, initialHash): def _doFastPoW(target, initialHash):
import shared
import time import time
from multiprocessing import Pool, cpu_count from multiprocessing import Pool, cpu_count
try: try:

View File

@ -436,11 +436,16 @@ class ECC:
pubkey = ephem.get_pubkey() pubkey = ephem.get_pubkey()
iv = OpenSSL.rand(OpenSSL.get_cipher(ciphername).get_blocksize()) iv = OpenSSL.rand(OpenSSL.get_cipher(ciphername).get_blocksize())
ctx = Cipher(key_e, iv, 1, ciphername) ctx = Cipher(key_e, iv, 1, ciphername)
import time
if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT
ciphertext = ctx.ciphering(data) ciphertext = ctx.ciphering(data)
#ciphertext = iv + pubkey + ctx.ciphering(data) # We will switch to this line after an upgrade period else:
ciphertext = iv + pubkey + ctx.ciphering(data) # Everyone should be using this line after the Bitmessage protocol v3 upgrade period
mac = hmac_sha256(key_m, ciphertext) mac = hmac_sha256(key_m, ciphertext)
if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT
return iv + pubkey + ciphertext + mac return iv + pubkey + ciphertext + mac
#return ciphertext + mac # We will switch to this line after an upgrade period. else:
return ciphertext + mac # Everyone should be using this line after the Bitmessage protocol v3 upgrade period
def decrypt(self, data, ciphername='aes-256-cbc'): def decrypt(self, data, ciphername='aes-256-cbc'):
""" """

View File

@ -1,9 +1,7 @@
softwareVersion = '0.4.3' softwareVersion = '0.4.4'
verbose = 1 verbose = 1
maximumAgeOfAnObjectThatIAmWillingToAccept = 216000 # Equals two days and 12 hours. maximumAgeOfAnObjectThatIAmWillingToAccept = 216000 # This is obsolete with the change to protocol v3 but the singleCleaner thread still hasn't been updated so we need this a little longer.
lengthOfTimeToLeaveObjectsInInventory = 237600 # Equals two days and 18 hours. This should be longer than maximumAgeOfAnObjectThatIAmWillingToAccept so that we don't process messages twice.
lengthOfTimeToHoldOnToAllPubkeys = 2419200 # Equals 4 weeks. You could make this longer if you want but making it shorter would not be advisable because there is a very small possibility that it could keep you from obtaining a needed pubkey for a period of time. lengthOfTimeToHoldOnToAllPubkeys = 2419200 # Equals 4 weeks. You could make this longer if you want but making it shorter would not be advisable because there is a very small possibility that it could keep you from obtaining a needed pubkey for a period of time.
maximumAgeOfObjectsThatIAdvertiseToOthers = 216000 # Equals two days and 12 hours
maximumAgeOfNodesThatIAdvertiseToOthers = 10800 # Equals three hours maximumAgeOfNodesThatIAdvertiseToOthers = 10800 # Equals three hours
useVeryEasyProofOfWorkForTesting = False # If you set this to True while on the normal network, you won't be able to send or sometimes receive messages. useVeryEasyProofOfWorkForTesting = False # If you set this to True while on the normal network, you won't be able to send or sometimes receive messages.
@ -22,12 +20,13 @@ import threading
import time import time
from os import path, environ from os import path, environ
from struct import Struct from struct import Struct
import traceback
# Project imports. # Project imports.
from addresses import * from addresses import *
import highlevelcrypto import highlevelcrypto
import shared import shared
import helper_startup #import helper_startup
from helper_sql import * from helper_sql import *
@ -82,8 +81,8 @@ objectProcessorQueue = Queue.Queue(
streamsInWhichIAmParticipating = {} streamsInWhichIAmParticipating = {}
#If changed, these values will cause particularly unexpected behavior: You won't be able to either send or receive messages because the proof of work you do (or demand) won't match that done or demanded by others. Don't change them! #If changed, these values will cause particularly unexpected behavior: You won't be able to either send or receive messages because the proof of work you do (or demand) won't match that done or demanded by others. Don't change them!
networkDefaultProofOfWorkNonceTrialsPerByte = 320 #The amount of work that should be performed (and demanded) per byte of the payload. Double this number to double the work. networkDefaultProofOfWorkNonceTrialsPerByte = 1000 #The amount of work that should be performed (and demanded) per byte of the payload.
networkDefaultPayloadLengthExtraBytes = 14000 #To make sending short messages a little more difficult, this value is added to the payload length for use in calculating the proof of work target. networkDefaultPayloadLengthExtraBytes = 1000 #To make sending short messages a little more difficult, this value is added to the payload length for use in calculating the proof of work target.
# Remember here the RPC port read from namecoin.conf so we can restore to # Remember here the RPC port read from namecoin.conf so we can restore to
# it as default whenever the user changes the "method" selection for # it as default whenever the user changes the "method" selection for
@ -114,9 +113,6 @@ Header = Struct('!L12sL4s')
#Create a packet #Create a packet
def CreatePacket(command, payload=''): def CreatePacket(command, payload=''):
payload_length = len(payload) payload_length = len(payload)
if payload_length == 0:
checksum = '\xCF\x83\xE1\x35'
else:
checksum = hashlib.sha512(payload).digest()[0:4] checksum = hashlib.sha512(payload).digest()[0:4]
b = bytearray(Header.size + payload_length) b = bytearray(Header.size + payload_length)
@ -137,7 +133,7 @@ def encodeHost(host):
def assembleVersionMessage(remoteHost, remotePort, myStreamNumber): def assembleVersionMessage(remoteHost, remotePort, myStreamNumber):
payload = '' payload = ''
payload += pack('>L', 2) # protocol version. payload += pack('>L', 3) # protocol version.
payload += pack('>q', 1) # bitflags of the services I offer. payload += pack('>q', 1) # bitflags of the services I offer.
payload += pack('>q', int(time.time())) payload += pack('>q', int(time.time()))
@ -314,17 +310,20 @@ def reloadBroadcastSendersForWhichImWatching():
privEncryptionKey = doubleHashOfAddressData[:32] privEncryptionKey = doubleHashOfAddressData[:32]
MyECSubscriptionCryptorObjects[tag] = highlevelcrypto.makeCryptor(privEncryptionKey.encode('hex')) MyECSubscriptionCryptorObjects[tag] = highlevelcrypto.makeCryptor(privEncryptionKey.encode('hex'))
def isProofOfWorkSufficient( def isProofOfWorkSufficient(data,
data,
nonceTrialsPerByte=0, nonceTrialsPerByte=0,
payloadLengthExtraBytes=0): payloadLengthExtraBytes=0):
if nonceTrialsPerByte < networkDefaultProofOfWorkNonceTrialsPerByte: if nonceTrialsPerByte < networkDefaultProofOfWorkNonceTrialsPerByte:
nonceTrialsPerByte = networkDefaultProofOfWorkNonceTrialsPerByte nonceTrialsPerByte = networkDefaultProofOfWorkNonceTrialsPerByte
if payloadLengthExtraBytes < networkDefaultPayloadLengthExtraBytes: if payloadLengthExtraBytes < networkDefaultPayloadLengthExtraBytes:
payloadLengthExtraBytes = networkDefaultPayloadLengthExtraBytes payloadLengthExtraBytes = networkDefaultPayloadLengthExtraBytes
endOfLifeTime, = unpack('>Q', data[8:16])
TTL = endOfLifeTime - int(time.time())
if TTL < 300:
TTL = 300
POW, = unpack('>Q', hashlib.sha512(hashlib.sha512(data[ POW, = unpack('>Q', hashlib.sha512(hashlib.sha512(data[
:8] + hashlib.sha512(data[8:]).digest()).digest()).digest()[0:8]) :8] + hashlib.sha512(data[8:]).digest()).digest()).digest()[0:8])
return POW <= 2 ** 64 / ((len(data) + payloadLengthExtraBytes) * (nonceTrialsPerByte)) return POW <= 2 ** 64 / (nonceTrialsPerByte*(len(data) + payloadLengthExtraBytes + ((TTL*(len(data)+payloadLengthExtraBytes))/(2 ** 16))))
def doCleanShutdown(): def doCleanShutdown():
global shutdown global shutdown
@ -383,9 +382,9 @@ def flushInventory():
#Note that the singleCleanerThread clears out the inventory dictionary from time to time, although it only clears things that have been in the dictionary for a long time. This clears the inventory dictionary Now. #Note that the singleCleanerThread clears out the inventory dictionary from time to time, although it only clears things that have been in the dictionary for a long time. This clears the inventory dictionary Now.
with SqlBulkExecute() as sql: with SqlBulkExecute() as sql:
for hash, storedValue in inventory.items(): for hash, storedValue in inventory.items():
objectType, streamNumber, payload, receivedTime, tag = storedValue objectType, streamNumber, payload, expiresTime, tag = storedValue
sql.execute('''INSERT INTO inventory VALUES (?,?,?,?,?,?)''', sql.execute('''INSERT INTO inventory VALUES (?,?,?,?,?,?)''',
hash,objectType,streamNumber,payload,receivedTime,tag) hash,objectType,streamNumber,payload,expiresTime,tag)
del inventory[hash] del inventory[hash]
def fixPotentiallyInvalidUTF8Data(text): def fixPotentiallyInvalidUTF8Data(text):
@ -455,44 +454,71 @@ def isBitSetWithinBitfield(fourByteString, n):
x, = unpack('>L', fourByteString) x, = unpack('>L', fourByteString)
return x & 2**n != 0 return x & 2**n != 0
def decryptAndCheckPubkeyPayload(payload, address):
def decryptAndCheckPubkeyPayload(data, address):
"""
With the changes in protocol v3, to maintain backwards compatibility, signatures will be sent
the 'old' way during an upgrade period and then a 'new' simpler way after that. We will therefore
check the sig both ways.
Old way:
signedData = timePubkeyWasSigned(8 bytes) + addressVersion + streamNumber + the decrypted data down through the payloadLengthExtraBytes
New way:
signedData = all of the payload data from the time to the tag + the decrypted data down through the payloadLengthExtraBytes
The timePubkeyWasSigned will be calculated by subtracting 28 days form the embedded expiresTime.
"""
"""
The time, address version, and stream number are not encrypted so let's
keep that data here for now.
"""
try:
status, addressVersion, streamNumber, ripe = decodeAddress(address) status, addressVersion, streamNumber, ripe = decodeAddress(address)
doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint(
addressVersion) + encodeVarint(streamNumber) + ripe).digest()).digest() readPosition = 20 # bypass the nonce, time, and object type
readPosition = 8 # bypass the nonce embeddedAddressVersion, varintLength = decodeVarint(data[readPosition:readPosition + 10])
readPosition += 8 # bypass the time
embeddedVersionNumber, varintLength = decodeVarint(
payload[readPosition:readPosition + 10])
if embeddedVersionNumber != addressVersion:
logger.info('Pubkey decryption was UNsuccessful due to address version mismatch. This shouldn\'t have happened.')
return 'failed'
readPosition += varintLength readPosition += varintLength
embeddedStreamNumber, varintLength = decodeVarint( embeddedStreamNumber, varintLength = decodeVarint(data[readPosition:readPosition + 10])
payload[readPosition:readPosition + 10])
if embeddedStreamNumber != streamNumber:
logger.info('Pubkey decryption was UNsuccessful due to stream number mismatch. This shouldn\'t have happened.')
return 'failed'
readPosition += varintLength readPosition += varintLength
signedData = payload[8:readPosition] # Some of the signed data is not encrypted so let's keep it for now.
toTag = payload[readPosition:readPosition+32] if addressVersion != embeddedAddressVersion:
readPosition += 32 #for the tag logger.info('Pubkey decryption was UNsuccessful due to address version mismatch.')
encryptedData = payload[readPosition:] return 'failed'
if streamNumber != embeddedStreamNumber:
logger.info('Pubkey decryption was UNsuccessful due to stream number mismatch.')
return 'failed'
expiresTime, = unpack('>Q', data[8:16])
TTL = 28 * 24 * 60 * 60
signedDataOldMethod = pack('>Q', (expiresTime - TTL)) # the time that the pubkey was signed. 8 bytes.
signedDataOldMethod += data[20:readPosition] # the address version and stream number
tag = data[readPosition:readPosition + 32]
readPosition += 32
signedDataNewMethod = data[8:readPosition] # the time through the tag
encryptedData = data[readPosition:]
# Let us try to decrypt the pubkey # Let us try to decrypt the pubkey
privEncryptionKey = doubleHashOfAddressData[:32] toAddress, cryptorObject = shared.neededPubkeys[tag]
cryptorObject = highlevelcrypto.makeCryptor(privEncryptionKey.encode('hex')) if toAddress != address:
logger.critical('decryptAndCheckPubkeyPayload failed due to toAddress mismatch. This is very peculiar. toAddress: %s, address %s' % (toAddress, address))
# the only way I can think that this could happen is if someone encodes their address data two different ways.
# That sort of address-malleability should have been prevented earlier.
return 'failed'
try: try:
decryptedData = cryptorObject.decrypt(encryptedData) decryptedData = cryptorObject.decrypt(encryptedData)
except: except:
# Someone must have encrypted some data with a different key # Someone must have encrypted some data with a different key
# but tagged it with a tag for which we are watching. # but tagged it with a tag for which we are watching.
logger.info('Pubkey decryption was UNsuccessful. This shouldn\'t have happened.') logger.info('Pubkey decryption was unsuccessful.')
return 'failed' return 'failed'
logger.debug('Pubkey decryption successful')
readPosition = 4 # bypass the behavior bitfield readPosition = 0
bitfieldBehaviors = decryptedData[readPosition:readPosition + 4]
readPosition += 4
publicSigningKey = '\x04' + decryptedData[readPosition:readPosition + 64] publicSigningKey = '\x04' + decryptedData[readPosition:readPosition + 64]
# Is it possible for a public key to be invalid such that trying to
# encrypt or check a sig with it will cause an error? If it is, we should
# probably test these keys here.
readPosition += 64 readPosition += 64
publicEncryptionKey = '\x04' + decryptedData[readPosition:readPosition + 64] publicEncryptionKey = '\x04' + decryptedData[readPosition:readPosition + 64]
readPosition += 64 readPosition += 64
@ -502,18 +528,22 @@ def decryptAndCheckPubkeyPayload(payload, address):
specifiedPayloadLengthExtraBytes, specifiedPayloadLengthExtraBytesLength = decodeVarint( specifiedPayloadLengthExtraBytes, specifiedPayloadLengthExtraBytesLength = decodeVarint(
decryptedData[readPosition:readPosition + 10]) decryptedData[readPosition:readPosition + 10])
readPosition += specifiedPayloadLengthExtraBytesLength readPosition += specifiedPayloadLengthExtraBytesLength
signedData += decryptedData[:readPosition] signedDataOldMethod += decryptedData[:readPosition]
signedDataNewMethod += decryptedData[:readPosition]
signatureLength, signatureLengthLength = decodeVarint( signatureLength, signatureLengthLength = decodeVarint(
decryptedData[readPosition:readPosition + 10]) decryptedData[readPosition:readPosition + 10])
readPosition += signatureLengthLength readPosition += signatureLengthLength
signature = decryptedData[readPosition:readPosition + signatureLength] signature = decryptedData[readPosition:readPosition + signatureLength]
try:
if not highlevelcrypto.verify(signedData, signature, publicSigningKey.encode('hex')): if highlevelcrypto.verify(signedDataOldMethod, signature, publicSigningKey.encode('hex')):
logger.info('ECDSA verify failed (within decryptAndCheckPubkeyPayload).') logger.info('ECDSA verify passed (within decryptAndCheckPubkeyPayload, old method)')
return 'failed' else:
logger.debug('ECDSA verify passed (within decryptAndCheckPubkeyPayload)') logger.info('ECDSA verify failed (within decryptAndCheckPubkeyPayload, old method)')
except Exception as err: # Try the protocol v3 signing method
logger.debug('ECDSA verify failed (within decryptAndCheckPubkeyPayload) %s' % err) if highlevelcrypto.verify(signedDataNewMethod, signature, publicSigningKey.encode('hex')):
logger.info('ECDSA verify passed (within decryptAndCheckPubkeyPayload, new method)')
else:
logger.info('ECDSA verify failed (within decryptAndCheckPubkeyPayload, new method)')
return 'failed' return 'failed'
sha = hashlib.new('sha512') sha = hashlib.new('sha512')
@ -526,44 +556,118 @@ def decryptAndCheckPubkeyPayload(payload, address):
# Although this pubkey object had the tag were were looking for and was # Although this pubkey object had the tag were were looking for and was
# encrypted with the correct encryption key, it doesn't contain the # encrypted with the correct encryption key, it doesn't contain the
# correct keys. Someone is either being malicious or using buggy software. # correct keys. Someone is either being malicious or using buggy software.
logger.info('Pubkey decryption was UNsuccessful due to RIPE mismatch. This shouldn\'t have happened.') logger.info('Pubkey decryption was UNsuccessful due to RIPE mismatch.')
return 'failed' return 'failed'
t = (ripe, addressVersion, signedData, int(time.time()), 'yes') # Everything checked out. Insert it into the pubkeys table.
logger.info('within decryptAndCheckPubkeyPayload, addressVersion: %s, streamNumber: %s \n\
ripe %s\n\
publicSigningKey in hex: %s\n\
publicEncryptionKey in hex: %s' % (addressVersion,
streamNumber,
ripe.encode('hex'),
publicSigningKey.encode('hex'),
publicEncryptionKey.encode('hex')
)
)
t = (ripe, addressVersion, signedDataOldMethod, int(time.time()), 'yes')
sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t) sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t)
return 'successful' return 'successful'
except varintDecodeError as e:
logger.info('Pubkey decryption was UNsuccessful due to a malformed varint.')
return 'failed'
except Exception as e:
logger.critical('Pubkey decryption was UNsuccessful because of an unhandled exception! This is definitely a bug! \n%s' % traceback.format_exc())
return 'failed'
Peer = collections.namedtuple('Peer', ['host', 'port']) Peer = collections.namedtuple('Peer', ['host', 'port'])
def checkAndShareMsgWithPeers(data): def checkAndShareObjectWithPeers(data):
"""
Function is called after either receiving an object off of the wire
or after receiving one as ackdata.
Returns the length of time that we should reserve to process this message
if we are receiving it off of the wire.
"""
# Let us check to make sure that the proof of work is sufficient. # Let us check to make sure that the proof of work is sufficient.
if not isProofOfWorkSufficient(data): if not isProofOfWorkSufficient(data):
logger.debug('Proof of work in msg message insufficient.') logger.info('Proof of work is insufficient.')
return return 0
readPosition = 8 endOfLifeTime, = unpack('>Q', data[8:16])
embeddedTime, = unpack('>I', data[readPosition:readPosition + 4]) if endOfLifeTime - int(time.time()) > 28 * 24 * 60 * 60 + 10800: # The TTL may not be larger than 28 days + 3 hours of wiggle room
logger.info('This object\'s End of Life time is too far in the future. Ignoring it. Time is %s' % endOfLifeTime)
# This section is used for the transition from 32 bit time to 64 bit return 0
# time in the protocol. if endOfLifeTime - int(time.time()) < - 3600: # The EOL time was more than an hour ago. That's too much.
if embeddedTime == 0: logger.info('This object\'s End of Life time was more than an hour ago. Ignoring the object. Time is %s' % endOfLifeTime)
embeddedTime, = unpack('>Q', data[readPosition:readPosition + 8]) return 0
readPosition += 8 intObjectType, = unpack('>I', data[16:20])
try:
if intObjectType == 0:
_checkAndShareGetpubkeyWithPeers(data)
return 0.1
elif intObjectType == 1:
_checkAndSharePubkeyWithPeers(data)
return 0.1
elif intObjectType == 2:
_checkAndShareMsgWithPeers(data)
return 0.6
elif intObjectType == 3:
_checkAndShareBroadcastWithPeers(data)
return 0.6
else: else:
readPosition += 4 _checkAndShareUndefinedObjectWithPeers(data)
return 0.6
except varintDecodeError as e:
logger.debug("There was a problem with a varint while checking to see whether it was appropriate to share an object with peers. Some details: %s" % e)
except Exception as e:
logger.critical('There was a problem while checking to see whether it was appropriate to share an object with peers. This is definitely a bug! \n%s' % traceback.format_exc())
return 0
if embeddedTime > (int(time.time()) + 10800):
logger.debug('The embedded time in this msg message is more than three hours in the future. That doesn\'t make sense. Ignoring message.') def _checkAndShareUndefinedObjectWithPeers(data):
return embeddedTime, = unpack('>Q', data[8:16])
if embeddedTime < (int(time.time()) - maximumAgeOfAnObjectThatIAmWillingToAccept): readPosition = 20 # bypass nonce, time, and object type
logger.debug('The embedded time in this msg message is too old. Ignoring message.') objectVersion, objectVersionLength = decodeVarint(
return
streamNumberAsClaimedByMsg, streamNumberAsClaimedByMsgLength = decodeVarint(
data[readPosition:readPosition + 9]) data[readPosition:readPosition + 9])
if not streamNumberAsClaimedByMsg in streamsInWhichIAmParticipating: readPosition += objectVersionLength
logger.debug('The streamNumber %s isn\'t one we are interested in.' % streamNumberAsClaimedByMsg) streamNumber, streamNumberLength = decodeVarint(
data[readPosition:readPosition + 9])
if not streamNumber in streamsInWhichIAmParticipating:
logger.debug('The streamNumber %s isn\'t one we are interested in.' % streamNumber)
return return
readPosition += streamNumberAsClaimedByMsgLength
inventoryHash = calculateInventoryHash(data)
shared.numberOfInventoryLookupsPerformed += 1
inventoryLock.acquire()
if inventoryHash in inventory:
logger.debug('We have already received this undefined object. Ignoring.')
inventoryLock.release()
return
elif isInSqlInventory(inventoryHash):
logger.debug('We have already received this undefined object (it is stored on disk in the SQL inventory). Ignoring it.')
inventoryLock.release()
return
objectType, = unpack('>I', data[16:20])
inventory[inventoryHash] = (
objectType, streamNumber, data, embeddedTime,'')
inventorySets[streamNumber].add(inventoryHash)
inventoryLock.release()
logger.debug('advertising inv with hash: %s' % inventoryHash.encode('hex'))
broadcastToSendDataQueues((streamNumber, 'advertiseobject', inventoryHash))
def _checkAndShareMsgWithPeers(data):
embeddedTime, = unpack('>Q', data[8:16])
readPosition = 20 # bypass nonce, time, and object type
streamNumber, streamNumberLength = decodeVarint(
data[readPosition:readPosition + 9])
if not streamNumber in streamsInWhichIAmParticipating:
logger.debug('The streamNumber %s isn\'t one we are interested in.' % streamNumber)
return
readPosition += streamNumberLength
inventoryHash = calculateInventoryHash(data) inventoryHash = calculateInventoryHash(data)
shared.numberOfInventoryLookupsPerformed += 1 shared.numberOfInventoryLookupsPerformed += 1
inventoryLock.acquire() inventoryLock.acquire()
@ -576,13 +680,13 @@ def checkAndShareMsgWithPeers(data):
inventoryLock.release() inventoryLock.release()
return return
# This msg message is valid. Let's let our peers know about it. # This msg message is valid. Let's let our peers know about it.
objectType = 'msg' objectType = 2
inventory[inventoryHash] = ( inventory[inventoryHash] = (
objectType, streamNumberAsClaimedByMsg, data, embeddedTime,'') objectType, streamNumber, data, embeddedTime,'')
inventorySets[streamNumberAsClaimedByMsg].add(inventoryHash) inventorySets[streamNumber].add(inventoryHash)
inventoryLock.release() inventoryLock.release()
logger.debug('advertising inv with hash: %s' % inventoryHash.encode('hex')) logger.debug('advertising inv with hash: %s' % inventoryHash.encode('hex'))
broadcastToSendDataQueues((streamNumberAsClaimedByMsg, 'advertiseobject', inventoryHash)) broadcastToSendDataQueues((streamNumber, 'advertiseobject', inventoryHash))
# Now let's enqueue it to be processed ourselves. # Now let's enqueue it to be processed ourselves.
# If we already have too much data in the queue to be processed, just sleep for now. # If we already have too much data in the queue to be processed, just sleep for now.
@ -592,30 +696,14 @@ def checkAndShareMsgWithPeers(data):
shared.objectProcessorQueueSize += len(data) shared.objectProcessorQueueSize += len(data)
objectProcessorQueue.put((objectType,data)) objectProcessorQueue.put((objectType,data))
def checkAndSharegetpubkeyWithPeers(data): def _checkAndShareGetpubkeyWithPeers(data):
if not isProofOfWorkSufficient(data): if len(data) < 42:
logger.debug('Proof of work in getpubkey message insufficient.') logger.info('getpubkey message doesn\'t contain enough data. Ignoring.')
return
if len(data) < 34:
logger.debug('getpubkey message doesn\'t contain enough data. Ignoring.')
return
readPosition = 8 # bypass the nonce
embeddedTime, = unpack('>I', data[readPosition:readPosition + 4])
# This section is used for the transition from 32 bit time to 64 bit
# time in the protocol.
if embeddedTime == 0:
embeddedTime, = unpack('>Q', data[readPosition:readPosition + 8])
readPosition += 8
else:
readPosition += 4
if embeddedTime > int(time.time()) + 10800:
logger.debug('The time in this getpubkey message is too new. Ignoring it. Time: %s' % embeddedTime)
return
if embeddedTime < int(time.time()) - maximumAgeOfAnObjectThatIAmWillingToAccept:
logger.debug('The time in this getpubkey message is too old. Ignoring it. Time: %s' % embeddedTime)
return return
if len(data) > 200:
logger.info('getpubkey is abnormally long. Sanity check failed. Ignoring object.')
embeddedTime, = unpack('>Q', data[8:16])
readPosition = 20 # bypass the nonce, time, and object type
requestedAddressVersionNumber, addressVersionLength = decodeVarint( requestedAddressVersionNumber, addressVersionLength = decodeVarint(
data[readPosition:readPosition + 10]) data[readPosition:readPosition + 10])
readPosition += addressVersionLength readPosition += addressVersionLength
@ -638,7 +726,7 @@ def checkAndSharegetpubkeyWithPeers(data):
inventoryLock.release() inventoryLock.release()
return return
objectType = 'getpubkey' objectType = 0
inventory[inventoryHash] = ( inventory[inventoryHash] = (
objectType, streamNumber, data, embeddedTime,'') objectType, streamNumber, data, embeddedTime,'')
inventorySets[streamNumber].add(inventoryHash) inventorySets[streamNumber].add(inventoryHash)
@ -655,31 +743,11 @@ def checkAndSharegetpubkeyWithPeers(data):
shared.objectProcessorQueueSize += len(data) shared.objectProcessorQueueSize += len(data)
objectProcessorQueue.put((objectType,data)) objectProcessorQueue.put((objectType,data))
def checkAndSharePubkeyWithPeers(data): def _checkAndSharePubkeyWithPeers(data):
if len(data) < 146 or len(data) > 420: # sanity check if len(data) < 146 or len(data) > 440: # sanity check
return
# Let us make sure that the proof of work is sufficient.
if not isProofOfWorkSufficient(data):
logger.debug('Proof of work in pubkey message insufficient.')
return
readPosition = 8 # for the nonce
embeddedTime, = unpack('>I', data[readPosition:readPosition + 4])
# This section is used for the transition from 32 bit time to 64 bit
# time in the protocol.
if embeddedTime == 0:
embeddedTime, = unpack('>Q', data[readPosition:readPosition + 8])
readPosition += 8
else:
readPosition += 4
if embeddedTime < int(time.time()) - lengthOfTimeToHoldOnToAllPubkeys:
logger.debug('The embedded time in this pubkey message is too old. Ignoring. Embedded time is: %s' % embeddedTime)
return
if embeddedTime > int(time.time()) + 10800:
logger.debug('The embedded time in this pubkey message more than several hours in the future. This is irrational. Ignoring message.')
return return
embeddedTime, = unpack('>Q', data[8:16])
readPosition = 20 # bypass the nonce, time, and object type
addressVersion, varintLength = decodeVarint( addressVersion, varintLength = decodeVarint(
data[readPosition:readPosition + 10]) data[readPosition:readPosition + 10])
readPosition += varintLength readPosition += varintLength
@ -706,7 +774,7 @@ def checkAndSharePubkeyWithPeers(data):
logger.debug('We have already received this pubkey (it is stored on disk in the SQL inventory). Ignoring it.') logger.debug('We have already received this pubkey (it is stored on disk in the SQL inventory). Ignoring it.')
inventoryLock.release() inventoryLock.release()
return return
objectType = 'pubkey' objectType = 1
inventory[inventoryHash] = ( inventory[inventoryHash] = (
objectType, streamNumber, data, embeddedTime, tag) objectType, streamNumber, data, embeddedTime, tag)
inventorySets[streamNumber].add(inventoryHash) inventorySets[streamNumber].add(inventoryHash)
@ -725,31 +793,12 @@ def checkAndSharePubkeyWithPeers(data):
objectProcessorQueue.put((objectType,data)) objectProcessorQueue.put((objectType,data))
def checkAndShareBroadcastWithPeers(data): def _checkAndShareBroadcastWithPeers(data):
# Let us verify that the proof of work is sufficient.
if not isProofOfWorkSufficient(data):
logger.debug('Proof of work in broadcast message insufficient.')
return
readPosition = 8 # bypass the nonce
embeddedTime, = unpack('>I', data[readPosition:readPosition + 4])
# This section is used for the transition from 32 bit time to 64 bit
# time in the protocol.
if embeddedTime == 0:
embeddedTime, = unpack('>Q', data[readPosition:readPosition + 8])
readPosition += 8
else:
readPosition += 4
if embeddedTime > (int(time.time()) + 10800): # prevent funny business
logger.debug('The embedded time in this broadcast message is more than three hours in the future. That doesn\'t make sense. Ignoring message.')
return
if embeddedTime < (int(time.time()) - maximumAgeOfAnObjectThatIAmWillingToAccept):
logger.debug('The embedded time in this broadcast message is too old. Ignoring message.')
return
if len(data) < 180: if len(data) < 180:
logger.debug('The payload length of this broadcast packet is unreasonably low. Someone is probably trying funny business. Ignoring message.') logger.debug('The payload length of this broadcast packet is unreasonably low. Someone is probably trying funny business. Ignoring message.')
return return
embeddedTime, = unpack('>Q', data[8:16])
readPosition = 20 # bypass the nonce, time, and object type
broadcastVersion, broadcastVersionLength = decodeVarint( broadcastVersion, broadcastVersionLength = decodeVarint(
data[readPosition:readPosition + 10]) data[readPosition:readPosition + 10])
readPosition += broadcastVersionLength readPosition += broadcastVersionLength
@ -775,7 +824,7 @@ def checkAndShareBroadcastWithPeers(data):
inventoryLock.release() inventoryLock.release()
return return
# It is valid. Let's let our peers know about it. # It is valid. Let's let our peers know about it.
objectType = 'broadcast' objectType = 3
inventory[inventoryHash] = ( inventory[inventoryHash] = (
objectType, streamNumber, data, embeddedTime, tag) objectType, streamNumber, data, embeddedTime, tag)
inventorySets[streamNumber].add(inventoryHash) inventorySets[streamNumber].add(inventoryHash)
@ -793,5 +842,5 @@ def checkAndShareBroadcastWithPeers(data):
objectProcessorQueue.put((objectType,data)) objectProcessorQueue.put((objectType,data))
helper_startup.loadConfig() #helper_startup.loadConfig()
from debug import logger from debug import logger