implement stealth ack objects #1062

Merged
f97ada87 merged 1 commits from stealth-ack into v0.6 2018-01-28 10:41:20 +01:00
8 changed files with 79 additions and 24 deletions

View File

@ -35,6 +35,7 @@ import network.stats
# Classes # Classes
from helper_sql import sqlQuery,sqlExecute,SqlBulkExecute,sqlStoredProcedure from helper_sql import sqlQuery,sqlExecute,SqlBulkExecute,sqlStoredProcedure
from helper_ackPayload import genAckPayload
from debug import logger from debug import logger
from inventory import Inventory from inventory import Inventory
from version import softwareVersion from version import softwareVersion
@ -679,7 +680,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
if not fromAddressEnabled: if not fromAddressEnabled:
raise APIError(14, 'Your fromAddress is disabled. Cannot send.') raise APIError(14, 'Your fromAddress is disabled. Cannot send.')
ackdata = OpenSSL.rand(32) stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel')
ackdata = genAckPayload(streamNumber, stealthLevel)
t = ('', t = ('',
toAddress, toAddress,
@ -740,7 +742,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
fromAddress, 'enabled') fromAddress, 'enabled')
except: except:
raise APIError(13, 'could not find your fromAddress in the keys.dat file.') raise APIError(13, 'could not find your fromAddress in the keys.dat file.')
ackdata = OpenSSL.rand(32) ackdata = genAckPayload(streamNumber, 0)
toAddress = '[Broadcast subscribers]' toAddress = '[Broadcast subscribers]'
ripe = '' ripe = ''

View File

