diff --git a/Makefile b/Makefile index 07e8b7bc..b6e6faa5 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ APP=pybitmessage -VERSION=0.3.5 +VERSION=0.4.0 RELEASE=1 ARCH_TYPE=`uname -m` PREFIX?=/usr/local diff --git a/arch.sh b/arch.sh index 795ec69d..42daefdc 100755 --- a/arch.sh +++ b/arch.sh @@ -2,8 +2,8 @@ GIT_APP=PyBitmessage APP=pybitmessage -PREV_VERSION=0.3.5 -VERSION=0.3.5 +PREV_VERSION=0.4.0 +VERSION=0.4.0 RELEASE=1 ARCH_TYPE=any CURRDIR=`pwd` diff --git a/archpackage/PKGBUILD b/archpackage/PKGBUILD index fd13b92a..455180c3 100644 --- a/archpackage/PKGBUILD +++ b/archpackage/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Bob Mottram (4096 bits) pkgname=pybitmessage -pkgver=0.3.5 +pkgver=0.4.0 pkgrel=1 pkgdesc="Bitmessage is a P2P communications protocol used to send encrypted messages to another person or to many subscribers. It is decentralized and trustless, meaning that you need-not inherently trust any entities like root certificate authorities. It uses strong authentication which means that the sender of a message cannot be spoofed, and it aims to hide "non-content" data, like the sender and receiver of messages, from passive eavesdroppers like those running warrantless wiretapping programs." arch=('any') diff --git a/debian.sh b/debian.sh index ea46acaf..b8fa4cf6 100755 --- a/debian.sh +++ b/debian.sh @@ -1,8 +1,8 @@ #!/bin/bash APP=pybitmessage -PREV_VERSION=0.3.5 -VERSION=0.3.5 +PREV_VERSION=0.4.0 +VERSION=0.4.0 RELEASE=1 ARCH_TYPE=all DIR=${APP}-${VERSION} diff --git a/ebuild.sh b/ebuild.sh index ed7f1903..c13186e4 100755 --- a/ebuild.sh +++ b/ebuild.sh @@ -1,8 +1,8 @@ #!/bin/bash APP=pybitmessage -PREV_VERSION=0.3.5 -VERSION=0.3.5 +PREV_VERSION=0.4.0 +VERSION=0.4.0 RELEASE=1 SOURCEDIR=. ARCH_TYPE=`uname -m` diff --git a/generate.sh b/generate.sh index 4154fff7..376c1926 100755 --- a/generate.sh +++ b/generate.sh @@ -4,7 +4,7 @@ rm -f Makefile rpmpackage/*.spec -packagemonkey -n "PyBitmessage" --version "0.3.5" --dir "." -l "mit" \ +packagemonkey -n "PyBitmessage" --version "0.4.0" --dir "." -l "mit" \ -e "Bob Mottram (4096 bits) " \ --brief "Send encrypted messages" \ --desc "Bitmessage is a P2P communications protocol used to send " \ diff --git a/puppy.sh b/puppy.sh index 5f2fcae0..b9e30c04 100755 --- a/puppy.sh +++ b/puppy.sh @@ -1,8 +1,8 @@ #!/bin/bash APP=pybitmessage -PREV_VERSION=0.3.5 -VERSION=0.3.5 +PREV_VERSION=0.4.0 +VERSION=0.4.0 RELEASE=1 BUILDDIR=~/petbuild CURRDIR=`pwd` diff --git a/rpm.sh b/rpm.sh index 09064280..893cae97 100755 --- a/rpm.sh +++ b/rpm.sh @@ -1,8 +1,8 @@ #!/bin/bash APP=pybitmessage -PREV_VERSION=0.3.5 -VERSION=0.3.5 +PREV_VERSION=0.4.0 +VERSION=0.4.0 RELEASE=1 SOURCEDIR=. ARCH_TYPE=`uname -m` diff --git a/rpmpackage/pybitmessage.spec b/rpmpackage/pybitmessage.spec index 3af43532..00012be9 100644 --- a/rpmpackage/pybitmessage.spec +++ b/rpmpackage/pybitmessage.spec @@ -1,5 +1,5 @@ Name: pybitmessage -Version: 0.3.5 +Version: 0.4.0 Release: 1%{?dist} Summary: Send encrypted messages License: MIT diff --git a/slack.sh b/slack.sh index 112488c0..07987fc2 100755 --- a/slack.sh +++ b/slack.sh @@ -1,8 +1,8 @@ #!/bin/bash APP=pybitmessage -PREV_VERSION=0.3.5 -VERSION=0.3.5 +PREV_VERSION=0.4.0 +VERSION=0.4.0 RELEASE=1 ARCH_TYPE=`uname -m` BUILDDIR=~/slackbuild diff --git a/src/addresses.py b/src/addresses.py index 1db2105d..5b092ca5 100644 --- a/src/addresses.py +++ b/src/addresses.py @@ -204,7 +204,6 @@ def decodeAddress(address): else: x00string = '\x00' * (20 - len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4])) return status,addressVersionNumber,streamNumber,x00string+data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4] - def addBMIfNotPresent(address): address = str(address).strip() @@ -213,56 +212,6 @@ def addBMIfNotPresent(address): else: return address -def addressStream(address): - #returns the stream number of an address or False if there is a problem with the address. - - #check for the BM- at the front of the address. If it isn't there, this address might be for a different version of Bitmessage - if address[:3] != 'BM-': - status = 'missingbm' - return False - #here we take off the BM- - integer = decodeBase58(address[3:]) - #after converting to hex, the string will be prepended with a 0x and appended with a L - hexdata = hex(integer)[2:-1] - - if len(hexdata) % 2 != 0: - hexdata = '0' + hexdata - - #print 'hexdata', hexdata - - data = hexdata.decode('hex') - checksum = data[-4:] - - sha = hashlib.new('sha512') - sha.update(data[:-4]) - currentHash = sha.digest() - #print 'sha after first hashing: ', sha.hexdigest() - sha = hashlib.new('sha512') - sha.update(currentHash) - #print 'sha after second hashing: ', sha.hexdigest() - - if checksum != sha.digest()[0:4]: - print 'checksum failed' - status = 'checksumfailed' - return False - #else: - # print 'checksum PASSED' - - addressVersionNumber, bytesUsedByVersionNumber = decodeVarint(data[:9]) - #print 'addressVersionNumber', addressVersionNumber - #print 'bytesUsedByVersionNumber', bytesUsedByVersionNumber - - if addressVersionNumber < 1: - print 'cannot decode version address version numbers this high' - status = 'versiontoohigh' - return False - - streamNumber, bytesUsedByStreamNumber = decodeVarint(data[bytesUsedByVersionNumber:9+bytesUsedByVersionNumber]) - #print streamNumber - status = 'success' - return streamNumber - - if __name__ == "__main__": print 'Let us make an address from scratch. Suppose we generate two random 32 byte values and call the first one the signing key and the second one the encryption key:' privateSigningKey = '93d0b61371a54b53df143b954035d612f8efa8a3ed1cf842c2186bfd8f876665' diff --git a/src/api_client.py b/src/api_client.py index e07258d5..6dc0c7b0 100644 --- a/src/api_client.py +++ b/src/api_client.py @@ -31,12 +31,12 @@ print 'Uncomment the next two lines to create a new random address with a slight print 'Uncomment these next four lines to create new deterministic addresses.' #passphrase = 'asdfasdfqwser'.encode('base64') -#jsonDeterministicAddresses = api.createDeterministicAddresses(passphrase, 2, 3, 1, False) +#jsonDeterministicAddresses = api.createDeterministicAddresses(passphrase, 2, 4, 1, False) #print jsonDeterministicAddresses #print json.loads(jsonDeterministicAddresses) #print 'Uncomment this next line to print the first deterministic address that would be generated with the given passphrase. This will Not add it to the Bitmessage interface or the keys.dat file.' -#print api.getDeterministicAddress('asdfasdfqwser'.encode('base64'),3,1) +#print api.getDeterministicAddress('asdfasdfqwser'.encode('base64'),4,1) #print 'Uncomment this line to subscribe to an address. (You must use your own address, this one is invalid).' #print api.addSubscription('2D94G5d8yp237GGqAheoecBYpdehdT3dha','test sub'.encode('base64')) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 413ae481..4d3f02cd 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -182,7 +182,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): elif method == 'statusBar': message, = params shared.UISignalQueue.put(('updateStatusBar', message)) - elif method == 'listAddresses': + elif method == 'listAddresses' or method == 'listAddresses2': data = '{"addresses":[' configSections = shared.config.sections() for addressInKeysFile in configSections: @@ -196,7 +196,10 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): chan = shared.config.getboolean(addressInKeysFile, 'chan') else: chan = False - data += json.dumps({'label': shared.config.get(addressInKeysFile, 'label'), 'address': addressInKeysFile, 'stream': + label = shared.config.get(addressInKeysFile, 'label') + if method == 'listAddresses2': + label = label.encode('base64') + data += json.dumps({'label': label, 'address': addressInKeysFile, 'stream': streamNumber, 'enabled': shared.config.getboolean(addressInKeysFile, 'enabled'), 'chan': chan}, indent=4, separators=(',', ': ')) data += ']}' return data @@ -276,7 +279,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): shared.apiAddressGeneratorReturnQueue.queue.clear() streamNumberForAddress = 1 shared.addressGeneratorQueue.put(( - 'createRandomAddress', 3, streamNumberForAddress, label, 1, "", eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes)) + 'createRandomAddress', 4, streamNumberForAddress, label, 1, "", eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes)) return shared.apiAddressGeneratorReturnQueue.get() elif method == 'createDeterministicAddresses': if len(params) == 0: @@ -341,9 +344,9 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): raise APIError(23, 'Bool expected in eighteenByteRipe, saw %s instead' % type(eighteenByteRipe)) passphrase = self._decode(passphrase, "base64") if addressVersionNumber == 0: # 0 means "just use the proper addressVersionNumber" - addressVersionNumber = 3 - if addressVersionNumber != 3: - raise APIError(2,'The address version number currently must be 3 (or 0 which means auto-select). ' + addressVersionNumber + ' isn\'t supported.') + addressVersionNumber = 4 + if addressVersionNumber != 3 and addressVersionNumber != 4: + raise APIError(2,'The address version number currently must be 3, 4, or 0 (which means auto-select). ' + addressVersionNumber + ' isn\'t supported.') if streamNumber == 0: # 0 means "just use the most available stream" streamNumber = 1 if streamNumber != 1: @@ -374,8 +377,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): if len(passphrase) == 0: raise APIError(1, 'The specified passphrase is blank.') passphrase = self._decode(passphrase, "base64") - if addressVersionNumber != 3: - raise APIError(2, 'The address version number currently must be 3. ' + addressVersionNumber + ' isn\'t supported.') + if addressVersionNumber != 3 and addressVersionNumber != 4: + raise APIError(2, 'The address version number currently must be 3 or 4. ' + addressVersionNumber + ' isn\'t supported.') if streamNumber != 1: raise APIError(3, ' The stream number must be 1. Others aren\'t supported.') shared.apiAddressGeneratorReturnQueue.queue.clear() @@ -776,7 +779,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): inventoryHash = calculateInventoryHash(encryptedPayload) objectType = 'msg' shared.inventory[inventoryHash] = ( - objectType, toStreamNumber, encryptedPayload, int(time.time())) + objectType, toStreamNumber, encryptedPayload, int(time.time()),'') shared.inventorySets[toStreamNumber].add(inventoryHash) with shared.printLock: print 'Broadcasting inv for msg(API disseminatePreEncryptedMsg command):', inventoryHash.encode('hex') @@ -812,14 +815,15 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): pubkeyStreamNumber = decodeVarint(payload[pubkeyReadPosition:pubkeyReadPosition+10])[0] inventoryHash = calculateInventoryHash(payload) objectType = 'pubkey' + #todo: support v4 pubkeys shared.inventory[inventoryHash] = ( - objectType, pubkeyStreamNumber, payload, int(time.time())) + objectType, pubkeyStreamNumber, payload, int(time.time()),'') shared.inventorySets[pubkeyStreamNumber].add(inventoryHash) with shared.printLock: print 'broadcasting inv within API command disseminatePubkey with hash:', inventoryHash.encode('hex') shared.broadcastToSendDataQueues(( streamNumber, 'advertiseobject', inventoryHash)) - elif method == 'getMessageDataByDestinationHash': + elif method == 'getMessageDataByDestinationHash' or method == 'getMessageDataByDestinationTag': # Method will eventually be used by a particular Android app to # select relevant messages. Do not yet add this to the api # doc. @@ -827,24 +831,24 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): if len(params) != 1: raise APIError(0, 'I need 1 parameter!') requestedHash, = params - if len(requestedHash) != 40: - raise APIError(19, 'The length of hash should be 20 bytes (encoded in hex thus 40 characters).') + if len(requestedHash) != 32: + raise APIError(19, 'The length of hash should be 32 bytes (encoded in hex thus 64 characters).') requestedHash = self._decode(requestedHash, "hex") # This is not a particularly commonly used API function. Before we # use it we'll need to fill out a field in our inventory database # which is blank by default (first20bytesofencryptedmessage). queryreturn = sqlQuery( - '''SELECT hash, payload FROM inventory WHERE first20bytesofencryptedmessage = '' and objecttype = 'msg' ; ''') + '''SELECT hash, payload FROM inventory WHERE tag = '' and objecttype = 'msg' ; ''') with SqlBulkExecute() as sql: for row in queryreturn: hash, payload = row readPosition = 16 # Nonce length + time length readPosition += decodeVarint(payload[readPosition:readPosition+10])[1] # Stream Number length - t = (payload[readPosition:readPosition+20],hash) - sql.execute('''UPDATE inventory SET first20bytesofencryptedmessage=? WHERE hash=?; ''', *t) + t = (payload[readPosition:readPosition+32],hash) + sql.execute('''UPDATE inventory SET tag=? WHERE hash=?; ''', *t) - queryreturn = sqlQuery('''SELECT payload FROM inventory WHERE first20bytesofencryptedmessage = ?''', + queryreturn = sqlQuery('''SELECT payload FROM inventory WHERE tag = ?''', requestedHash) data = '{"receivedMessageDatas":[' for row in queryreturn: @@ -877,7 +881,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): networkStatus = 'connectedButHaveNotReceivedIncomingConnections' else: networkStatus = 'connectedAndReceivingIncomingConnections' - return json.dumps({'networkConnections':len(shared.connectedHostsList),'numberOfMessagesProcessed':shared.numberOfMessagesProcessed, 'numberOfBroadcastsProcessed':shared.numberOfBroadcastsProcessed, 'numberOfPubkeysProcessed':shared.numberOfPubkeysProcessed, 'networkStatus':networkStatus}, indent=4, separators=(',', ': ')) + return json.dumps({'networkConnections':len(shared.connectedHostsList),'numberOfMessagesProcessed':shared.numberOfMessagesProcessed, 'numberOfBroadcastsProcessed':shared.numberOfBroadcastsProcessed, 'numberOfPubkeysProcessed':shared.numberOfPubkeysProcessed, 'networkStatus':networkStatus, 'softwareName':'PyBitmessage','softwareVersion':shared.softwareVersion}, indent=4, separators=(',', ': ')) else: raise APIError(20, 'Invalid method: %s' % method) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 54e10adf..8cbea0f9 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -334,7 +334,7 @@ class MyForm(QtGui.QMainWindow): newItem.setTextColor(QtGui.QColor(137, 04, 177)) # magenta self.ui.tableWidgetYourIdentities.setItem(0, 1, newItem) newItem = QtGui.QTableWidgetItem(str( - addressStream(addressInKeysFile))) + decodeAddress(addressInKeysFile)[2])) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) if not isEnabled: @@ -1133,8 +1133,15 @@ class MyForm(QtGui.QMainWindow): else: streamNumberForAddress = int( self.regenerateAddressesDialogInstance.ui.lineEditStreamNumber.text()) - addressVersionNumber = int( - self.regenerateAddressesDialogInstance.ui.lineEditAddressVersionNumber.text()) + try: + addressVersionNumber = int( + self.regenerateAddressesDialogInstance.ui.lineEditAddressVersionNumber.text()) + except: + QMessageBox.about(self, _translate("MainWindow", "Bad address version number"), _translate( + "MainWindow", "Your address version number must be a number: either 3 or 4.")) + if addressVersionNumber < 3 or addressVersionNumber > 4: + QMessageBox.about(self, _translate("MainWindow", "Bad address version number"), _translate( + "MainWindow", "Your address version number must be either 3 or 4.")) # self.addressGenerator = addressGenerator() # self.addressGenerator.setup(addressVersionNumber,streamNumberForAddress,"unused address",self.regenerateAddressesDialogInstance.ui.spinBoxNumberOfAddressesToMake.value(),self.regenerateAddressesDialogInstance.ui.lineEditPassphrase.text().toUtf8(),self.regenerateAddressesDialogInstance.ui.checkBoxEighteenByteRipe.isChecked()) # QtCore.QObject.connect(self.addressGenerator, SIGNAL("writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.writeNewAddressToTable) @@ -1153,7 +1160,7 @@ class MyForm(QtGui.QMainWindow): "MainWindow", "You didn't enter a chan name.")) return shared.apiAddressGeneratorReturnQueue.queue.clear() - shared.addressGeneratorQueue.put(('createChan', 3, 1, self.str_chan + ' ' + str(self.newChanDialogInstance.ui.lineEditChanNameCreate.text().toUtf8()), self.newChanDialogInstance.ui.lineEditChanNameCreate.text().toUtf8())) + shared.addressGeneratorQueue.put(('createChan', 4, 1, self.str_chan + ' ' + str(self.newChanDialogInstance.ui.lineEditChanNameCreate.text().toUtf8()), self.newChanDialogInstance.ui.lineEditChanNameCreate.text().toUtf8())) addressGeneratorReturnValue = shared.apiAddressGeneratorReturnQueue.get() print 'addressGeneratorReturnValue', addressGeneratorReturnValue if len(addressGeneratorReturnValue) == 0: @@ -1566,7 +1573,7 @@ class MyForm(QtGui.QMainWindow): continue except: pass - if addressVersionNumber > 3 or addressVersionNumber <= 1: + if addressVersionNumber > 4 or addressVersionNumber <= 1: QMessageBox.about(self, _translate("MainWindow", "Address version number"), _translate( "MainWindow", "Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version.").arg(toAddress).arg(str(addressVersionNumber))) continue @@ -2200,9 +2207,9 @@ class MyForm(QtGui.QMainWindow): else: # User selected 'Use the same stream as an existing # address.' - streamNumberForAddress = addressStream( - self.dialog.ui.comboBoxExisting.currentText()) - shared.addressGeneratorQueue.put(('createRandomAddress', 3, streamNumberForAddress, str( + streamNumberForAddress = decodeAddress( + self.dialog.ui.comboBoxExisting.currentText())[2] + shared.addressGeneratorQueue.put(('createRandomAddress', 4, streamNumberForAddress, str( self.dialog.ui.newaddresslabel.text().toUtf8()), 1, "", self.dialog.ui.checkBoxEighteenByteRipe.isChecked())) else: if self.dialog.ui.lineEditPassphrase.text() != self.dialog.ui.lineEditPassphraseAgain.text(): @@ -2213,7 +2220,7 @@ class MyForm(QtGui.QMainWindow): "MainWindow", "Choose a passphrase"), _translate("MainWindow", "You really do need a passphrase.")) else: streamNumberForAddress = 1 # this will eventually have to be replaced by logic to determine the most available stream number. - shared.addressGeneratorQueue.put(('createDeterministicAddresses', 3, streamNumberForAddress, "unused deterministic address", self.dialog.ui.spinBoxNumberOfAddressesToMake.value( + shared.addressGeneratorQueue.put(('createDeterministicAddresses', 4, streamNumberForAddress, "unused deterministic address", self.dialog.ui.spinBoxNumberOfAddressesToMake.value( ), self.dialog.ui.lineEditPassphrase.text().toUtf8(), self.dialog.ui.checkBoxEighteenByteRipe.isChecked())) else: print 'new address dialog box rejected' diff --git a/src/bitmessageqt/newaddressdialog.py b/src/bitmessageqt/newaddressdialog.py index f9bf9203..afe6fa2d 100644 --- a/src/bitmessageqt/newaddressdialog.py +++ b/src/bitmessageqt/newaddressdialog.py @@ -2,8 +2,8 @@ # Form implementation generated from reading ui file 'newaddressdialog.ui' # -# Created: Thu Jun 13 20:12:21 2013 -# by: PyQt4 UI code generator 4.10.1 +# Created: Sun Sep 15 23:53:31 2013 +# by: PyQt4 UI code generator 4.10.2 # # WARNING! All changes made in this file will be lost! @@ -178,7 +178,7 @@ class Ui_NewAddressDialog(object): self.radioButtonDeterministicAddress.setText(_translate("NewAddressDialog", "Use a passphrase to make addresses", None)) self.checkBoxEighteenByteRipe.setText(_translate("NewAddressDialog", "Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter", None)) self.groupBoxDeterministic.setTitle(_translate("NewAddressDialog", "Make deterministic addresses", None)) - self.label_9.setText(_translate("NewAddressDialog", "Address version number: 3", None)) + self.label_9.setText(_translate("NewAddressDialog", "Address version number: 4", None)) self.label_8.setText(_translate("NewAddressDialog", "In addition to your passphrase, you must remember these numbers:", None)) self.label_6.setText(_translate("NewAddressDialog", "Passphrase", None)) self.label_11.setText(_translate("NewAddressDialog", "Number of addresses to make based on your passphrase:", None)) @@ -191,13 +191,3 @@ class Ui_NewAddressDialog(object): self.radioButtonExisting.setText(_translate("NewAddressDialog", "Use the same stream as an existing address", None)) self.label_4.setText(_translate("NewAddressDialog", "(saves you some bandwidth and processing power)", None)) - -if __name__ == "__main__": - import sys - app = QtGui.QApplication(sys.argv) - NewAddressDialog = QtGui.QDialog() - ui = Ui_NewAddressDialog() - ui.setupUi(NewAddressDialog) - NewAddressDialog.show() - sys.exit(app.exec_()) - diff --git a/src/bitmessageqt/newaddressdialog.ui b/src/bitmessageqt/newaddressdialog.ui index 72fc39ec..a9eda5c3 100644 --- a/src/bitmessageqt/newaddressdialog.ui +++ b/src/bitmessageqt/newaddressdialog.ui @@ -99,7 +99,7 @@ The 'Random Number' option is selected by default but deterministic addresses ha - Address version number: 3 + Address version number: 4 diff --git a/src/bitmessageqt/regenerateaddresses.py b/src/bitmessageqt/regenerateaddresses.py index dfcd9135..7129b632 100644 --- a/src/bitmessageqt/regenerateaddresses.py +++ b/src/bitmessageqt/regenerateaddresses.py @@ -2,8 +2,8 @@ # Form implementation generated from reading ui file 'regenerateaddresses.ui' # -# Created: Tue Apr 30 12:21:16 2013 -# by: PyQt4 UI code generator 4.9.4 +# Created: Sun Sep 15 23:50:23 2013 +# by: PyQt4 UI code generator 4.10.2 # # WARNING! All changes made in this file will be lost! @@ -12,7 +12,16 @@ from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: - _fromUtf8 = lambda s: s + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) class Ui_regenerateAddressesDialog(object): def setupUi(self, regenerateAddressesDialog): @@ -56,13 +65,14 @@ class Ui_regenerateAddressesDialog(object): self.label_2.setObjectName(_fromUtf8("label_2")) self.gridLayout.addWidget(self.label_2, 4, 0, 1, 1) self.lineEditAddressVersionNumber = QtGui.QLineEdit(self.groupBox) - self.lineEditAddressVersionNumber.setEnabled(False) + self.lineEditAddressVersionNumber.setEnabled(True) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.lineEditAddressVersionNumber.sizePolicy().hasHeightForWidth()) self.lineEditAddressVersionNumber.setSizePolicy(sizePolicy) self.lineEditAddressVersionNumber.setMaximumSize(QtCore.QSize(31, 16777215)) + self.lineEditAddressVersionNumber.setText(_fromUtf8("")) self.lineEditAddressVersionNumber.setObjectName(_fromUtf8("lineEditAddressVersionNumber")) self.gridLayout.addWidget(self.lineEditAddressVersionNumber, 4, 1, 1, 1) spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) @@ -101,15 +111,14 @@ class Ui_regenerateAddressesDialog(object): QtCore.QMetaObject.connectSlotsByName(regenerateAddressesDialog) def retranslateUi(self, regenerateAddressesDialog): - regenerateAddressesDialog.setWindowTitle(QtGui.QApplication.translate("regenerateAddressesDialog", "Regenerate Existing Addresses", None, QtGui.QApplication.UnicodeUTF8)) - self.groupBox.setTitle(QtGui.QApplication.translate("regenerateAddressesDialog", "Regenerate existing addresses", None, QtGui.QApplication.UnicodeUTF8)) - self.label_6.setText(QtGui.QApplication.translate("regenerateAddressesDialog", "Passphrase", None, QtGui.QApplication.UnicodeUTF8)) - self.label_11.setText(QtGui.QApplication.translate("regenerateAddressesDialog", "Number of addresses to make based on your passphrase:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_2.setText(QtGui.QApplication.translate("regenerateAddressesDialog", "Address version Number:", None, QtGui.QApplication.UnicodeUTF8)) - self.lineEditAddressVersionNumber.setText(QtGui.QApplication.translate("regenerateAddressesDialog", "3", None, QtGui.QApplication.UnicodeUTF8)) - self.label_3.setText(QtGui.QApplication.translate("regenerateAddressesDialog", "Stream number:", None, QtGui.QApplication.UnicodeUTF8)) - self.lineEditStreamNumber.setText(QtGui.QApplication.translate("regenerateAddressesDialog", "1", None, QtGui.QApplication.UnicodeUTF8)) - self.checkBoxEighteenByteRipe.setText(QtGui.QApplication.translate("regenerateAddressesDialog", "Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter", None, QtGui.QApplication.UnicodeUTF8)) - self.label_4.setText(QtGui.QApplication.translate("regenerateAddressesDialog", "You must check (or not check) this box just like you did (or didn\'t) when you made your addresses the first time.", None, QtGui.QApplication.UnicodeUTF8)) - self.label.setText(QtGui.QApplication.translate("regenerateAddressesDialog", "If you have previously made deterministic addresses but lost them due to an accident (like hard drive failure), you can regenerate them here. If you used the random number generator to make your addresses then this form will be of no use to you.", None, QtGui.QApplication.UnicodeUTF8)) + regenerateAddressesDialog.setWindowTitle(_translate("regenerateAddressesDialog", "Regenerate Existing Addresses", None)) + self.groupBox.setTitle(_translate("regenerateAddressesDialog", "Regenerate existing addresses", None)) + self.label_6.setText(_translate("regenerateAddressesDialog", "Passphrase", None)) + self.label_11.setText(_translate("regenerateAddressesDialog", "Number of addresses to make based on your passphrase:", None)) + self.label_2.setText(_translate("regenerateAddressesDialog", "Address version number:", None)) + self.label_3.setText(_translate("regenerateAddressesDialog", "Stream number:", None)) + self.lineEditStreamNumber.setText(_translate("regenerateAddressesDialog", "1", None)) + self.checkBoxEighteenByteRipe.setText(_translate("regenerateAddressesDialog", "Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter", None)) + self.label_4.setText(_translate("regenerateAddressesDialog", "You must check (or not check) this box just like you did (or didn\'t) when you made your addresses the first time.", None)) + self.label.setText(_translate("regenerateAddressesDialog", "If you have previously made deterministic addresses but lost them due to an accident (like hard drive failure), you can regenerate them here. If you used the random number generator to make your addresses then this form will be of no use to you.", None)) diff --git a/src/bitmessageqt/regenerateaddresses.ui b/src/bitmessageqt/regenerateaddresses.ui index 89cdf901..e58a20e9 100644 --- a/src/bitmessageqt/regenerateaddresses.ui +++ b/src/bitmessageqt/regenerateaddresses.ui @@ -86,14 +86,14 @@ - Address version Number: + Address version number: - false + true @@ -108,7 +108,7 @@ - 3 + diff --git a/src/build_osx.py b/src/build_osx.py index de7dc851..f9ab2c03 100644 --- a/src/build_osx.py +++ b/src/build_osx.py @@ -1,7 +1,7 @@ from setuptools import setup name = "Bitmessage" -version = "0.3.5" +version = "0.4.0" mainscript = ["bitmessagemain.py"] setup( diff --git a/src/class_addressGenerator.py b/src/class_addressGenerator.py index 54582541..c6e99a4b 100644 --- a/src/class_addressGenerator.py +++ b/src/class_addressGenerator.py @@ -25,20 +25,38 @@ class addressGenerator(threading.Thread): command, addressVersionNumber, streamNumber, label, deterministicPassphrase = queueValue eighteenByteRipe = False numberOfAddressesToMake = 1 + numberOfNullBytesDemandedOnFrontOfRipeHash = 1 elif queueValue[0] == 'joinChan': command, chanAddress, label, deterministicPassphrase = queueValue eighteenByteRipe = False addressVersionNumber = decodeAddress(chanAddress)[1] streamNumber = decodeAddress(chanAddress)[2] numberOfAddressesToMake = 1 + numberOfNullBytesDemandedOnFrontOfRipeHash = 1 elif len(queueValue) == 7: command, addressVersionNumber, streamNumber, label, numberOfAddressesToMake, deterministicPassphrase, eighteenByteRipe = queueValue + try: + numberOfNullBytesDemandedOnFrontOfRipeHash = shared.config.getint( + 'bitmessagesettings', 'numberofnullbytesonaddress') + except: + if eighteenByteRipe: + numberOfNullBytesDemandedOnFrontOfRipeHash = 2 + else: + numberOfNullBytesDemandedOnFrontOfRipeHash = 1 # the default elif len(queueValue) == 9: command, addressVersionNumber, streamNumber, label, numberOfAddressesToMake, deterministicPassphrase, eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes = queueValue + try: + numberOfNullBytesDemandedOnFrontOfRipeHash = shared.config.getint( + 'bitmessagesettings', 'numberofnullbytesonaddress') + except: + if eighteenByteRipe: + numberOfNullBytesDemandedOnFrontOfRipeHash = 2 + else: + numberOfNullBytesDemandedOnFrontOfRipeHash = 1 # the default else: sys.stderr.write( 'Programming error: A structure with the wrong number of values was passed into the addressGeneratorQueue. Here is the queueValue: %s\n' % repr(queueValue)) - if addressVersionNumber < 3 or addressVersionNumber > 3: + if addressVersionNumber < 3 or addressVersionNumber > 4: sys.stderr.write( 'Program error: For some reason the address generator queue has been given a request to create at least one version %s address which it cannot do.\n' % addressVersionNumber) if nonceTrialsPerByte == 0: @@ -51,26 +69,121 @@ class addressGenerator(threading.Thread): 'bitmessagesettings', 'defaultpayloadlengthextrabytes') if payloadLengthExtraBytes < shared.networkDefaultPayloadLengthExtraBytes: payloadLengthExtraBytes = shared.networkDefaultPayloadLengthExtraBytes - if addressVersionNumber == 3: # currently the only one supported. - if command == 'createRandomAddress': + if command == 'createRandomAddress': + shared.UISignalQueue.put(( + 'updateStatusBar', tr.translateText("MainWindow", "Generating one new address"))) + # This next section is a little bit strange. We're going to generate keys over and over until we + # find one that starts with either \x00 or \x00\x00. Then when we pack them into a Bitmessage address, + # we won't store the \x00 or \x00\x00 bytes thus making the + # address shorter. + startTime = time.time() + numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix = 0 + potentialPrivSigningKey = OpenSSL.rand(32) + potentialPubSigningKey = pointMult(potentialPrivSigningKey) + while True: + numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix += 1 + potentialPrivEncryptionKey = OpenSSL.rand(32) + potentialPubEncryptionKey = pointMult( + potentialPrivEncryptionKey) + # print 'potentialPubSigningKey', potentialPubSigningKey.encode('hex') + # print 'potentialPubEncryptionKey', + # potentialPubEncryptionKey.encode('hex') + ripe = hashlib.new('ripemd160') + sha = hashlib.new('sha512') + sha.update( + potentialPubSigningKey + potentialPubEncryptionKey) + ripe.update(sha.digest()) + # print 'potential ripe.digest', + # ripe.digest().encode('hex') + if ripe.digest()[:numberOfNullBytesDemandedOnFrontOfRipeHash] == '\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash: + break + print 'Generated address with ripe digest:', ripe.digest().encode('hex') + print 'Address generator calculated', numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix, 'addresses at', numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix / (time.time() - startTime), 'addresses per second before finding one with the correct ripe-prefix.' + address = encodeAddress(addressVersionNumber, streamNumber, ripe.digest()) + + # An excellent way for us to store our keys is in Wallet Import Format. Let us convert now. + # https://en.bitcoin.it/wiki/Wallet_import_format + privSigningKey = '\x80' + potentialPrivSigningKey + checksum = hashlib.sha256(hashlib.sha256( + privSigningKey).digest()).digest()[0:4] + privSigningKeyWIF = arithmetic.changebase( + privSigningKey + checksum, 256, 58) + # print 'privSigningKeyWIF',privSigningKeyWIF + + privEncryptionKey = '\x80' + potentialPrivEncryptionKey + checksum = hashlib.sha256(hashlib.sha256( + privEncryptionKey).digest()).digest()[0:4] + privEncryptionKeyWIF = arithmetic.changebase( + privEncryptionKey + checksum, 256, 58) + # print 'privEncryptionKeyWIF',privEncryptionKeyWIF + + shared.config.add_section(address) + shared.config.set(address, 'label', label) + shared.config.set(address, 'enabled', 'true') + shared.config.set(address, 'decoy', 'false') + shared.config.set(address, 'noncetrialsperbyte', str( + nonceTrialsPerByte)) + shared.config.set(address, 'payloadlengthextrabytes', str( + payloadLengthExtraBytes)) + shared.config.set( + address, 'privSigningKey', privSigningKeyWIF) + shared.config.set( + address, 'privEncryptionKey', privEncryptionKeyWIF) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + + # The API and the join and create Chan functionality + # both need information back from the address generator. + shared.apiAddressGeneratorReturnQueue.put(address) + + shared.UISignalQueue.put(( + 'updateStatusBar', tr.translateText("MainWindow", "Done generating address. Doing work necessary to broadcast it..."))) + shared.UISignalQueue.put(('writeNewAddressToTable', ( + label, address, streamNumber))) + shared.reloadMyAddressHashes() + if addressVersionNumber == 3: + shared.workerQueue.put(( + 'sendOutOrStoreMyV3Pubkey', ripe.digest())) + elif addressVersionNumber == 4: + shared.workerQueue.put(( + 'sendOutOrStoreMyV4Pubkey', address)) + + elif command == 'createDeterministicAddresses' or command == 'getDeterministicAddress' or command == 'createChan' or command == 'joinChan': + if len(deterministicPassphrase) == 0: + sys.stderr.write( + 'WARNING: You are creating deterministic address(es) using a blank passphrase. Bitmessage will do it but it is rather stupid.') + if command == 'createDeterministicAddresses': + statusbar = 'Generating ' + str( + numberOfAddressesToMake) + ' new addresses.' shared.UISignalQueue.put(( - 'updateStatusBar', tr.translateText("MainWindow", "Generating one new address"))) + 'updateStatusBar', statusbar)) + signingKeyNonce = 0 + encryptionKeyNonce = 1 + listOfNewAddressesToSendOutThroughTheAPI = [ + ] # We fill out this list no matter what although we only need it if we end up passing the info to the API. + + for i in range(numberOfAddressesToMake): # This next section is a little bit strange. We're going to generate keys over and over until we - # find one that starts with either \x00 or \x00\x00. Then when we pack them into a Bitmessage address, - # we won't store the \x00 or \x00\x00 bytes thus making the - # address shorter. + # find one that has a RIPEMD hash that starts with either \x00 or \x00\x00. Then when we pack them + # into a Bitmessage address, we won't store the \x00 or + # \x00\x00 bytes thus making the address shorter. startTime = time.time() numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix = 0 - potentialPrivSigningKey = OpenSSL.rand(32) - potentialPubSigningKey = pointMult(potentialPrivSigningKey) while True: numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix += 1 - potentialPrivEncryptionKey = OpenSSL.rand(32) + potentialPrivSigningKey = hashlib.sha512( + deterministicPassphrase + encodeVarint(signingKeyNonce)).digest()[:32] + potentialPrivEncryptionKey = hashlib.sha512( + deterministicPassphrase + encodeVarint(encryptionKeyNonce)).digest()[:32] + potentialPubSigningKey = pointMult( + potentialPrivSigningKey) potentialPubEncryptionKey = pointMult( potentialPrivEncryptionKey) # print 'potentialPubSigningKey', potentialPubSigningKey.encode('hex') # print 'potentialPubEncryptionKey', # potentialPubEncryptionKey.encode('hex') + signingKeyNonce += 2 + encryptionKeyNonce += 2 ripe = hashlib.new('ripemd160') sha = hashlib.new('sha512') sha.update( @@ -78,193 +191,97 @@ class addressGenerator(threading.Thread): ripe.update(sha.digest()) # print 'potential ripe.digest', # ripe.digest().encode('hex') - if eighteenByteRipe: - if ripe.digest()[:2] == '\x00\x00': - break - else: - if ripe.digest()[:1] == '\x00': - break - print 'Generated address with ripe digest:', ripe.digest().encode('hex') - print 'Address generator calculated', numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix, 'addresses at', numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix / (time.time() - startTime), 'addresses per second before finding one with the correct ripe-prefix.' - address = encodeAddress(3, streamNumber, ripe.digest()) + if ripe.digest()[:numberOfNullBytesDemandedOnFrontOfRipeHash] == '\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash: + break - # An excellent way for us to store our keys is in Wallet Import Format. Let us convert now. - # https://en.bitcoin.it/wiki/Wallet_import_format - privSigningKey = '\x80' + potentialPrivSigningKey - checksum = hashlib.sha256(hashlib.sha256( - privSigningKey).digest()).digest()[0:4] - privSigningKeyWIF = arithmetic.changebase( - privSigningKey + checksum, 256, 58) - # print 'privSigningKeyWIF',privSigningKeyWIF + print 'ripe.digest', ripe.digest().encode('hex') + print 'Address generator calculated', numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix, 'addresses at', numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix / (time.time() - startTime), 'keys per second.' + address = encodeAddress(addressVersionNumber, streamNumber, ripe.digest()) - privEncryptionKey = '\x80' + potentialPrivEncryptionKey - checksum = hashlib.sha256(hashlib.sha256( - privEncryptionKey).digest()).digest()[0:4] - privEncryptionKeyWIF = arithmetic.changebase( - privEncryptionKey + checksum, 256, 58) - # print 'privEncryptionKeyWIF',privEncryptionKeyWIF - - shared.config.add_section(address) - shared.config.set(address, 'label', label) - shared.config.set(address, 'enabled', 'true') - shared.config.set(address, 'decoy', 'false') - shared.config.set(address, 'noncetrialsperbyte', str( - nonceTrialsPerByte)) - shared.config.set(address, 'payloadlengthextrabytes', str( - payloadLengthExtraBytes)) - shared.config.set( - address, 'privSigningKey', privSigningKeyWIF) - shared.config.set( - address, 'privEncryptionKey', privEncryptionKeyWIF) - with open(shared.appdata + 'keys.dat', 'wb') as configfile: - shared.config.write(configfile) - - # The API and the join and create Chan functionality - # both need information back from the address generator. - shared.apiAddressGeneratorReturnQueue.put(address) - - shared.UISignalQueue.put(( - 'updateStatusBar', tr.translateText("MainWindow", "Done generating address. Doing work necessary to broadcast it..."))) - shared.UISignalQueue.put(('writeNewAddressToTable', ( - label, address, streamNumber))) - shared.reloadMyAddressHashes() - shared.workerQueue.put(( - 'sendOutOrStoreMyV3Pubkey', ripe.digest())) - - elif command == 'createDeterministicAddresses' or command == 'getDeterministicAddress' or command == 'createChan' or command == 'joinChan': - if len(deterministicPassphrase) == 0: - sys.stderr.write( - 'WARNING: You are creating deterministic address(es) using a blank passphrase. Bitmessage will do it but it is rather stupid.') - if command == 'createDeterministicAddresses': - statusbar = 'Generating ' + str( - numberOfAddressesToMake) + ' new addresses.' - shared.UISignalQueue.put(( - 'updateStatusBar', statusbar)) - signingKeyNonce = 0 - encryptionKeyNonce = 1 - listOfNewAddressesToSendOutThroughTheAPI = [ - ] # We fill out this list no matter what although we only need it if we end up passing the info to the API. - - for i in range(numberOfAddressesToMake): - # This next section is a little bit strange. We're going to generate keys over and over until we - # find one that has a RIPEMD hash that starts with either \x00 or \x00\x00. Then when we pack them - # into a Bitmessage address, we won't store the \x00 or - # \x00\x00 bytes thus making the address shorter. - startTime = time.time() - numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix = 0 - while True: - numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix += 1 - potentialPrivSigningKey = hashlib.sha512( - deterministicPassphrase + encodeVarint(signingKeyNonce)).digest()[:32] - potentialPrivEncryptionKey = hashlib.sha512( - deterministicPassphrase + encodeVarint(encryptionKeyNonce)).digest()[:32] - potentialPubSigningKey = pointMult( - potentialPrivSigningKey) - potentialPubEncryptionKey = pointMult( - potentialPrivEncryptionKey) - # print 'potentialPubSigningKey', potentialPubSigningKey.encode('hex') - # print 'potentialPubEncryptionKey', - # potentialPubEncryptionKey.encode('hex') - signingKeyNonce += 2 - encryptionKeyNonce += 2 - ripe = hashlib.new('ripemd160') - sha = hashlib.new('sha512') - sha.update( - potentialPubSigningKey + potentialPubEncryptionKey) - ripe.update(sha.digest()) - # print 'potential ripe.digest', - # ripe.digest().encode('hex') - if eighteenByteRipe: - if ripe.digest()[:2] == '\x00\x00': - break - else: - if ripe.digest()[:1] == '\x00': - break - - print 'ripe.digest', ripe.digest().encode('hex') - print 'Address generator calculated', numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix, 'addresses at', numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix / (time.time() - startTime), 'keys per second.' - address = encodeAddress(3, streamNumber, ripe.digest()) - - saveAddressToDisk = True - # If we are joining an existing chan, let us check to make sure it matches the provided Bitmessage address - if command == 'joinChan': - if address != chanAddress: - shared.apiAddressGeneratorReturnQueue.put('chan name does not match address') - saveAddressToDisk = False - if command == 'getDeterministicAddress': + saveAddressToDisk = True + # If we are joining an existing chan, let us check to make sure it matches the provided Bitmessage address + if command == 'joinChan': + if address != chanAddress: + shared.apiAddressGeneratorReturnQueue.put('chan name does not match address') saveAddressToDisk = False + if command == 'getDeterministicAddress': + saveAddressToDisk = False - if saveAddressToDisk: - # An excellent way for us to store our keys is in Wallet Import Format. Let us convert now. - # https://en.bitcoin.it/wiki/Wallet_import_format - privSigningKey = '\x80' + potentialPrivSigningKey - checksum = hashlib.sha256(hashlib.sha256( - privSigningKey).digest()).digest()[0:4] - privSigningKeyWIF = arithmetic.changebase( - privSigningKey + checksum, 256, 58) + if saveAddressToDisk: + # An excellent way for us to store our keys is in Wallet Import Format. Let us convert now. + # https://en.bitcoin.it/wiki/Wallet_import_format + privSigningKey = '\x80' + potentialPrivSigningKey + checksum = hashlib.sha256(hashlib.sha256( + privSigningKey).digest()).digest()[0:4] + privSigningKeyWIF = arithmetic.changebase( + privSigningKey + checksum, 256, 58) - privEncryptionKey = '\x80' + \ - potentialPrivEncryptionKey - checksum = hashlib.sha256(hashlib.sha256( - privEncryptionKey).digest()).digest()[0:4] - privEncryptionKeyWIF = arithmetic.changebase( - privEncryptionKey + checksum, 256, 58) + privEncryptionKey = '\x80' + \ + potentialPrivEncryptionKey + checksum = hashlib.sha256(hashlib.sha256( + privEncryptionKey).digest()).digest()[0:4] + privEncryptionKeyWIF = arithmetic.changebase( + privEncryptionKey + checksum, 256, 58) - addressAlreadyExists = False - try: - shared.config.add_section(address) - except: - print address, 'already exists. Not adding it again.' - addressAlreadyExists = True - if not addressAlreadyExists: - print 'label:', label - shared.config.set(address, 'label', label) - shared.config.set(address, 'enabled', 'true') - shared.config.set(address, 'decoy', 'false') - if command == 'joinChan' or command == 'createChan': - shared.config.set(address, 'chan', 'true') - shared.config.set(address, 'noncetrialsperbyte', str( - nonceTrialsPerByte)) - shared.config.set(address, 'payloadlengthextrabytes', str( - payloadLengthExtraBytes)) - shared.config.set( - address, 'privSigningKey', privSigningKeyWIF) - shared.config.set( - address, 'privEncryptionKey', privEncryptionKeyWIF) - with open(shared.appdata + 'keys.dat', 'wb') as configfile: - shared.config.write(configfile) + addressAlreadyExists = False + try: + shared.config.add_section(address) + except: + print address, 'already exists. Not adding it again.' + addressAlreadyExists = True + if not addressAlreadyExists: + print 'label:', label + shared.config.set(address, 'label', label) + shared.config.set(address, 'enabled', 'true') + shared.config.set(address, 'decoy', 'false') + if command == 'joinChan' or command == 'createChan': + shared.config.set(address, 'chan', 'true') + shared.config.set(address, 'noncetrialsperbyte', str( + nonceTrialsPerByte)) + shared.config.set(address, 'payloadlengthextrabytes', str( + payloadLengthExtraBytes)) + shared.config.set( + address, 'privSigningKey', privSigningKeyWIF) + shared.config.set( + address, 'privEncryptionKey', privEncryptionKeyWIF) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) - shared.UISignalQueue.put(('writeNewAddressToTable', ( - label, address, str(streamNumber)))) - listOfNewAddressesToSendOutThroughTheAPI.append( - address) - shared.myECCryptorObjects[ripe.digest()] = highlevelcrypto.makeCryptor( - potentialPrivEncryptionKey.encode('hex')) - shared.myAddressesByHash[ - ripe.digest()] = address + shared.UISignalQueue.put(('writeNewAddressToTable', ( + label, address, str(streamNumber)))) + listOfNewAddressesToSendOutThroughTheAPI.append( + address) + shared.myECCryptorObjects[ripe.digest()] = highlevelcrypto.makeCryptor( + potentialPrivEncryptionKey.encode('hex')) + shared.myAddressesByHash[ripe.digest()] = address + tag = hashlib.sha512(hashlib.sha512(encodeVarint( + addressVersionNumber) + encodeVarint(streamNumber) + ripe.digest()).digest()).digest()[32:] + shared.myAddressesByTag[tag] = address + if addressVersionNumber == 3: shared.workerQueue.put(( 'sendOutOrStoreMyV3Pubkey', ripe.digest())) # If this is a chan address, # the worker thread won't send out the pubkey over the network. + elif addressVersionNumber == 4: + shared.workerQueue.put(( + 'sendOutOrStoreMyV4Pubkey', address)) - # Done generating addresses. - if command == 'createDeterministicAddresses' or command == 'joinChan' or command == 'createChan': - shared.apiAddressGeneratorReturnQueue.put( - listOfNewAddressesToSendOutThroughTheAPI) - shared.UISignalQueue.put(( - 'updateStatusBar', tr.translateText("MainWindow", "Done generating address"))) - # shared.reloadMyAddressHashes() - elif command == 'getDeterministicAddress': - shared.apiAddressGeneratorReturnQueue.put(address) - #todo: return things to the API if createChan or joinChan assuming saveAddressToDisk - else: - raise Exception( - "Error in the addressGenerator thread. Thread was given a command it could not understand: " + command) + # Done generating addresses. + if command == 'createDeterministicAddresses' or command == 'joinChan' or command == 'createChan': + shared.apiAddressGeneratorReturnQueue.put( + listOfNewAddressesToSendOutThroughTheAPI) + shared.UISignalQueue.put(( + 'updateStatusBar', tr.translateText("MainWindow", "Done generating address"))) + # shared.reloadMyAddressHashes() + elif command == 'getDeterministicAddress': + shared.apiAddressGeneratorReturnQueue.put(address) + #todo: return things to the API if createChan or joinChan assuming saveAddressToDisk + else: + raise Exception( + "Error in the addressGenerator thread. Thread was given a command it could not understand: " + command) # Does an EC point multiplication; turns a private key into a public key. - - def pointMult(secret): # ctx = OpenSSL.BN_CTX_new() #This value proved to cause Seg Faults on # Linux. It turns out that it really didn't speed up EC_POINT_mul anyway. diff --git a/src/class_receiveDataThread.py b/src/class_receiveDataThread.py index 643185fa..f9fe403e 100644 --- a/src/class_receiveDataThread.py +++ b/src/class_receiveDataThread.py @@ -300,7 +300,7 @@ class receiveDataThread(threading.Thread): with shared.inventoryLock: for hash, storedValue in shared.inventory.items(): if hash not in self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware: - objectType, streamNumber, payload, receivedTime = storedValue + objectType, streamNumber, payload, receivedTime, tag = storedValue if streamNumber == self.streamNumber and receivedTime > int(time.time()) - shared.maximumAgeOfObjectsThatIAdvertiseToOthers: bigInvList[hash] = 0 numberOfObjectsInInvMessage = 0 @@ -391,7 +391,7 @@ class receiveDataThread(threading.Thread): # It is valid so far. Let's let our peers know about it. objectType = 'broadcast' shared.inventory[self.inventoryHash] = ( - objectType, self.streamNumber, data, embeddedTime) + objectType, self.streamNumber, data, embeddedTime,'') shared.inventorySets[self.streamNumber].add(self.inventoryHash) shared.inventoryLock.release() self.broadcastinv(self.inventoryHash) @@ -432,8 +432,8 @@ class receiveDataThread(threading.Thread): broadcastVersion, broadcastVersionLength = decodeVarint( data[readPosition:readPosition + 9]) readPosition += broadcastVersionLength - if broadcastVersion < 1 or broadcastVersion > 2: - print 'Cannot decode incoming broadcast versions higher than 2. Assuming the sender isn\'t being silly, you should upgrade Bitmessage because this message shall be ignored.' + if broadcastVersion < 1 or broadcastVersion > 3: + print '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.' return if broadcastVersion == 1: beginningOfPubkeyPosition = readPosition # used when we add the pubkey to our pubkey table @@ -502,6 +502,10 @@ class receiveDataThread(threading.Thread): print 'ECDSA verify failed', err return # verify passed + fromAddress = encodeAddress( + sendersAddressVersion, sendersStream, ripe.digest()) + with shared.printLock: + print 'fromAddress:', 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 @@ -515,16 +519,11 @@ class receiveDataThread(threading.Thread): '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' + '\xFF\xFF\xFF\xFF' + data[beginningOfPubkeyPosition:endOfPubkeyPosition], int(time.time()), 'yes') - # shared.workerQueue.put(('newpubkey',(sendersAddressVersion,sendersStream,ripe.digest()))) # 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.digest()) + self.possibleNewPubkey(ripe=ripe.digest()) - fromAddress = encodeAddress( - sendersAddressVersion, sendersStream, ripe.digest()) - with shared.printLock: - print 'fromAddress:', fromAddress if messageEncodingType == 2: subject, body = self.decodeType2Message(message) @@ -665,7 +664,7 @@ class receiveDataThread(threading.Thread): # 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.digest()) + self.possibleNewPubkey(ripe=ripe.digest()) fromAddress = encodeAddress( sendersAddressVersion, sendersStream, ripe.digest()) @@ -709,6 +708,150 @@ class receiveDataThread(threading.Thread): with shared.printLock: print 'Time spent processing this interesting broadcast:', time.time() - self.messageProcessingStartTime + if broadcastVersion == 3: + cleartextStreamNumber, cleartextStreamNumberLength = decodeVarint( + data[readPosition:readPosition + 10]) + readPosition += cleartextStreamNumberLength + embeddedTag = data[readPosition:readPosition+32] + readPosition += 32 + if embeddedTag not in shared.MyECSubscriptionCryptorObjects: + with shared.printLock: + print 'We\'re not interested in this broadcast.' + return + # We are interested in this broadcast because of its tag. + cryptorObject = shared.MyECSubscriptionCryptorObjects[embeddedTag] + try: + decryptedData = cryptorObject.decrypt(data[readPosition:]) + print 'EC decryption successful' + except Exception as err: + with shared.printLock: + print 'Broadcast version 3 decryption Unsuccessful.' + return + + signedBroadcastVersion, readPosition = decodeVarint( + decryptedData[:10]) + beginningOfPubkeyPosition = readPosition # used when we add the pubkey to our pubkey table + sendersAddressVersion, sendersAddressVersionLength = decodeVarint( + decryptedData[readPosition:readPosition + 9]) + if sendersAddressVersion < 4: + print '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.' + return + readPosition += sendersAddressVersionLength + sendersStream, sendersStreamLength = decodeVarint( + decryptedData[readPosition:readPosition + 9]) + if sendersStream != cleartextStreamNumber: + print 'The stream number outside of the encryption on which the POW was completed doesn\'t match the stream number inside the encryption. Ignoring broadcast.' + return + readPosition += sendersStreamLength + behaviorBitfield = decryptedData[readPosition:readPosition + 4] + readPosition += 4 + sendersPubSigningKey = '\x04' + \ + decryptedData[readPosition:readPosition + 64] + readPosition += 64 + sendersPubEncryptionKey = '\x04' + \ + decryptedData[readPosition:readPosition + 64] + readPosition += 64 + if sendersAddressVersion >= 3: + requiredAverageProofOfWorkNonceTrialsPerByte, varintLength = decodeVarint( + decryptedData[readPosition:readPosition + 10]) + readPosition += varintLength + print 'sender\'s requiredAverageProofOfWorkNonceTrialsPerByte is', requiredAverageProofOfWorkNonceTrialsPerByte + requiredPayloadLengthExtraBytes, varintLength = decodeVarint( + decryptedData[readPosition:readPosition + 10]) + readPosition += varintLength + print 'sender\'s requiredPayloadLengthExtraBytes is', requiredPayloadLengthExtraBytes + endOfPubkeyPosition = readPosition + + sha = hashlib.new('sha512') + sha.update(sendersPubSigningKey + sendersPubEncryptionKey) + ripeHasher = hashlib.new('ripemd160') + ripeHasher.update(sha.digest()) + calculatedRipe = ripeHasher.digest() + + calculatedTag = hashlib.sha512(hashlib.sha512(encodeVarint( + sendersAddressVersion) + encodeVarint(sendersStream) + calculatedRipe).digest()).digest()[32:] + if calculatedTag != embeddedTag: + print 'The tag and encryption key used to encrypt this message doesn\'t match the keys inbedded in the message itself. Ignoring message.' + return + messageEncodingType, messageEncodingTypeLength = decodeVarint( + decryptedData[readPosition:readPosition + 9]) + if messageEncodingType == 0: + return + readPosition += messageEncodingTypeLength + messageLength, messageLengthLength = decodeVarint( + decryptedData[readPosition:readPosition + 9]) + readPosition += messageLengthLength + message = decryptedData[readPosition:readPosition + messageLength] + readPosition += messageLength + readPositionAtBottomOfMessage = readPosition + signatureLength, signatureLengthLength = decodeVarint( + decryptedData[readPosition:readPosition + 9]) + readPosition += signatureLengthLength + signature = decryptedData[ + readPosition:readPosition + signatureLength] + try: + if not highlevelcrypto.verify(decryptedData[:readPositionAtBottomOfMessage], signature, sendersPubSigningKey.encode('hex')): + print 'ECDSA verify failed' + return + print 'ECDSA verify passed' + except Exception as err: + print 'ECDSA verify failed', err + return + # verify passed + + fromAddress = encodeAddress( + sendersAddressVersion, sendersStream, calculatedRipe) + with shared.printLock: + print 'fromAddress:', fromAddress + + # Let's store the public key in case we want to reply to this person. + sqlExecute( + '''INSERT INTO pubkeys VALUES (?,?,?,?)''', + calculatedRipe, + '\x00\x00\x00\x00\x00\x00\x00\x01' + decryptedData[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(address = fromAddress) + + if messageEncodingType == 2: + subject, body = self.decodeType2Message(message) + elif messageEncodingType == 1: + body = message + subject = '' + elif messageEncodingType == 0: + print 'messageEncodingType == 0. Doing nothing with the message.' + else: + body = 'Unknown encoding type.\n\n' + repr(message) + subject = '' + + toAddress = '[Broadcast subscribers]' + if messageEncodingType != 0: + + t = (self.inventoryHash, toAddress, fromAddress, subject, int( + time.time()), body, 'inbox', messageEncodingType, 0) + helper_inbox.insert(t) + + shared.UISignalQueue.put(('displayNewInboxMessage', ( + self.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 + with shared.printLock: + print 'Time spent processing this interesting broadcast:', time.time() - self.messageProcessingStartTime # We have received a msg message. def recmsg(self, data): @@ -755,7 +898,7 @@ class receiveDataThread(threading.Thread): # This msg message is valid. Let's let our peers know about it. objectType = 'msg' shared.inventory[self.inventoryHash] = ( - objectType, self.streamNumber, data, embeddedTime) + objectType, self.streamNumber, data, embeddedTime,'') shared.inventorySets[self.streamNumber].add(self.inventoryHash) shared.inventoryLock.release() self.broadcastinv(self.inventoryHash) @@ -846,7 +989,7 @@ class receiveDataThread(threading.Thread): if sendersAddressVersionNumber == 0: print 'Cannot understand sendersAddressVersionNumber = 0. Ignoring message.' return - if sendersAddressVersionNumber >= 4: + if sendersAddressVersionNumber > 4: print 'Sender\'s address version number', sendersAddressVersionNumber, 'not yet supported. Ignoring message.' return if len(decryptedData) < 170: @@ -921,21 +1064,32 @@ class receiveDataThread(threading.Thread): sha.update(pubSigningKey + pubEncryptionKey) ripe = hashlib.new('ripemd160') ripe.update(sha.digest()) - # Let's store the public key in case we want to reply to this - # person. - sqlExecute( - '''INSERT INTO pubkeys VALUES (?,?,?,?)''', - ripe.digest(), - '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' + '\xFF\xFF\xFF\xFF' + decryptedData[messageVersionLength:endOfThePublicKeyPosition], - int(time.time()), - 'yes') - # shared.workerQueue.put(('newpubkey',(sendersAddressVersionNumber,sendersStreamNumber,ripe.digest()))) - # 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.digest()) fromAddress = encodeAddress( sendersAddressVersionNumber, sendersStreamNumber, ripe.digest()) + # Let's store the public key in case we want to reply to this + # person. + if sendersAddressVersionNumber <= 3: + sqlExecute( + '''INSERT INTO pubkeys VALUES (?,?,?,?)''', + ripe.digest(), + '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' + '\xFF\xFF\xFF\xFF' + decryptedData[messageVersionLength:endOfThePublicKeyPosition], + 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()) + elif sendersAddressVersionNumber >= 4: + sqlExecute( + '''INSERT INTO pubkeys VALUES (?,?,?,?)''', + ripe.digest(), + '\x00\x00\x00\x00\x00\x00\x00\x01' + decryptedData[messageVersionLength:endOfThePublicKeyPosition], + 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(address = fromAddress) # If this message is bound for one of my version 3 addresses (or # higher), then we must check to make sure it meets our demanded # proof of work requirement. @@ -1087,23 +1241,41 @@ class receiveDataThread(threading.Thread): else: return '[' + mailingListName + '] ' + subject - def possibleNewPubkey(self, toRipe): - if toRipe in shared.neededPubkeys: - print 'We have been awaiting the arrival of this pubkey.' - del shared.neededPubkeys[toRipe] - sqlExecute( - '''UPDATE sent SET status='doingmsgpow' WHERE toripe=? AND (status='awaitingpubkey' or status='doingpubkeypow') and folder='sent' ''', - toRipe) - shared.workerQueue.put(('sendmessage', '')) - else: - with shared.printLock: - print 'We don\'t need this pub key. We didn\'t ask for it. Pubkey hash:', toRipe.encode('hex') - + # We have inserted a pubkey into our pubkey table which we received from a + # pubkey, msg, or broadcast message. It might be one that we have been + # waiting for. Let's check. + def possibleNewPubkey(self, ripe=None, address=None): + # For address versions <= 3, we wait on a key with the correct ripe hash + if ripe != None: + if ripe in shared.neededPubkeys: + print 'We have been awaiting the arrival of this pubkey.' + del shared.neededPubkeys[ripe] + sqlExecute( + '''UPDATE sent SET status='doingmsgpow' WHERE toripe=? AND (status='awaitingpubkey' or status='doingpubkeypow') and folder='sent' ''', + ripe) + shared.workerQueue.put(('sendmessage', '')) + else: + with shared.printLock: + print 'We don\'t need this pub key. We didn\'t ask for it. Pubkey hash:', ripe.encode('hex') + # For address versions >= 4, we wait on a pubkey with the correct tag. + # Let us create the tag from the address and see if we were waiting + # for it. + elif address != None: + status, addressVersion, streamNumber, ripe = decodeAddress(address) + tag = hashlib.sha512(hashlib.sha512(encodeVarint( + addressVersion) + encodeVarint(streamNumber) + ripe).digest()).digest()[32:] + if tag in shared.neededPubkeys: + print 'We have been awaiting the arrival of this pubkey.' + del shared.neededPubkeys[tag] + sqlExecute( + '''UPDATE sent SET status='doingmsgpow' WHERE toripe=? AND (status='awaitingpubkey' or status='doingpubkeypow') and folder='sent' ''', + ripe) + shared.workerQueue.put(('sendmessage', '')) # We have received a pubkey def recpubkey(self, data): self.pubkeyProcessingStartTime = time.time() - if len(data) < 146 or len(data) > 600: # sanity check + if len(data) < 146 or len(data) > 420: # sanity check return # We must check to make sure the proof of work is sufficient. if not self.isProofOfWorkSufficient(data): @@ -1140,6 +1312,11 @@ class receiveDataThread(threading.Thread): if self.streamNumber != streamNumber: print 'stream number embedded in this pubkey doesn\'t match our stream number. Ignoring.' return + if addressVersion >= 4: + tag = data[readPosition:readPosition + 32] + print 'tag in received pubkey is:', tag.encode('hex') + else: + tag = '' shared.numberOfInventoryLookupsPerformed += 1 inventoryHash = calculateInventoryHash(data) @@ -1154,7 +1331,7 @@ class receiveDataThread(threading.Thread): return objectType = 'pubkey' shared.inventory[inventoryHash] = ( - objectType, self.streamNumber, data, embeddedTime) + objectType, self.streamNumber, data, embeddedTime, tag) shared.inventorySets[self.streamNumber].add(inventoryHash) shared.inventoryLock.release() self.broadcastinv(inventoryHash) @@ -1164,7 +1341,7 @@ class receiveDataThread(threading.Thread): self.processpubkey(data) - lengthOfTimeWeShouldUseToProcessThisMessage = .2 + lengthOfTimeWeShouldUseToProcessThisMessage = .1 sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - \ (time.time() - self.pubkeyProcessingStartTime) if sleepTime > 0 and doTimingAttackMitigation: @@ -1197,7 +1374,7 @@ class receiveDataThread(threading.Thread): if addressVersion == 0: print '(Within processpubkey) addressVersion of 0 doesn\'t make sense.' return - if addressVersion > 3 or addressVersion == 1: + if addressVersion > 4 or addressVersion == 1: with shared.printLock: print 'This version of Bitmessage cannot handle version', addressVersion, 'addresses.' @@ -1242,7 +1419,7 @@ class receiveDataThread(threading.Thread): # This will also update the embeddedTime. sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?)''', *t) # shared.workerQueue.put(('newpubkey',(addressVersion,streamNumber,ripe))) - self.possibleNewPubkey(ripe) + self.possibleNewPubkey(ripe = ripe) if addressVersion == 3: if len(data) < 170: # sanity check. print '(within processpubkey) payloadLength less than 170. Sanity check failed.' @@ -1298,8 +1475,94 @@ class receiveDataThread(threading.Thread): t = (ripe, data, embeddedTime, 'no') # This will also update the embeddedTime. sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?)''', *t) - # shared.workerQueue.put(('newpubkey',(addressVersion,streamNumber,ripe))) - self.possibleNewPubkey(ripe) + self.possibleNewPubkey(ripe = ripe) + + if addressVersion == 4: + print 'length of v4 pubkey:', len(data) + if len(data) < 350: # sanity check. + print '(within processpubkey) payloadLength less than 350. Sanity check failed.' + return + signedData = data[8:readPosition] # Used only for v4 or higher pubkeys + tag = data[readPosition:readPosition + 32] + readPosition += 32 + encryptedData = data[readPosition:] + if tag not in shared.neededPubkeys: + with shared.printLock: + print 'We don\'t need this v4 pubkey. We didn\'t ask for it.' + return + + # Let us try to decrypt the pubkey + cryptorObject = shared.neededPubkeys[tag] + try: + decryptedData = cryptorObject.decrypt(encryptedData) + except: + # Someone must have encrypted some data with a different key + # but tagged it with a tag for which we are watching. + with shared.printLock: + print '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 sign 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')): + print 'ECDSA verify failed (within processpubkey)' + return + print 'ECDSA verify passed (within processpubkey)' + except Exception as err: + print 'ECDSA verify failed (within processpubkey)', 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:]: + with shared.printLock: + print 'Someone was trying to act malicious: tag doesn\'t match the keys in this pubkey message. Ignoring it.' + return + else: + print 'Tag successfully matches keys in pubkey message' # testing. Will remove soon. + + with shared.printLock: + print 'within recpubkey, addressVersion:', addressVersion, ', streamNumber:', streamNumber + print 'ripe', ripe.encode('hex') + print 'publicSigningKey in hex:', publicSigningKey.encode('hex') + print 'publicEncryptionKey in hex:', publicEncryptionKey.encode('hex') + + t = (ripe, signedData, embeddedTime, 'yes') + sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?)''', *t) + + 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 + # the messages that require it. + self.possibleNewPubkey(address = fromAddress) + # We have received a getpubkey message def recgetpubkey(self, data): @@ -1350,7 +1613,7 @@ class receiveDataThread(threading.Thread): objectType = 'getpubkey' shared.inventory[inventoryHash] = ( - objectType, self.streamNumber, data, embeddedTime) + objectType, self.streamNumber, data, embeddedTime,'') shared.inventorySets[self.streamNumber].add(inventoryHash) shared.inventoryLock.release() # This getpubkey request is valid so far. Forward to peers. @@ -1362,50 +1625,66 @@ class receiveDataThread(threading.Thread): elif requestedAddressVersionNumber == 1: print 'The requestedAddressVersionNumber of the pubkey request is 1 which isn\'t supported anymore. Ignoring it.' return - elif requestedAddressVersionNumber > 3: + elif requestedAddressVersionNumber > 4: print 'The requestedAddressVersionNumber of the pubkey request is too high. Can\'t understand. Ignoring it.' return - requestedHash = data[readPosition:readPosition + 20] - if len(requestedHash) != 20: - print 'The length of the requested hash is not 20 bytes. Something is wrong. Ignoring.' - return - with shared.printLock: - print 'the hash requested in this getpubkey request is:', requestedHash.encode('hex') - - if requestedHash in shared.myAddressesByHash: # if this address hash is one of mine - if decodeAddress(shared.myAddressesByHash[requestedHash])[1] != requestedAddressVersionNumber: - with shared.printLock: - sys.stderr.write( - '(Within the recgetpubkey function) Someone requested one of my pubkeys but the requestedAddressVersionNumber doesn\'t match my actual address version number. They shouldn\'t have done that. Ignoring.\n') + myAddress = '' + if requestedAddressVersionNumber <= 3 : + requestedHash = data[readPosition:readPosition + 20] + if len(requestedHash) != 20: + print 'The length of the requested hash is not 20 bytes. Something is wrong. Ignoring.' return - if shared.safeConfigGetBoolean(shared.myAddressesByHash[requestedHash], 'chan'): - with shared.printLock: - print 'Ignoring getpubkey request because it is for one of my chan addresses. The other party should already have the pubkey.' - return - try: - lastPubkeySendTime = int(shared.config.get( - shared.myAddressesByHash[requestedHash], 'lastpubkeysendtime')) - except: - lastPubkeySendTime = 0 - if lastPubkeySendTime > time.time() - shared.lengthOfTimeToHoldOnToAllPubkeys: # If the last time we sent our pubkey was more recent than 28 days ago... - with shared.printLock: - print 'Found getpubkey-requested-hash in my list of EC hashes BUT we already sent it recently. Ignoring request. The lastPubkeySendTime is:', lastPubkeySendTime - return - with shared.printLock: - print '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.' - if requestedAddressVersionNumber == 2: - shared.workerQueue.put(( - 'doPOWForMyV2Pubkey', requestedHash)) - elif requestedAddressVersionNumber == 3: - shared.workerQueue.put(( - 'sendOutOrStoreMyV3Pubkey', requestedHash)) - + print 'the hash requested in this getpubkey request is:', requestedHash.encode('hex') + if requestedHash in shared.myAddressesByHash: # if this address hash is one of mine + myAddress = shared.myAddressesByHash[requestedHash] + elif requestedAddressVersionNumber >= 4: + requestedTag = data[readPosition:readPosition + 32] + if len(requestedTag) != 32: + print 'The length of the requested tag is not 32 bytes. Something is wrong. Ignoring.' + return + with shared.printLock: + print 'the tag requested in this getpubkey request is:', requestedTag.encode('hex') + if requestedTag in shared.myAddressesByTag: - else: + myAddress = shared.myAddressesByTag[requestedTag] + + if myAddress == '': with shared.printLock: print 'This getpubkey request is not for any of my keys.' + return + + if decodeAddress(myAddress)[1] != requestedAddressVersionNumber: + with shared.printLock: + sys.stderr.write( + '(Within the recgetpubkey function) Someone requested one of my pubkeys but the requestedAddressVersionNumber doesn\'t match my actual address version number. They shouldn\'t have done that. Ignoring.\n') + return + if shared.safeConfigGetBoolean(myAddress, 'chan'): + with shared.printLock: + print 'Ignoring getpubkey request because it is for one of my chan addresses. The other party should already have the pubkey.' + return + try: + lastPubkeySendTime = int(shared.config.get( + myAddress, 'lastpubkeysendtime')) + except: + lastPubkeySendTime = 0 + if lastPubkeySendTime > time.time() - shared.lengthOfTimeToHoldOnToAllPubkeys: # If the last time we sent our pubkey was more recent than 28 days ago... + with shared.printLock: + print 'Found getpubkey-requested-item in my list of EC hashes BUT we already sent it recently. Ignoring request. The lastPubkeySendTime is:', lastPubkeySendTime + return + + with shared.printLock: + print '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.' + if requestedAddressVersionNumber == 2: + shared.workerQueue.put(( + 'doPOWForMyV2Pubkey', requestedHash)) + elif requestedAddressVersionNumber == 3: + shared.workerQueue.put(( + 'sendOutOrStoreMyV3Pubkey', requestedHash)) + elif requestedAddressVersionNumber == 4: + shared.workerQueue.put(( + 'sendOutOrStoreMyV4Pubkey', myAddress)) # We have received an inv message @@ -1498,7 +1777,7 @@ class receiveDataThread(threading.Thread): shared.numberOfInventoryLookupsPerformed += 1 shared.inventoryLock.acquire() if hash in shared.inventory: - objectType, streamNumber, payload, receivedTime = shared.inventory[ + objectType, streamNumber, payload, receivedTime, tag = shared.inventory[ hash] shared.inventoryLock.release() self.sendData(objectType, payload) diff --git a/src/class_singleCleaner.py b/src/class_singleCleaner.py index 44cb893c..84829e87 100644 --- a/src/class_singleCleaner.py +++ b/src/class_singleCleaner.py @@ -39,7 +39,7 @@ class singleCleaner(threading.Thread): with shared.inventoryLock: # If you use both the inventoryLock and the sqlLock, always use the inventoryLock OUTSIDE of the sqlLock. with SqlBulkExecute() as sql: for hash, storedValue in shared.inventory.items(): - objectType, streamNumber, payload, receivedTime = storedValue + objectType, streamNumber, payload, receivedTime, tag = storedValue if int(time.time()) - 3600 > receivedTime: sql.execute( '''INSERT INTO inventory VALUES (?,?,?,?,?,?)''', @@ -48,7 +48,7 @@ class singleCleaner(threading.Thread): streamNumber, payload, receivedTime, - '') + tag) del shared.inventory[hash] shared.UISignalQueue.put(('updateStatusBar', '')) shared.broadcastToSendDataQueues(( @@ -125,7 +125,7 @@ class singleCleaner(threading.Thread): shared.inventorySets[streamNumber].add(row[0]) with shared.inventoryLock: for hash, storedValue in shared.inventory.items(): - objectType, streamNumber, payload, receivedTime = storedValue + objectType, streamNumber, payload, receivedTime, tag = storedValue if streamNumber in shared.inventorySets: shared.inventorySets[streamNumber].add(hash) diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index d8990ede..8a66c125 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -24,10 +24,18 @@ class singleWorker(threading.Thread): def run(self): queryreturn = sqlQuery( - '''SELECT toripe FROM sent WHERE ((status='awaitingpubkey' OR status='doingpubkeypow') AND folder='sent')''') + '''SELECT toripe, toaddress FROM sent WHERE ((status='awaitingpubkey' OR status='doingpubkeypow') AND folder='sent')''') for row in queryreturn: - toripe, = row - shared.neededPubkeys[toripe] = 0 + toripe, toaddress = row + toStatus, toAddressVersionNumber, toStreamNumber, toRipe = decodeAddress(toaddress) + if toAddressVersionNumber <= 3 : + shared.neededPubkeys[toripe] = 0 + elif toAddressVersionNumber >= 4: + doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( + toAddressVersionNumber) + encodeVarint(toStreamNumber) + toRipe).digest()).digest() + privEncryptionKey = doubleHashOfAddressData[:32] # Note that this is the first half of the sha512 hash. + tag = doubleHashOfAddressData[32:] + shared.neededPubkeys[tag] = highlevelcrypto.makeCryptor(privEncryptionKey.encode('hex')) # We'll need this for when we receive a pubkey reply: it will be encrypted and we'll need to decrypt it. # Initialize the shared.ackdataForWhichImWatching data structure using data # from the sql database. @@ -64,23 +72,8 @@ class singleWorker(threading.Thread): self.doPOWForMyV2Pubkey(data) elif command == 'sendOutOrStoreMyV3Pubkey': self.sendOutOrStoreMyV3Pubkey(data) - """elif command == 'newpubkey': - toAddressVersion,toStreamNumber,toRipe = data - if toRipe in shared.neededPubkeys: - print 'We have been awaiting the arrival of this pubkey.' - del shared.neededPubkeys[toRipe] - t = (toRipe,) - shared.sqlLock.acquire() - shared.sqlSubmitQueue.put('''UPDATE sent SET status='doingmsgpow' WHERE toripe=? AND status='awaitingpubkey' and folder='sent' ''') - shared.sqlSubmitQueue.put(t) - shared.sqlReturnQueue.get() - shared.sqlSubmitQueue.put('commit') - shared.sqlLock.release() - self.sendMsg() - else: - with shared.printLock: - print 'We don\'t need this pub key. We didn\'t ask for it. Pubkey hash:', toRipe.encode('hex') - """ + elif command == 'sendOutOrStoreMyV4Pubkey': + self.sendOutOrStoreMyV4Pubkey(data) else: with shared.printLock: sys.stderr.write( @@ -139,18 +132,11 @@ class singleWorker(threading.Thread): trialValue, nonce = proofofwork.run(target, initialHash) print '(For pubkey message) Found proof of work', trialValue, 'Nonce:', nonce payload = pack('>Q', nonce) + payload - """t = (hash,payload,embeddedTime,'no') - shared.sqlLock.acquire() - shared.sqlSubmitQueue.put('''INSERT INTO pubkeys VALUES (?,?,?,?)''') - shared.sqlSubmitQueue.put(t) - queryreturn = shared.sqlReturnQueue.get() - shared.sqlSubmitQueue.put('commit') - shared.sqlLock.release()""" inventoryHash = calculateInventoryHash(payload) objectType = 'pubkey' shared.inventory[inventoryHash] = ( - objectType, streamNumber, payload, embeddedTime) + objectType, streamNumber, payload, embeddedTime,'') shared.inventorySets[streamNumber].add(inventoryHash) with shared.printLock: @@ -224,7 +210,7 @@ class singleWorker(threading.Thread): inventoryHash = calculateInventoryHash(payload) objectType = 'pubkey' shared.inventory[inventoryHash] = ( - objectType, streamNumber, payload, embeddedTime) + objectType, streamNumber, payload, embeddedTime,'') shared.inventorySets[streamNumber].add(inventoryHash) with shared.printLock: @@ -249,6 +235,106 @@ class singleWorker(threading.Thread): with open(shared.appdata + 'keys.dat', 'wb') as configfile: shared.config.write(configfile) + # If this isn't a chan address, this function assembles the pubkey data, + # does the necessary POW and sends it out. If it *is* a chan then it + # assembles the pubkey and stores is in the pubkey table so that we can + # send messages to "ourselves". + def sendOutOrStoreMyV4Pubkey(self, myAddress): + status, addressVersionNumber, streamNumber, hash = decodeAddress( + myAddress) + embeddedTime = int(time.time() + random.randrange( + -300, 300)) # the current time plus or minus five minutes + payload = pack('>Q', (embeddedTime)) + payload += encodeVarint(addressVersionNumber) # Address version number + payload += encodeVarint(streamNumber) + dataToStoreInOurPubkeysTable = payload # used if this is a chan. We'll add more data further down. + + dataToEncrypt = '\x00\x00\x00\x01' # bitfield of features supported by me (see the wiki). + + try: + privSigningKeyBase58 = shared.config.get( + myAddress, 'privsigningkey') + privEncryptionKeyBase58 = shared.config.get( + myAddress, 'privencryptionkey') + except Exception as err: + with shared.printLock: + sys.stderr.write( + 'Error within sendOutOrStoreMyV4Pubkey. Could not read the keys from the keys.dat file for a requested address. %s\n' % err) + return + + privSigningKeyHex = shared.decodeWalletImportFormat( + privSigningKeyBase58).encode('hex') + privEncryptionKeyHex = shared.decodeWalletImportFormat( + privEncryptionKeyBase58).encode('hex') + pubSigningKey = highlevelcrypto.privToPub( + privSigningKeyHex).decode('hex') + pubEncryptionKey = highlevelcrypto.privToPub( + privEncryptionKeyHex).decode('hex') + dataToEncrypt += pubSigningKey[1:] + dataToEncrypt += pubEncryptionKey[1:] + + dataToEncrypt += encodeVarint(shared.config.getint( + myAddress, 'noncetrialsperbyte')) + dataToEncrypt += encodeVarint(shared.config.getint( + myAddress, 'payloadlengthextrabytes')) + + dataToStoreInOurPubkeysTable += dataToEncrypt # dataToStoreInOurPubkeysTable is used if this is a chan + + signature = highlevelcrypto.sign(payload + dataToEncrypt, privSigningKeyHex) + dataToEncrypt += encodeVarint(len(signature)) + dataToEncrypt += signature + + if not shared.safeConfigGetBoolean(myAddress, 'chan'): + # Let us encrypt the necessary data. We will use a hash of the data + # contained in an address as a decryption key. This way in order to + # read the public keys in a pubkey message, a node must know the address + # first. We'll also tag, unencrypted, the pubkey with part of the hash + # so that nodes know which pubkey object to try to decrypt when they + # want to send a message. + doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( + addressVersionNumber) + encodeVarint(streamNumber) + hash).digest()).digest() + payload += doubleHashOfAddressData[32:] # the tag + privEncryptionKey = doubleHashOfAddressData[:32] + pubEncryptionKey = pointMult(privEncryptionKey) + payload += highlevelcrypto.encrypt( + dataToEncrypt, pubEncryptionKey.encode('hex')) + + # Do the POW for this pubkey message + target = 2 ** 64 / ((len(payload) + shared.networkDefaultPayloadLengthExtraBytes + + 8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) + print '(For pubkey message) Doing proof of work...' + initialHash = hashlib.sha512(payload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + print '(For pubkey message) Found proof of work', trialValue, 'Nonce:', nonce + + payload = pack('>Q', nonce) + payload + inventoryHash = calculateInventoryHash(payload) + objectType = 'pubkey' + shared.inventory[inventoryHash] = ( + objectType, streamNumber, payload, embeddedTime, doubleHashOfAddressData[32:]) + shared.inventorySets[streamNumber].add(inventoryHash) + + with shared.printLock: + print 'broadcasting inv with hash:', inventoryHash.encode('hex') + + shared.broadcastToSendDataQueues(( + streamNumber, 'advertiseobject', inventoryHash)) + shared.UISignalQueue.put(('updateStatusBar', '')) + # If this is a chan address then we won't send out the pubkey over the + # network but rather will only store it in our pubkeys table so that + # we can send messages to "ourselves". + if shared.safeConfigGetBoolean(myAddress, 'chan'): + + sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?)''', + hash, + dataToStoreInOurPubkeysTable, + embeddedTime, + 'yes') + shared.config.set( + myAddress, 'lastpubkeysendtime', str(int(time.time()))) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + def sendBroadcast(self): queryreturn = sqlQuery( '''SELECT fromaddress, subject, message, ackdata FROM sent WHERE status=? and folder='sent' ''', 'broadcastqueued') @@ -283,12 +369,26 @@ class singleWorker(threading.Thread): pubEncryptionKey = highlevelcrypto.privToPub( privEncryptionKeyHex).decode('hex') + print 'embedding pubEncryptionKey:', pubEncryptionKey.encode('hex') + payload = pack('>Q', (int(time.time()) + random.randrange( -300, 300))) # the current time plus or minus five minutes - payload += encodeVarint(2) # broadcast version + if addressVersionNumber <= 3: + payload += encodeVarint(2) # broadcast version + else: + payload += encodeVarint(3) # broadcast version payload += encodeVarint(streamNumber) + if addressVersionNumber >= 4: + doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( + addressVersionNumber) + encodeVarint(streamNumber) + ripe).digest()).digest() + payload += doubleHashOfAddressData[32:] # the tag + print 'embeddedTag is', doubleHashOfAddressData[32:].encode('hex') + print 'embeddedTag is', repr(doubleHashOfAddressData[32:]) - dataToEncrypt = encodeVarint(2) # broadcast version + if addressVersionNumber <= 3: + dataToEncrypt = encodeVarint(2) # broadcast version + else: + dataToEncrypt = encodeVarint(3) # broadcast version dataToEncrypt += encodeVarint(addressVersionNumber) dataToEncrypt += encodeVarint(streamNumber) dataToEncrypt += '\x00\x00\x00\x01' # behavior bitfield @@ -308,8 +408,11 @@ class singleWorker(threading.Thread): # Encrypt the broadcast with the information contained in the broadcaster's address. Anyone who knows the address can generate # the private encryption key to decrypt the broadcast. This provides virtually no privacy; its purpose is to keep questionable # and illegal content from flowing through the Internet connections and being stored on the disk of 3rd parties. - privEncryptionKey = hashlib.sha512(encodeVarint( - addressVersionNumber) + encodeVarint(streamNumber) + ripe).digest()[:32] + if addressVersionNumber <= 3: + privEncryptionKey = hashlib.sha512(encodeVarint( + addressVersionNumber) + encodeVarint(streamNumber) + ripe).digest()[:32] + else: + privEncryptionKey = doubleHashOfAddressData[:32] pubEncryptionKey = pointMult(privEncryptionKey) payload += highlevelcrypto.encrypt( dataToEncrypt, pubEncryptionKey.encode('hex')) @@ -328,7 +431,7 @@ class singleWorker(threading.Thread): inventoryHash = calculateInventoryHash(payload) objectType = 'broadcast' shared.inventory[inventoryHash] = ( - objectType, streamNumber, payload, int(time.time())) + objectType, streamNumber, payload, int(time.time()),'') shared.inventorySets[streamNumber].add(inventoryHash) with shared.printLock: print 'sending inv (within sendBroadcast function) for object:', inventoryHash.encode('hex') @@ -354,28 +457,63 @@ class singleWorker(threading.Thread): '''SELECT DISTINCT toaddress FROM sent WHERE (status='msgqueued' AND folder='sent')''') for row in queryreturn: # For each address to which we need to send a message, check to see if we have its pubkey already. toaddress, = row - toripe = decodeAddress(toaddress)[3] + status, toAddressVersion, toStreamNumber, toRipe = decodeAddress(toaddress) queryreturn = sqlQuery( - '''SELECT hash FROM pubkeys WHERE hash=? ''', toripe) - if queryreturn != []: # If we have the needed pubkey, set the status to doingmsgpow (we'll do it further down) + '''SELECT hash FROM pubkeys WHERE hash=? ''', toRipe) + if queryreturn != []: # If we have the needed pubkey in the pubkey table already, set the status to doingmsgpow (we'll do it further down) sqlExecute( '''UPDATE sent SET status='doingmsgpow' WHERE toaddress=? AND status='msgqueued' ''', toaddress) - else: # We don't have the needed pubkey. Set the status to 'awaitingpubkey' and request it if we haven't already - if toripe in shared.neededPubkeys: + else: # We don't have the needed pubkey in the pubkey table already. + if toAddressVersion <= 3: + toTag = '' + else: + toTag = hashlib.sha512(hashlib.sha512(encodeVarint(toAddressVersion)+encodeVarint(toStreamNumber)+toRipe).digest()).digest()[32:] + if toRipe in shared.neededPubkeys or toTag in shared.neededPubkeys: # We already sent a request for the pubkey sqlExecute( '''UPDATE sent SET status='awaitingpubkey' WHERE toaddress=? AND status='msgqueued' ''', toaddress) shared.UISignalQueue.put(('updateSentItemStatusByHash', ( - toripe, tr.translateText("MainWindow",'Encryption key was requested earlier.')))) + toRipe, tr.translateText("MainWindow",'Encryption key was requested earlier.')))) else: # We have not yet sent a request for the pubkey - sqlExecute( - '''UPDATE sent SET status='doingpubkeypow' WHERE toaddress=? AND status='msgqueued' ''', - toaddress) - shared.UISignalQueue.put(('updateSentItemStatusByHash', ( - toripe, tr.translateText("MainWindow",'Sending a request for the recipient\'s encryption key.')))) - self.requestPubKey(toaddress) + needToRequestPubkey = True + if toAddressVersion >= 4: # If we are trying to send to address version >= 4 then the needed pubkey might be encrypted in the inventory. + # If we have it we'll need to decrypt it and put it in the pubkeys table. + queryreturn = sqlQuery( + '''SELECT payload FROM inventory WHERE objecttype='pubkey' and tag=? ''', toTag) + if queryreturn != []: # if there was a pubkey in our inventory with the correct tag, we need to try to decrypt it. + for row in queryreturn: + data, = row + if shared.decryptAndCheckPubkeyPayload(data[8:], toaddress) == 'successful': + needToRequestPubkey = False + print 'debug. successfully decrypted and checked pubkey from sql inventory.' #testing + sqlExecute( + '''UPDATE sent SET status='doingmsgpow' WHERE toaddress=? AND status='msgqueued' ''', + toaddress) + break + else: # There was something wrong with this pubkey even though it had the correct tag- almost certainly because of malicious behavior or a badly programmed client. + continue + if needToRequestPubkey: # Obviously we had no success looking in the sql inventory. Let's look through the memory inventory. + with shared.inventoryLock: + for hash, storedValue in shared.inventory.items(): + objectType, streamNumber, payload, receivedTime, tag = storedValue + if objectType == 'pubkey' and tag == toTag: + result = shared.decryptAndCheckPubkeyPayload(payload[8:], toaddress) #if valid, this function also puts it in the pubkeys table. + if result == 'successful': + print 'debug. successfully decrypted and checked pubkey from memory inventory.' + needToRequestPubkey = False + sqlExecute( + '''UPDATE sent SET status='doingmsgpow' WHERE toaddress=? AND status='msgqueued' ''', + toaddress) + break + if needToRequestPubkey: + sqlExecute( + '''UPDATE sent SET status='doingpubkeypow' WHERE toaddress=? AND status='msgqueued' ''', + toaddress) + shared.UISignalQueue.put(('updateSentItemStatusByHash', ( + toRipe, tr.translateText("MainWindow",'Sending a request for the recipient\'s encryption key.')))) + self.requestPubKey(toaddress) # Get all messages that are ready to be sent, and also all messages # which we have sent in the last 28 days which were previously marked # as 'toodifficult'. If the user as raised the maximum acceptable @@ -442,7 +580,10 @@ class singleWorker(threading.Thread): # The pubkey message is stored the way we originally received it # which means that we need to read beyond things like the nonce and # time to get to the actual public keys. - readPosition = 8 # to bypass the nonce + if toAddressVersionNumber <= 3: + readPosition = 8 # to bypass the nonce + elif toAddressVersionNumber >= 4: + readPosition = 0 # the nonce is not included here so we don't need to skip over it. pubkeyEmbeddedTime, = unpack( '>I', pubkeyPayload[readPosition:readPosition + 4]) # This section is used for the transition from 32 bit time to 64 @@ -482,7 +623,7 @@ class singleWorker(threading.Thread): requiredPayloadLengthExtraBytes = shared.networkDefaultPayloadLengthExtraBytes shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( ackdata, tr.translateText("MainWindow", "Doing work necessary to send message.\nThere is no required difficulty for version 2 addresses like this.")))) - elif toAddressVersionNumber == 3: + elif toAddressVersionNumber >= 3: requiredAverageProofOfWorkNonceTrialsPerByte, varintLength = decodeVarint( pubkeyPayload[readPosition:readPosition + 10]) readPosition += varintLength @@ -555,7 +696,7 @@ class singleWorker(threading.Thread): payload += encodeVarint(len(signature)) payload += signature - if fromAddressVersionNumber == 3: + if fromAddressVersionNumber >= 3: payload = '\x01' # Message version. payload += encodeVarint(fromAddressVersionNumber) payload += encodeVarint(fromStreamNumber) @@ -624,7 +765,7 @@ class singleWorker(threading.Thread): payload += encodeVarint(len(signature)) payload += signature - + print 'using pubEncryptionKey:', pubEncryptionKeyBase256.encode('hex') # We have assembled the data that will be encrypted. try: encrypted = highlevelcrypto.encrypt(payload,"04"+pubEncryptionKeyBase256.encode('hex')) @@ -652,7 +793,7 @@ class singleWorker(threading.Thread): inventoryHash = calculateInventoryHash(encryptedPayload) objectType = 'msg' shared.inventory[inventoryHash] = ( - objectType, toStreamNumber, encryptedPayload, int(time.time())) + objectType, toStreamNumber, encryptedPayload, int(time.time()),'') shared.inventorySets[toStreamNumber].add(inventoryHash) if shared.safeConfigGetBoolean(toaddress, 'chan'): shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr.translateText("MainWindow", "Message sent. Sent on %1").arg(unicode( @@ -683,14 +824,24 @@ class singleWorker(threading.Thread): toAddress) + '. Please report this error to Atheros.') return - shared.neededPubkeys[ripe] = 0 + if addressVersionNumber <= 3: + shared.neededPubkeys[ripe] = 0 + elif addressVersionNumber >= 4: + privEncryptionKey = hashlib.sha512(hashlib.sha512(encodeVarint(addressVersionNumber)+encodeVarint(streamNumber)+ripe).digest()).digest()[:32] # Note that this is the first half of the sha512 hash. + tag = hashlib.sha512(hashlib.sha512(encodeVarint(addressVersionNumber)+encodeVarint(streamNumber)+ripe).digest()).digest()[32:] # Note that this is the second half of the sha512 hash. + shared.neededPubkeys[tag] = highlevelcrypto.makeCryptor(privEncryptionKey.encode('hex')) # We'll need this for when we receive a pubkey reply: it will be encrypted and we'll need to decrypt it. payload = pack('>Q', (int(time.time()) + random.randrange( -300, 300))) # the current time plus or minus five minutes. payload += encodeVarint(addressVersionNumber) payload += encodeVarint(streamNumber) - payload += ripe - with shared.printLock: - print 'making request for pubkey with ripe:', ripe.encode('hex') + if addressVersionNumber <= 3: + payload += ripe + with shared.printLock: + print 'making request for pubkey with ripe:', ripe.encode('hex') + else: + payload += tag + with shared.printLock: + print 'making request for v4 pubkey with tag:', tag.encode('hex') # print 'trial value', trialValue statusbar = 'Doing the computations necessary to request the recipient\'s public key.' @@ -709,7 +860,7 @@ class singleWorker(threading.Thread): inventoryHash = calculateInventoryHash(payload) objectType = 'getpubkey' shared.inventory[inventoryHash] = ( - objectType, streamNumber, payload, int(time.time())) + objectType, streamNumber, payload, int(time.time()),'') shared.inventorySets[streamNumber].add(inventoryHash) print 'sending inv (for the getpubkey message)' shared.broadcastToSendDataQueues(( diff --git a/src/class_sqlThread.py b/src/class_sqlThread.py index 34109172..829b243d 100644 --- a/src/class_sqlThread.py +++ b/src/class_sqlThread.py @@ -48,7 +48,7 @@ class sqlThread(threading.Thread): self.cur.execute( '''CREATE TABLE pubkeys (hash blob, transmitdata blob, time int, usedpersonally text, UNIQUE(hash) ON CONFLICT REPLACE)''' ) self.cur.execute( - '''CREATE TABLE inventory (hash blob, objecttype text, streamnumber int, payload blob, receivedtime integer, first20bytesofencryptedmessage blob, UNIQUE(hash) ON CONFLICT REPLACE)''' ) + '''CREATE TABLE inventory (hash blob, objecttype text, streamnumber int, payload blob, receivedtime integer, tag blob, UNIQUE(hash) ON CONFLICT REPLACE)''' ) self.cur.execute( '''CREATE TABLE knownnodes (timelastseen int, stream int, services blob, host blob, port blob, UNIQUE(host, stream, port) ON CONFLICT REPLACE)''' ) # This table isn't used in the program yet but I @@ -57,7 +57,7 @@ class sqlThread(threading.Thread): '''INSERT INTO subscriptions VALUES('Bitmessage new releases/announcements','BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw',1)''') self.cur.execute( '''CREATE TABLE settings (key blob, value blob, UNIQUE(key) ON CONFLICT REPLACE)''' ) - self.cur.execute( '''INSERT INTO settings VALUES('version','2')''') + self.cur.execute( '''INSERT INTO settings VALUES('version','4')''') self.cur.execute( '''INSERT INTO settings VALUES('lastvacuumtime',?)''', ( int(time.time()),)) self.conn.commit() @@ -193,7 +193,7 @@ class sqlThread(threading.Thread): ensureNamecoinOptions() - # Add a new column to the inventory table to store the first 20 bytes of encrypted messages to support Android app + """# Add a new column to the inventory table to store the first 20 bytes of encrypted messages to support Android app item = '''SELECT value FROM settings WHERE key='version';''' parameters = '' self.cur.execute(item, parameters) @@ -204,6 +204,40 @@ class sqlThread(threading.Thread): self.cur.execute(item, parameters) item = '''update settings set value=? WHERE key='version';''' parameters = (2,) + self.cur.execute(item, parameters)""" + + # Let's get rid of the first20bytesofencryptedmessage field in the inventory table. + item = '''SELECT value FROM settings WHERE key='version';''' + parameters = '' + self.cur.execute(item, parameters) + if int(self.cur.fetchall()[0][0]) == 2: + logger.debug('In messages.dat database, removing an obsolete field from the inventory table.') + self.cur.execute( + '''CREATE TEMPORARY TABLE inventory_backup(hash blob, objecttype text, streamnumber int, payload blob, receivedtime integer, UNIQUE(hash) ON CONFLICT REPLACE);''') + self.cur.execute( + '''INSERT INTO inventory_backup SELECT hash, objecttype, streamnumber, payload, receivedtime FROM inventory;''') + self.cur.execute( '''DROP TABLE inventory''') + self.cur.execute( + '''CREATE TABLE inventory (hash blob, objecttype text, streamnumber int, payload blob, receivedtime integer, UNIQUE(hash) ON CONFLICT REPLACE)''' ) + self.cur.execute( + '''INSERT INTO inventory SELECT hash, objecttype, streamnumber, payload, receivedtime FROM inventory_backup;''') + self.cur.execute( '''DROP TABLE inventory_backup;''') + item = '''update settings set value=? WHERE key='version';''' + parameters = (3,) + self.cur.execute(item, parameters) + + # Add a new column to the inventory table to store tags. + item = '''SELECT value FROM settings WHERE key='version';''' + parameters = '' + self.cur.execute(item, parameters) + currentVersion = int(self.cur.fetchall()[0][0]) + if currentVersion == 1 or currentVersion == 3: + logger.debug('In messages.dat database, adding tag field to the inventory table.') + item = '''ALTER TABLE inventory ADD tag blob DEFAULT '' ''' + parameters = '' + self.cur.execute(item, parameters) + item = '''update settings set value=? WHERE key='version';''' + parameters = (4,) self.cur.execute(item, parameters) if not shared.config.has_option('bitmessagesettings', 'userlocale'): @@ -211,6 +245,14 @@ class sqlThread(threading.Thread): if not shared.config.has_option('bitmessagesettings', 'sendoutgoingconnections'): shared.config.set('bitmessagesettings', 'sendoutgoingconnections', 'True') + # Raise the default required difficulty from 1 to 2 + if shared.config.getint('bitmessagesettings', 'settingsversion') == 6: + if int(shared.config.get('bitmessagesettings','defaultnoncetrialsperbyte')) == shared.networkDefaultProofOfWorkNonceTrialsPerByte: + shared.config.set('bitmessagesettings','defaultnoncetrialsperbyte', str(shared.networkDefaultProofOfWorkNonceTrialsPerByte * 2)) + shared.config.set('bitmessagesettings', 'settingsversion', '7') + 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 # Bitmessage users? Add it right above this line! diff --git a/src/defaultKnownNodes.py b/src/defaultKnownNodes.py index f481581c..92e53339 100644 --- a/src/defaultKnownNodes.py +++ b/src/defaultKnownNodes.py @@ -11,14 +11,18 @@ def createDefaultKnownNodes(appdata): ############## Stream 1 ################ stream1 = {} - stream1[shared.Peer('85.171.174.131', 8444)] = int(time.time()) - stream1[shared.Peer('23.28.68.159', 8444)] = int(time.time()) - stream1[shared.Peer('66.108.210.240', 8444)] = int(time.time()) - stream1[shared.Peer('204.236.246.212', 8444)] = int(time.time()) - stream1[shared.Peer('78.81.56.239', 8444)] = int(time.time()) - stream1[shared.Peer('122.60.235.157', 8444)] = int(time.time()) - stream1[shared.Peer('204.236.246.212', 8444)] = int(time.time()) - stream1[shared.Peer('24.98.219.109', 8444)] = int(time.time()) + stream1[shared.Peer('98.233.84.134', 8444)] = int(time.time()) + stream1[shared.Peer('76.24.18.44', 8444)] = int(time.time()) + stream1[shared.Peer('176.31.246.114', 8444)] = int(time.time()) + stream1[shared.Peer('66.108.210.240', 8080)] = int(time.time()) + stream1[shared.Peer('76.22.240.163', 8445)] = int(time.time()) + stream1[shared.Peer('46.21.99.29', 20982)] = int(time.time()) + stream1[shared.Peer('109.227.72.36', 8444)] = int(time.time()) + stream1[shared.Peer('176.31.246.114', 8444)] = int(time.time()) + stream1[shared.Peer('188.110.3.133', 8444)] = int(time.time()) + stream1[shared.Peer('174.0.45.163', 8444)] = int(time.time()) + stream1[shared.Peer('134.2.182.92', 8444)] = int(time.time()) + stream1[shared.Peer('24.143.60.183', 8444)] = int(time.time()) ############# Stream 2 ################# diff --git a/src/helper_bootstrap.py b/src/helper_bootstrap.py index f0cae8f2..6b5b98dc 100644 --- a/src/helper_bootstrap.py +++ b/src/helper_bootstrap.py @@ -25,7 +25,7 @@ def knownNodes(): shared.knownNodes[stream][peer] = time except: shared.knownNodes = defaultKnownNodes.createDefaultKnownNodes(shared.appdata) - if shared.config.getint('bitmessagesettings', 'settingsversion') > 6: + if shared.config.getint('bitmessagesettings', 'settingsversion') > 7: print 'Bitmessage cannot read future versions of the keys file (keys.dat). Run the newer version of Bitmessage.' raise SystemExit diff --git a/src/helper_startup.py b/src/helper_startup.py index 05afcb9c..5a5d7b81 100644 --- a/src/helper_startup.py +++ b/src/helper_startup.py @@ -42,7 +42,7 @@ def loadConfig(): # This appears to be the first time running the program; there is # no config file (or it cannot be accessed). Create config file. shared.config.add_section('bitmessagesettings') - shared.config.set('bitmessagesettings', 'settingsversion', '6') + shared.config.set('bitmessagesettings', 'settingsversion', '7') shared.config.set('bitmessagesettings', 'port', '8444') shared.config.set( 'bitmessagesettings', 'timeformat', '%%a, %%d %%b %%Y %%I:%%M %%p') @@ -74,7 +74,7 @@ def loadConfig(): shared.config.set( 'bitmessagesettings', 'messagesencrypted', 'false') shared.config.set('bitmessagesettings', 'defaultnoncetrialsperbyte', str( - shared.networkDefaultProofOfWorkNonceTrialsPerByte)) + shared.networkDefaultProofOfWorkNonceTrialsPerByte * 2)) shared.config.set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str( shared.networkDefaultPayloadLengthExtraBytes)) shared.config.set('bitmessagesettings', 'minimizeonclose', 'false') diff --git a/src/shared.py b/src/shared.py index 22a6d890..1b67fef9 100644 --- a/src/shared.py +++ b/src/shared.py @@ -1,4 +1,4 @@ -softwareVersion = '0.3.5' +softwareVersion = '0.4.0' verbose = 1 maximumAgeOfAnObjectThatIAmWillingToAccept = 216000 # Equals two days and 12 hours. lengthOfTimeToLeaveObjectsInInventory = 237600 # Equals two days and 18 hours. This should be longer than maximumAgeOfAnObjectThatIAmWillingToAccept so that we don't process messages twice. @@ -34,6 +34,7 @@ config = ConfigParser.SafeConfigParser() myECCryptorObjects = {} MyECSubscriptionCryptorObjects = {} myAddressesByHash = {} #The key in this dictionary is the RIPE hash which is encoded in an address and value is the address itself. +myAddressesByTag = {} # The key in this dictionary is the tag generated from the address. broadcastSendersForWhichImWatching = {} workerQueue = Queue.Queue() UISignalQueue = Queue.Queue() @@ -222,6 +223,7 @@ def reloadMyAddressHashes(): logger.debug('reloading keys from keys.dat file') myECCryptorObjects.clear() myAddressesByHash.clear() + myAddressesByTag.clear() #myPrivateKeys.clear() keyfileSecure = checkSensitiveFilePermissions(appdata + 'keys.dat') @@ -242,6 +244,9 @@ def reloadMyAddressHashes(): if len(privEncryptionKey) == 64:#It is 32 bytes encoded as 64 hex characters myECCryptorObjects[hash] = highlevelcrypto.makeCryptor(privEncryptionKey) myAddressesByHash[hash] = addressInKeysFile + tag = hashlib.sha512(hashlib.sha512(encodeVarint( + addressVersionNumber) + encodeVarint(streamNumber) + hash).digest()).digest()[32:] + myAddressesByTag[tag] = addressInKeysFile else: logger.error('Error in reloadMyAddressHashes: Can\'t handle address versions other than 2, 3, or 4.\n') @@ -250,18 +255,26 @@ def reloadMyAddressHashes(): fixSensitiveFilePermissions(appdata + 'keys.dat', hasEnabledKeys) def reloadBroadcastSendersForWhichImWatching(): - logger.debug('reloading subscriptions...') broadcastSendersForWhichImWatching.clear() MyECSubscriptionCryptorObjects.clear() queryreturn = sqlQuery('SELECT address FROM subscriptions where enabled=1') + logger.debug('reloading subscriptions...') for row in queryreturn: address, = row status,addressVersionNumber,streamNumber,hash = decodeAddress(address) if addressVersionNumber == 2: broadcastSendersForWhichImWatching[hash] = 0 #Now, for all addresses, even version 2 addresses, we should create Cryptor objects in a dictionary which we will use to attempt to decrypt encrypted broadcast messages. - privEncryptionKey = hashlib.sha512(encodeVarint(addressVersionNumber)+encodeVarint(streamNumber)+hash).digest()[:32] - MyECSubscriptionCryptorObjects[hash] = highlevelcrypto.makeCryptor(privEncryptionKey.encode('hex')) + + if addressVersionNumber <= 3: + privEncryptionKey = hashlib.sha512(encodeVarint(addressVersionNumber)+encodeVarint(streamNumber)+hash).digest()[:32] + MyECSubscriptionCryptorObjects[hash] = highlevelcrypto.makeCryptor(privEncryptionKey.encode('hex')) + else: + doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( + addressVersionNumber) + encodeVarint(streamNumber) + hash).digest()).digest() + tag = doubleHashOfAddressData[32:] + privEncryptionKey = doubleHashOfAddressData[:32] + MyECSubscriptionCryptorObjects[tag] = highlevelcrypto.makeCryptor(privEncryptionKey.encode('hex')) def doCleanShutdown(): global shutdown @@ -311,9 +324,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. with SqlBulkExecute() as sql: for hash, storedValue in inventory.items(): - objectType, streamNumber, payload, receivedTime = storedValue + objectType, streamNumber, payload, receivedTime, tag = storedValue sql.execute('''INSERT INTO inventory VALUES (?,?,?,?,?,?)''', - hash,objectType,streamNumber,payload,receivedTime,'') + hash,objectType,streamNumber,payload,receivedTime,tag) del inventory[hash] def fixPotentiallyInvalidUTF8Data(text): @@ -378,6 +391,88 @@ def isBitSetWithinBitfield(fourByteString, n): x, = unpack('>L', fourByteString) return x & 2**n != 0 +def decryptAndCheckPubkeyPayload(payload, address): + status, addressVersion, streamNumber, ripe = decodeAddress(address) + doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( + addressVersion) + encodeVarint(streamNumber) + ripe).digest()).digest() + # this function expects that the nonce is Not included in payload. + readPosition = 8 # for the time + embeddedVersionNumber, varintLength = decodeVarint( + payload[readPosition:readPosition + 10]) + if embeddedVersionNumber != addressVersion: + with shared.printLock: + print 'Pubkey decryption was UNsuccessful due to address version mismatch. This shouldn\'t have happened.' + return 'failed' + readPosition += varintLength + embeddedStreamNumber, varintLength = decodeVarint( + payload[readPosition:readPosition + 10]) + if embeddedStreamNumber != streamNumber: + with shared.printLock: + print 'Pubkey decryption was UNsuccessful due to stream number mismatch. This shouldn\'t have happened.' + return 'failed' + readPosition += varintLength + signedData = payload[:readPosition] # Some of the signed data is not encrypted so let's keep it for now. + toTag = payload[readPosition:readPosition+32] + readPosition += 32 #for the tag + encryptedData = payload[readPosition:] + # Let us try to decrypt the pubkey + privEncryptionKey = doubleHashOfAddressData[:32] + cryptorObject = highlevelcrypto.makeCryptor(privEncryptionKey.encode('hex')) + try: + decryptedData = cryptorObject.decrypt(encryptedData) + except: + # Someone must have encrypted some data with a different key + # but tagged it with a tag for which we are watching. + with shared.printLock: + print 'Pubkey decryption was UNsuccessful. This shouldn\'t have happened.' + return 'failed' + print 'Pubkey decryption successful' + readPosition = 4 # bypass the behavior bitfield + publicSigningKey = '\x04' + decryptedData[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 + 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')): + print 'ECDSA verify failed (within decryptAndCheckPubkeyPayload).' + return 'failed' + print 'ECDSA verify passed (within decryptAndCheckPubkeyPayload)' + except Exception as err: + print 'ECDSA verify failed (within decryptAndCheckPubkeyPayload)', err + return 'failed' + + sha = hashlib.new('sha512') + sha.update(publicSigningKey + publicEncryptionKey) + ripeHasher = hashlib.new('ripemd160') + ripeHasher.update(sha.digest()) + embeddedRipe = ripeHasher.digest() + + if embeddedRipe != ripe: + # Although this pubkey object had the tag were were looking for and was + # encrypted with the correct encryption key, it doesn't contain the + # correct keys. Someone is either being malicious or using buggy software. + with shared.printLock: + print 'Pubkey decryption was UNsuccessful due to RIPE mismatch. This shouldn\'t have happened.' + return 'failed' + + t = (ripe, signedData, int(time.time()), 'yes') + sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?)''', *t) + return 'successful' + Peer = collections.namedtuple('Peer', ['host', 'port']) helper_startup.loadConfig()