diff --git a/src/api.py b/src/api.py index 4bf316f2..e3acac29 100644 --- a/src/api.py +++ b/src/api.py @@ -66,12 +66,11 @@ import socket import subprocess # nosec B404 import time from binascii import hexlify, unhexlify -from struct import pack +from struct import pack, unpack import six from six.moves import configparser, http_client, xmlrpc_server -import defaults import helper_inbox import helper_sent import protocol @@ -89,6 +88,9 @@ from addresses import ( ) from bmconfigparser import config from debug import logger +from defaults import ( + networkDefaultProofOfWorkNonceTrialsPerByte, + networkDefaultPayloadLengthExtraBytes) from helper_sql import ( SqlBulkExecute, sqlExecute, sqlQuery, sqlStoredProcedure, sql_ready) from highlevelcrypto import calculateInventoryHash @@ -657,13 +659,11 @@ class BMRPCDispatcher(object): nonceTrialsPerByte = self.config.get( 'bitmessagesettings', 'defaultnoncetrialsperbyte' ) if not totalDifficulty else int( - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - * totalDifficulty) + networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty) payloadLengthExtraBytes = self.config.get( 'bitmessagesettings', 'defaultpayloadlengthextrabytes' ) if not smallMessageDifficulty else int( - defaults.networkDefaultPayloadLengthExtraBytes - * smallMessageDifficulty) + networkDefaultPayloadLengthExtraBytes * smallMessageDifficulty) if not isinstance(eighteenByteRipe, bool): raise APIError( @@ -705,13 +705,11 @@ class BMRPCDispatcher(object): nonceTrialsPerByte = self.config.get( 'bitmessagesettings', 'defaultnoncetrialsperbyte' ) if not totalDifficulty else int( - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - * totalDifficulty) + networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty) payloadLengthExtraBytes = self.config.get( 'bitmessagesettings', 'defaultpayloadlengthextrabytes' ) if not smallMessageDifficulty else int( - defaults.networkDefaultPayloadLengthExtraBytes - * smallMessageDifficulty) + networkDefaultPayloadLengthExtraBytes * smallMessageDifficulty) if not passphrase: raise APIError(1, 'The specified passphrase is blank.') @@ -1284,55 +1282,63 @@ class BMRPCDispatcher(object): }) return {'subscriptions': data} - @command('disseminatePreEncryptedMsg') - def HandleDisseminatePreEncryptedMsg( - self, encryptedPayload, requiredAverageProofOfWorkNonceTrialsPerByte, - requiredPayloadLengthExtraBytes): - """Handle a request to disseminate an encrypted message""" + @command('disseminatePreEncryptedMsg', 'disseminatePreparedObject') + def HandleDisseminatePreparedObject( + self, encryptedPayload, + nonceTrialsPerByte=networkDefaultProofOfWorkNonceTrialsPerByte, + payloadLengthExtraBytes=networkDefaultPayloadLengthExtraBytes + ): + """ + Handle a request to disseminate an encrypted message. - # The device issuing this command to PyBitmessage supplies a msg - # object that has already been encrypted but which still needs the POW - # to be done. PyBitmessage accepts this msg object and sends it out - # to the rest of the Bitmessage network as if it had generated - # the message itself. Please do not yet add this to the api doc. - encryptedPayload = b'\x00' * 8 + self._decode(encryptedPayload, "hex") - # compatibility stub ^, since disseminatePreEncryptedMsg - # still expects the encryptedPayload without a nonce + The device issuing this command to PyBitmessage supplies an object + that has already been encrypted but which may still need the PoW + to be done. PyBitmessage accepts this object and sends it out + to the rest of the Bitmessage network as if it had generated + the message itself. + + *encryptedPayload* is a hex encoded string starting with the nonce, + 8 zero bytes in case of no PoW done. + """ + encryptedPayload = self._decode(encryptedPayload, "hex") + + nonce, = unpack('>Q', encryptedPayload[:8]) objectType, toStreamNumber, expiresTime = \ protocol.decodeObjectParameters(encryptedPayload) - encryptedPayload = encryptedPayload[8:] - TTL = expiresTime - time.time() + 300 # a bit of extra padding - # Let us do the POW and attach it to the front - target = 2**64 / ( - requiredAverageProofOfWorkNonceTrialsPerByte * ( - len(encryptedPayload) + 8 - + requiredPayloadLengthExtraBytes + (( - TTL * ( - len(encryptedPayload) + 8 - + requiredPayloadLengthExtraBytes - )) / (2 ** 16)) - )) - logger.debug("expiresTime: %s", expiresTime) - logger.debug("TTL: %s", TTL) - logger.debug("objectType: %s", objectType) - logger.info( - '(For msg message via API) Doing proof of work. Total required' - ' difficulty: %s\nRequired small message difficulty: %s', - float(requiredAverageProofOfWorkNonceTrialsPerByte) - / defaults.networkDefaultProofOfWorkNonceTrialsPerByte, - float(requiredPayloadLengthExtraBytes) - / defaults.networkDefaultPayloadLengthExtraBytes, - ) - powStartTime = time.time() - initialHash = hashlib.sha512(encryptedPayload).digest() - trialValue, nonce = proofofwork.run(target, initialHash) - logger.info( - '(For msg message via API) Found proof of work %s\nNonce: %s\n' - 'POW took %s seconds. %s nonce trials per second.', - trialValue, nonce, int(time.time() - powStartTime), - nonce / (time.time() - powStartTime) - ) - encryptedPayload = pack('>Q', nonce) + encryptedPayload + + if nonce == 0: # Let us do the POW and attach it to the front + encryptedPayload = encryptedPayload[8:] + TTL = expiresTime - time.time() + 300 # a bit of extra padding + # Let us do the POW and attach it to the front + logger.debug("expiresTime: %s", expiresTime) + logger.debug("TTL: %s", TTL) + logger.debug("objectType: %s", objectType) + logger.info( + '(For msg message via API) Doing proof of work. Total required' + ' difficulty: %s\nRequired small message difficulty: %s', + float(nonceTrialsPerByte) + / networkDefaultProofOfWorkNonceTrialsPerByte, + float(payloadLengthExtraBytes) + / networkDefaultPayloadLengthExtraBytes, + ) + powStartTime = time.time() + target = 2**64 / ( + nonceTrialsPerByte * ( + len(encryptedPayload) + 8 + payloadLengthExtraBytes + (( + TTL * ( + len(encryptedPayload) + 8 + payloadLengthExtraBytes + )) / (2 ** 16)) + )) + initialHash = hashlib.sha512(encryptedPayload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + logger.info( + '(For msg message via API) Found proof of work %s\nNonce: %s\n' + 'POW took %s seconds. %s nonce trials per second.', + trialValue, nonce, int(time.time() - powStartTime), + nonce / (time.time() - powStartTime) + ) + encryptedPayload = pack('>Q', nonce) + encryptedPayload + inventoryHash = calculateInventoryHash(encryptedPayload) Inventory()[inventoryHash] = ( objectType, toStreamNumber, encryptedPayload, @@ -1365,8 +1371,8 @@ class BMRPCDispatcher(object): # Let us do the POW target = 2 ** 64 / (( - len(payload) + defaults.networkDefaultPayloadLengthExtraBytes + 8 - ) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte) + len(payload) + networkDefaultPayloadLengthExtraBytes + 8 + ) * networkDefaultProofOfWorkNonceTrialsPerByte) logger.info('(For pubkey message via API) Doing proof of work...') initialHash = hashlib.sha512(payload).digest() trialValue, nonce = proofofwork.run(target, initialHash) diff --git a/src/tests/samples.py b/src/tests/samples.py index a80a61ae..d96187ca 100644 --- a/src/tests/samples.py +++ b/src/tests/samples.py @@ -20,11 +20,11 @@ sample_addr_data = unhexlify( # These keys are from addresses test script sample_pubsigningkey = unhexlify( - '044a367f049ec16cb6b6118eb734a9962d10b8db59c890cd08f210c43ff08bdf09d' - '16f502ca26cd0713f38988a1237f1fc8fa07b15653c996dc4013af6d15505ce') + '044a367f049ec16cb6b6118eb734a9962d10b8db59c890cd08f210c43ff08bdf09' + 'd16f502ca26cd0713f38988a1237f1fc8fa07b15653c996dc4013af6d15505ce') sample_pubencryptionkey = unhexlify( - '044597d59177fc1d89555d38915f581b5ff2286b39d022ca0283d2bdd5c36be5d3c' - 'e7b9b97792327851a562752e4b79475d1f51f5a71352482b241227f45ed36a9') + '044597d59177fc1d89555d38915f581b5ff2286b39d022ca0283d2bdd5c36be5d3' + 'ce7b9b97792327851a562752e4b79475d1f51f5a71352482b241227f45ed36a9') sample_privsigningkey = \ b'93d0b61371a54b53df143b954035d612f8efa8a3ed1cf842c2186bfd8f876665' sample_privencryptionkey = \ diff --git a/src/tests/test_api_thread.py b/src/tests/test_api_thread.py index 5b004066..5abc13bb 100644 --- a/src/tests/test_api_thread.py +++ b/src/tests/test_api_thread.py @@ -8,9 +8,7 @@ from struct import pack from six.moves import queue, xmlrpc_client from pybitmessage import protocol -from pybitmessage.defaults import ( - networkDefaultProofOfWorkNonceTrialsPerByte, - networkDefaultPayloadLengthExtraBytes) +from pybitmessage.highlevelcrypto import calculateInventoryHash from .partial import TestPartialRun from .samples import sample_statusbar_msg, sample_object_data @@ -80,12 +78,14 @@ class TestAPIThread(TestPartialRun): from inventory import Inventory proofofwork.init() - update_object = pack( + self.assertEqual( + unhexlify(self.api.disseminatePreparedObject( + hexlify(sample_object_data).decode())), + calculateInventoryHash(sample_object_data)) + update_object = b'\x00' * 8 + pack( '>Q', int(time.time() + 7200)) + sample_object_data[16:] invhash = unhexlify(self.api.disseminatePreEncryptedMsg( - hexlify(update_object).decode(), - networkDefaultProofOfWorkNonceTrialsPerByte, - networkDefaultPayloadLengthExtraBytes + hexlify(update_object).decode() )) obj_type, obj_stream, obj_data = Inventory()[invhash][:3] self.assertEqual(obj_type, 42)