@ -20,6 +20,7 @@ import curses
import dialog import dialog
from dialog import Dialog from dialog import Dialog
from helper_sql import * from helper_sql import *
from helper_ackPayload import genAckPayload
from addresses import * from addresses import *
import ConfigParser import ConfigParser
@ -778,7 +779,8 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F
if len(shared.connectedHostsList) == 0: if len(shared.connectedHostsList) == 0:
set_background_title(d, "Not connected warning") set_background_title(d, "Not connected warning")
scrollbox(d, unicode("Because you are not currently connected to the network, ")) scrollbox(d, unicode("Because you are not currently connected to the network, "))
ackdata = OpenSSL.rand(32) stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel')
ackdata = genAckPayload(streamNumber, stealthLevel)
sqlExecute( sqlExecute(
"INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", "INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
"", "",
@ -802,7 +804,8 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F
set_background_title(d, "Empty sender error") set_background_title(d, "Empty sender error")
scrollbox(d, unicode("You must specify an address to send the message from.")) scrollbox(d, unicode("You must specify an address to send the message from."))
else: else:
ackdata = OpenSSL.rand(32) # dummy ackdata, no need for stealth
ackdata = genAckPayload(streamNumber, 0)
recv = BROADCAST_STR recv = BROADCAST_STR
ripe = "" ripe = ""
sqlExecute( sqlExecute(

View File

@ -52,6 +52,7 @@ import random
import string import string
from datetime import datetime, timedelta from datetime import datetime, timedelta
from helper_sql import * from helper_sql import *
from helper_ackPayload import genAckPayload
import helper_search import helper_search
import l10n import l10n
import openclpow import openclpow
@ -1879,7 +1880,8 @@ class MyForm(settingsmixin.SMainWindow):
if shared.statusIconColor == 'red': if shared.statusIconColor == 'red':
self.statusBar().showMessage(_translate( self.statusBar().showMessage(_translate(
"MainWindow", "Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won\'t send until you connect.")) "MainWindow", "Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won\'t send until you connect."))
ackdata = OpenSSL.rand(32) stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel')
ackdata = genAckPayload(streamNumber, stealthLevel)
t = () t = ()
sqlExecute( sqlExecute(
'''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''',
@ -1933,7 +1935,7 @@ class MyForm(settingsmixin.SMainWindow):
# We don't actually need the ackdata for acknowledgement since # We don't actually need the ackdata for acknowledgement since
# this is a broadcast message, but we can use it to update the # this is a broadcast message, but we can use it to update the
# user interface when the POW is done generating. # user interface when the POW is done generating.
ackdata = OpenSSL.rand(32) ackdata = genAckPayload(streamNumber, 0)
toAddress = str_broadcast_subscribers toAddress = str_broadcast_subscribers
ripe = '' ripe = ''
t = ('', # msgid. We don't know what this will be until the POW is done. t = ('', # msgid. We don't know what this will be until the POW is done.

View File

@ -5,6 +5,7 @@ import re
import sys import sys
import inspect import inspect
from helper_sql import * from helper_sql import *
from helper_ackPayload import genAckPayload
from addresses import decodeAddress from addresses import decodeAddress
from bmconfigparser import BMConfigParser from bmconfigparser import BMConfigParser
from foldertree import AccountMixin from foldertree import AccountMixin
@ -166,7 +167,8 @@ class GatewayAccount(BMAccount):
def send(self): def send(self):
status, addressVersionNumber, streamNumber, ripe = decodeAddress(self.toAddress) status, addressVersionNumber, streamNumber, ripe = decodeAddress(self.toAddress)
ackdata = OpenSSL.rand(32) stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel')
ackdata = genAckPayload(streamNumber, stealthLevel)
t = () t = ()
sqlExecute( sqlExecute(
'''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''',

View File

@ -21,6 +21,7 @@ import helper_inbox
import helper_msgcoding import helper_msgcoding
import helper_sent import helper_sent
from helper_sql import * from helper_sql import *
from helper_ackPayload import genAckPayload
import protocol import protocol
import queues import queues
import state import state
@ -97,15 +98,9 @@ class objectProcessor(threading.Thread):
# Let's check whether this is a message acknowledgement bound for us. # Let's check whether this is a message acknowledgement bound for us.
if len(data) < 32: if len(data) < 32:
return return
readPosition = 20 # bypass the nonce, time, and object type
# chomp version number # bypass nonce and time, retain object type/version/stream + body
versionNumber, varIntLength = decodeVarint( readPosition = 16
data[readPosition:readPosition + 10])
readPosition += varIntLength
# chomp stream number
streamNumber, varIntLength = decodeVarint(
data[readPosition:readPosition + 10])
readPosition += varIntLength
if data[readPosition:] in shared.ackdataForWhichImWatching: if data[readPosition:] in shared.ackdataForWhichImWatching:
logger.info('This object is an acknowledgement bound for me.') logger.info('This object is an acknowledgement bound for me.')
@ -558,8 +553,8 @@ class objectProcessor(threading.Thread):
message = time.strftime("%a, %Y-%m-%d %H:%M:%S UTC", time.gmtime( message = time.strftime("%a, %Y-%m-%d %H:%M:%S UTC", time.gmtime(
)) + ' Message ostensibly from ' + fromAddress + ':\n\n' + body )) + ' Message ostensibly from ' + fromAddress + ':\n\n' + body
fromAddress = toAddress # The fromAddress for the broadcast that we are about to send is the toAddress (my address) for the msg message we are currently processing. fromAddress = toAddress # The fromAddress for the broadcast that we are about to send is the toAddress (my address) for the msg message we are currently processing.
ackdataForBroadcast = OpenSSL.rand( # We don't actually need the ackdataForBroadcast for acknowledgement since this is a broadcast message but we can use it to update the user interface when the POW is done generating.
32) # We don't actually need the ackdataForBroadcast for acknowledgement since this is a broadcast message but we can use it to update the user interface when the POW is done generating. ackdata = genAckPayload(streamNumber, 0)
toAddress = '[Broadcast subscribers]' toAddress = '[Broadcast subscribers]'
ripe = '' ripe = ''

View File

@ -81,6 +81,16 @@ class singleWorker(threading.Thread, StoppableThread):
logger.info('Watching for ackdata ' + hexlify(ackdata)) logger.info('Watching for ackdata ' + hexlify(ackdata))
shared.ackdataForWhichImWatching[ackdata] = 0 shared.ackdataForWhichImWatching[ackdata] = 0
# Fix legacy (headerless) watched ackdata to include header
for oldack in shared.ackdataForWhichImWatching.keys():
if (len(oldack)==32):
# attach legacy header, always constant (msg/1/1)
newack = '\x00\x00\x00\x02\x01\x01' + oldack
shared.ackdataForWhichImWatching[newack] = 0
sqlExecute('UPDATE sent SET ackdata=? WHERE ackdata=?',
newack, oldack )
del shared.ackdataForWhichImWatching[oldack]
self.stop.wait( self.stop.wait(
10) # give some time for the GUI to start before we start on existing POW tasks. 10) # give some time for the GUI to start before we start on existing POW tasks.
@ -967,10 +977,9 @@ class singleWorker(threading.Thread, StoppableThread):
TTL = 28*24*60*60 # 4 weeks TTL = 28*24*60*60 # 4 weeks
TTL = int(TTL + random.randrange(-300, 300)) # Add some randomness to the TTL TTL = int(TTL + random.randrange(-300, 300)) # Add some randomness to the TTL
embeddedTime = int(time.time() + TTL) embeddedTime = int(time.time() + TTL)
payload = pack('>Q', (embeddedTime))
payload += '\x00\x00\x00\x02' # object type: msg # type/version/stream already included
payload += encodeVarint(1) # msg version payload = pack('>Q', (embeddedTime)) + ackdata
payload += encodeVarint(toStreamNumber) + ackdata
target = 2 ** 64 / (defaults.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + defaults.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+defaults.networkDefaultPayloadLengthExtraBytes))/(2 ** 16)))) target = 2 ** 64 / (defaults.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + defaults.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+defaults.networkDefaultPayloadLengthExtraBytes))/(2 ** 16))))
logger.info('(For ack message) Doing proof of work. TTL set to ' + str(TTL)) logger.info('(For ack message) Doing proof of work. TTL set to ' + str(TTL))

View File

@ -14,6 +14,7 @@ from addresses import decodeAddress
from bmconfigparser import BMConfigParser from bmconfigparser import BMConfigParser
from debug import logger from debug import logger
from helper_sql import sqlExecute from helper_sql import sqlExecute
from helper_ackPayload import genAckPayload
from helper_threading import StoppableThread from helper_threading import StoppableThread
from pyelliptic.openssl import OpenSSL from pyelliptic.openssl import OpenSSL
import queues import queues
@ -65,7 +66,8 @@ class smtpServerPyBitmessage(smtpd.SMTPServer):
def send(self, fromAddress, toAddress, subject, message): def send(self, fromAddress, toAddress, subject, message):
status, addressVersionNumber, streamNumber, ripe = decodeAddress(toAddress) status, addressVersionNumber, streamNumber, ripe = decodeAddress(toAddress)
ackdata = OpenSSL.rand(32) stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel')
ackdata = genAckPayload(streamNumber, stealthLevel)
t = () t = ()
sqlExecute( sqlExecute(
'''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''',

40
src/helper_ackPayload.py Normal file
View File

@ -0,0 +1,40 @@
import hashlib
import highlevelcrypto
import random
import helper_random
from binascii import hexlify, unhexlify
from struct import pack, unpack
from addresses import encodeVarint
# This function generates payload objects for message acknowledgements
# Several stealth levels are available depending on the privacy needs;
# a higher level means better stealth, but also higher cost (size+POW)
# - level 0: a random 32-byte sequence with a message header appended
# - level 1: a getpubkey request for a (random) dummy key hash
# - level 2: a standard message, encrypted to a random pubkey
def genAckPayload(streamNumber=1, stealthLevel=0):
if (stealthLevel==2): # Generate privacy-enhanced payload
# Generate a dummy privkey and derive the pubkey
dummyPubKeyHex = highlevelcrypto.privToPub(hexlify(helper_random.randomBytes(32)))
# Generate a dummy message of random length
# (the smallest possible standard-formatted message is 234 bytes)
dummyMessage = helper_random.randomBytes(random.randint(234, 800))
# Encrypt the message using standard BM encryption (ECIES)
ackdata = highlevelcrypto.encrypt(dummyMessage, dummyPubKeyHex)
acktype = 2 # message
version = 1
elif (stealthLevel==1): # Basic privacy payload (random getpubkey)
ackdata = helper_random.randomBytes(32)
acktype = 0 # getpubkey
version = 4
else: # Minimum viable payload (non stealth)
ackdata = helper_random.randomBytes(32)
acktype = 2 # message
version = 1
ackobject = pack('>I', acktype) + encodeVarint(version) + encodeVarint(streamNumber) + ackdata
return ackobject