From 4b3c3876346b896244b180aa28cb1179a5a2ea53 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Tue, 27 Jul 2021 22:21:20 +0300 Subject: [PATCH 01/14] Add a test for sign() and verify() --- src/tests/samples.py | 24 +++++++++++++++++++----- src/tests/test_api.py | 22 +++++++++++++--------- src/tests/test_crypto.py | 26 ++++++++++++++++++++++++-- 3 files changed, 56 insertions(+), 16 deletions(-) diff --git a/src/tests/samples.py b/src/tests/samples.py index e1a3e676ff..fd7393e175 100644 --- a/src/tests/samples.py +++ b/src/tests/samples.py @@ -36,9 +36,23 @@ sample_daddr3_512 = 18875720106589866286514488037355423395410802084648916523381 sample_daddr4_512 = 25152821841976547050350277460563089811513157529113201589004 -sample_statusbar_msg = "new status bar message" -sample_inbox_msg_ids = ['27e644765a3e4b2e973ee7ccf958ea20', '51fc5531-3989-4d69-bbb5-68d64b756f5b', - '2c975c515f8b414db5eea60ba57ba455', 'bc1f2d8a-681c-4cc0-9a12-6067c7e1ac24'] -# second address in sample_test_subscription_address is for the announcement broadcast -sample_test_subscription_address = ['BM-2cWQLCBGorT9pUGkYSuGGVr9LzE4mRnQaq', 'BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw'] +sample_statusbar_msg = 'new status bar message' +sample_inbox_msg_ids = [ + '27e644765a3e4b2e973ee7ccf958ea20', '51fc5531-3989-4d69-bbb5-68d64b756f5b', + '2c975c515f8b414db5eea60ba57ba455', 'bc1f2d8a-681c-4cc0-9a12-6067c7e1ac24'] +# second address in sample_subscription_addresses +# is for the announcement broadcast +sample_subscription_addresses = [ + 'BM-2cWQLCBGorT9pUGkYSuGGVr9LzE4mRnQaq', + 'BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw'] sample_subscription_name = 'test sub' + +sample_msg = unhexlify( + '0592a10584ffabf96539f3d780d776828c67da1ab5b169e9e8aed838aaecc9ed36d49ff' + '1423c55f019e050c66c6324f53588be88894fef4dcffdb74b98e2b200') +sample_sig = unhexlify( + '304402202302475351db6b822de15d922e29397541f10d8a19780ba2ca4a920b1035f075' + '02205e5bba40d5f07a24c23a89ba5f01a3828371dfbb685dd5375fa1c29095fd232b') +sample_sig_sha1 = unhexlify( + '304502203b50123af78b4e40f5f819ae5b8786f48826e56d0f3e65744708a493f5b65de1' + '0221009ddce2981ea143c0ac70404a535327e774adce8eebbae2d35104f1d326255f9a') diff --git a/src/tests/test_api.py b/src/tests/test_api.py index 835b4afbfa..41a3c91116 100644 --- a/src/tests/test_api.py +++ b/src/tests/test_api.py @@ -12,8 +12,10 @@ import psutil from .samples import ( - sample_seed, sample_deterministic_addr3, sample_deterministic_addr4, sample_statusbar_msg, - sample_inbox_msg_ids, sample_test_subscription_address, sample_subscription_name) + sample_seed, sample_deterministic_addr3, sample_deterministic_addr4, + sample_statusbar_msg, sample_inbox_msg_ids, sample_subscription_addresses, + sample_subscription_name +) from .test_process import TestProcessProto @@ -263,9 +265,10 @@ def test_addressbook(self): def test_subscriptions(self): """Testing the API commands related to subscriptions""" - self.assertEqual( - self.api.addSubscription(sample_test_subscription_address[0], sample_subscription_name.encode('base64')), + self.api.addSubscription( + sample_subscription_addresses[0], + sample_subscription_name.encode('base64')), 'Added subscription.' ) @@ -273,18 +276,19 @@ def test_subscriptions(self): # check_address for sub in json.loads(self.api.listSubscriptions())['subscriptions']: # special address, added when sqlThread starts - if sub['address'] == sample_test_subscription_address[0]: + if sub['address'] == sample_subscription_addresses[0]: added_subscription = sub break self.assertEqual( - base64.decodestring(added_subscription['label']) if added_subscription['label'] else None, + base64.decodestring(added_subscription['label']) + if added_subscription['label'] else None, sample_subscription_name) self.assertTrue(added_subscription['enabled']) for s in json.loads(self.api.listSubscriptions())['subscriptions']: # special address, added when sqlThread starts - if s['address'] == sample_test_subscription_address[1]: + if s['address'] == sample_subscription_addresses[1]: self.assertEqual( base64.decodestring(s['label']), 'Bitmessage new releases/announcements') @@ -295,10 +299,10 @@ def test_subscriptions(self): 'Could not find Bitmessage new releases/announcements' ' in subscriptions') self.assertEqual( - self.api.deleteSubscription(sample_test_subscription_address[0]), + self.api.deleteSubscription(sample_subscription_addresses[0]), 'Deleted subscription if it existed.') self.assertEqual( - self.api.deleteSubscription(sample_test_subscription_address[1]), + self.api.deleteSubscription(sample_subscription_addresses[1]), 'Deleted subscription if it existed.') self.assertEqual( json.loads(self.api.listSubscriptions())['subscriptions'], []) diff --git a/src/tests/test_crypto.py b/src/tests/test_crypto.py index 3841035952..e5bb600adc 100644 --- a/src/tests/test_crypto.py +++ b/src/tests/test_crypto.py @@ -16,8 +16,9 @@ RIPEMD = None from .samples import ( - sample_pubsigningkey, sample_pubencryptionkey, - sample_privsigningkey, sample_privencryptionkey, sample_ripe + sample_msg, sample_pubsigningkey, sample_pubencryptionkey, + sample_privsigningkey, sample_privencryptionkey, sample_ripe, + sample_sig, sample_sig_sha1 ) @@ -62,6 +63,27 @@ def _hashdigest(data): class TestHighlevelcrypto(unittest.TestCase): """Test highlevelcrypto public functions""" + # def test_sign(self): + # """Check the signature of the sample_msg created with sample key""" + # self.assertEqual( + # highlevelcrypto.sign(sample_msg, sample_privsigningkey), sample_sig) + + def test_verify(self): + """Verify sample signatures and newly generated ones""" + pubkey_hex = hexlify(sample_pubsigningkey) + # pregenerated signatures + self.assertTrue( + highlevelcrypto.verify(sample_msg, sample_sig, pubkey_hex)) + self.assertTrue( + highlevelcrypto.verify(sample_msg, sample_sig_sha1, pubkey_hex)) + # new signatures + sig256 = highlevelcrypto.sign(sample_msg, sample_privsigningkey) + sig1 = highlevelcrypto.sign(sample_msg, sample_privsigningkey, "sha1") + self.assertTrue( + highlevelcrypto.verify(sample_msg, sig256, pubkey_hex)) + self.assertTrue( + highlevelcrypto.verify(sample_msg, sig1, pubkey_hex)) + def test_privtopub(self): """Generate public keys and check the result""" self.assertEqual( From 32e7e863f8acbd3afb0abc8c4a389d536155e6fa Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Wed, 28 Jul 2021 00:44:19 +0300 Subject: [PATCH 02/14] Don't use BMConfigParser in highlevelcrypto, instead use digestAlg kwarg --- src/class_singleWorker.py | 13 ++++++++----- src/highlevelcrypto.py | 20 +++++++------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index fea842ea66..49c41c077d 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -50,6 +50,8 @@ class singleWorker(StoppableThread): def __init__(self): super(singleWorker, self).__init__(name="singleWorker") + self.digestAlg = BMConfigParser().safeGet( + 'bitmessagesettings', 'digestalg', 'sha256') proofofwork.init() def stopThread(self): @@ -368,7 +370,8 @@ def sendOutOrStoreMyV3Pubkey(self, adressHash): payload += encodeVarint(BMConfigParser().getint( myAddress, 'payloadlengthextrabytes')) - signature = highlevelcrypto.sign(payload, privSigningKeyHex) + signature = highlevelcrypto.sign( + payload, privSigningKeyHex, self.digestAlg) payload += encodeVarint(len(signature)) payload += signature @@ -455,8 +458,7 @@ def sendOutOrStoreMyV4Pubkey(self, myAddress): ).digest()).digest() payload += doubleHashOfAddressData[32:] # the tag signature = highlevelcrypto.sign( - payload + dataToEncrypt, privSigningKeyHex - ) + payload + dataToEncrypt, privSigningKeyHex, self.digestAlg) dataToEncrypt += encodeVarint(len(signature)) dataToEncrypt += signature @@ -641,7 +643,7 @@ def sendBroadcast(self): dataToSign = payload + dataToEncrypt signature = highlevelcrypto.sign( - dataToSign, privSigningKeyHex) + dataToSign, privSigningKeyHex, self.digestAlg) dataToEncrypt += encodeVarint(len(signature)) dataToEncrypt += signature @@ -1223,7 +1225,8 @@ def sendMsg(self): payload += fullAckPayload dataToSign = pack('>Q', embeddedTime) + '\x00\x00\x00\x02' + \ encodeVarint(1) + encodeVarint(toStreamNumber) + payload - signature = highlevelcrypto.sign(dataToSign, privSigningKeyHex) + signature = highlevelcrypto.sign( + dataToSign, privSigningKeyHex, self.digestAlg) payload += encodeVarint(len(signature)) payload += signature diff --git a/src/highlevelcrypto.py b/src/highlevelcrypto.py index 82743acf10..9a31ad972e 100644 --- a/src/highlevelcrypto.py +++ b/src/highlevelcrypto.py @@ -13,7 +13,6 @@ from pyelliptic import OpenSSL from pyelliptic import arithmetic as a -from bmconfigparser import BMConfigParser __all__ = ['encrypt', 'makeCryptor', 'pointMult', 'privToPub', 'sign', 'verify'] @@ -67,22 +66,17 @@ def decryptFast(msg, cryptor): return cryptor.decrypt(msg) -def sign(msg, hexPrivkey): +def sign(msg, hexPrivkey, digestAlg="sha256"): """ Signs with hex private key using SHA1 or SHA256 depending on - "digestalg" setting + *digestAlg* keyword. """ - digestAlg = BMConfigParser().safeGet( - 'bitmessagesettings', 'digestalg', 'sha256') - if digestAlg == "sha1": - # SHA1, this will eventually be deprecated - return makeCryptor(hexPrivkey).sign( - msg, digest_alg=OpenSSL.digest_ecdsa_sha1) - elif digestAlg == "sha256": - # SHA256. Eventually this will become the default - return makeCryptor(hexPrivkey).sign(msg, digest_alg=OpenSSL.EVP_sha256) - else: + if digestAlg not in ("sha1", "sha256"): raise ValueError("Unknown digest algorithm %s" % digestAlg) + # SHA1, this will eventually be deprecated + return makeCryptor(hexPrivkey).sign( + msg, digest_alg=OpenSSL.digest_ecdsa_sha1 + if digestAlg == "sha1" else OpenSSL.EVP_sha256) def verify(msg, sig, hexPubkey): From 0290607538ef5379230f0bf8485a22c57761483a Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Thu, 29 Jul 2021 14:47:23 +0300 Subject: [PATCH 03/14] Add tests for base58 and WIF decoding using pyelliptic.arithmetic --- src/pyelliptic/tests/test_arithmetic.py | 38 +++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/pyelliptic/tests/test_arithmetic.py b/src/pyelliptic/tests/test_arithmetic.py index 7b5c59b15f..dbbceedaff 100644 --- a/src/pyelliptic/tests/test_arithmetic.py +++ b/src/pyelliptic/tests/test_arithmetic.py @@ -23,6 +23,12 @@ sample_privencryptionkey = \ b'4b0b73a54e19b059dc274ab69df095fe699f43b17397bca26fdf40f4d7400a3a' +# [chan] bitmessage +sample_wif_privsigningkey = \ + b'a2e8b841a531c1c558ee0680c396789c7a2ea3ac4795ae3f000caf9fe367d144' +sample_wif_privencryptionkey = \ + b'114ec0e2dca24a826a0eed064b0405b0ac148abc3b1d52729697f4d7b873fdc6' + sample_factor = \ 66858749573256452658262553961707680376751171096153613379801854825275240965733 # G * sample_factor @@ -40,6 +46,38 @@ def test_base10_multiply(self): sample_point, arithmetic.base10_multiply(arithmetic.G, sample_factor)) + def test_base58(self): + """Test encoding/decoding base58 using arithmetic functions""" + self.assertEqual( + arithmetic.decode(arithmetic.changebase( + b'2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK', 58, 256), 256), + 25152821841976547050350277460563089811513157529113201589004) + self.assertEqual( + arithmetic.decode(arithmetic.changebase( + b'2DBPTgeSawWYZceFD69AbDT5q4iUWtj1ZN', 58, 256), 256), + 18875720106589866286514488037355423395410802084648916523381) + self.assertEqual( + arithmetic.changebase(arithmetic.encode( + 25152821841976547050350277460563089811513157529113201589004, + 256), 256, 58), b'2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK') + self.assertEqual( + arithmetic.changebase(arithmetic.encode( + 18875720106589866286514488037355423395410802084648916523381, + 256), 256, 58), b'2DBPTgeSawWYZceFD69AbDT5q4iUWtj1ZN') + + def test_wif(self): + """Decode WIFs of [chan] bitmessage and check the keys""" + self.assertEqual( + sample_wif_privsigningkey, + arithmetic.changebase(arithmetic.changebase( + b'5K42shDERM5g7Kbi3JT5vsAWpXMqRhWZpX835M2pdSoqQQpJMYm', 58, 256 + )[1:-4], 256, 16)) + self.assertEqual( + sample_wif_privencryptionkey, + arithmetic.changebase(arithmetic.changebase( + b'5HwugVWm31gnxtoYcvcK7oywH2ezYTh6Y4tzRxsndAeMi6NHqpA', 58, 256 + )[1:-4], 256, 16)) + def test_decode(self): """Decode sample privsigningkey from hex to int and compare to factor""" self.assertEqual( From 07f815c28bfa418dd7f6802046f1ed410b876b99 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Thu, 29 Jul 2021 16:33:33 +0300 Subject: [PATCH 04/14] Add a test for WIF decoding --- src/tests/samples.py | 7 +++++++ src/tests/test_addresses.py | 16 ++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/tests/samples.py b/src/tests/samples.py index fd7393e175..57510c3f19 100644 --- a/src/tests/samples.py +++ b/src/tests/samples.py @@ -56,3 +56,10 @@ sample_sig_sha1 = unhexlify( '304502203b50123af78b4e40f5f819ae5b8786f48826e56d0f3e65744708a493f5b65de1' '0221009ddce2981ea143c0ac70404a535327e774adce8eebbae2d35104f1d326255f9a') + + +# [chan] bitmessage +sample_wif_privsigningkey = unhexlify( + b'a2e8b841a531c1c558ee0680c396789c7a2ea3ac4795ae3f000caf9fe367d144') +sample_wif_privencryptionkey = unhexlify( + b'114ec0e2dca24a826a0eed064b0405b0ac148abc3b1d52729697f4d7b873fdc6') diff --git a/src/tests/test_addresses.py b/src/tests/test_addresses.py index 8f9c283dba..43b4884fc9 100644 --- a/src/tests/test_addresses.py +++ b/src/tests/test_addresses.py @@ -2,12 +2,13 @@ import unittest from binascii import unhexlify -from pybitmessage import addresses +from pybitmessage import addresses, shared from .samples import ( sample_address, sample_daddr3_512, sample_daddr4_512, sample_deterministic_addr4, sample_deterministic_addr3, - sample_deterministic_ripe, sample_ripe) + sample_deterministic_ripe, sample_ripe, + sample_wif_privsigningkey, sample_wif_privencryptionkey) sample_addr3 = sample_deterministic_addr3.split('-')[1] sample_addr4 = sample_deterministic_addr4.split('-')[1] @@ -59,3 +60,14 @@ def test_base58(self): sample_addr4, addresses.encodeBase58(sample_daddr4_512)) self.assertEqual( sample_addr3, addresses.encodeBase58(sample_daddr3_512)) + + def test_wif(self): + """Decode WIFs of [chan] bitmessage and check the keys""" + self.assertEqual( + sample_wif_privsigningkey, + shared.decodeWalletImportFormat( + b'5K42shDERM5g7Kbi3JT5vsAWpXMqRhWZpX835M2pdSoqQQpJMYm')) + self.assertEqual( + sample_wif_privencryptionkey, + shared.decodeWalletImportFormat( + b'5HwugVWm31gnxtoYcvcK7oywH2ezYTh6Y4tzRxsndAeMi6NHqpA')) From ba2d0e26878a3e43d8771cd3a998d940863290fc Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Thu, 29 Jul 2021 22:16:37 +0300 Subject: [PATCH 05/14] Start adding hashes with double SHA512 --- src/addresses.py | 31 +++++----------- src/api.py | 8 +++-- src/class_addressGenerator.py | 4 +-- src/class_objectProcessor.py | 20 +++++------ src/class_singleWorker.py | 68 ++++++++++++++++------------------- src/highlevelcrypto.py | 13 +++++++ src/network/bmobject.py | 2 +- src/proofofwork.py | 22 +++++++----- src/protocol.py | 7 ++-- src/shared.py | 11 +++--- 10 files changed, 91 insertions(+), 95 deletions(-) diff --git a/src/addresses.py b/src/addresses.py index e48873a1c2..885c1f649f 100644 --- a/src/addresses.py +++ b/src/addresses.py @@ -2,11 +2,16 @@ Operations with addresses """ # pylint: disable=inconsistent-return-statements -import hashlib + import logging from binascii import hexlify, unhexlify from struct import pack, unpack +try: + from highlevelcrypto import double_sha512 +except ImportError: + from .highlevelcrypto import double_sha512 + logger = logging.getLogger('default') @@ -134,15 +139,6 @@ def decodeVarint(data): return (encodedValue, 9) -def calculateInventoryHash(data): - """Calculate inventory hash from object data""" - sha = hashlib.new('sha512') - sha2 = hashlib.new('sha512') - sha.update(data) - sha2.update(sha.digest()) - return sha2.digest()[0:32] - - def encodeAddress(version, stream, ripe): """Convert ripe to address""" if version >= 2 and version < 4: @@ -166,12 +162,7 @@ def encodeAddress(version, stream, ripe): storedBinaryData = encodeVarint(version) + encodeVarint(stream) + ripe # Generate the checksum - sha = hashlib.new('sha512') - sha.update(storedBinaryData) - currentHash = sha.digest() - sha = hashlib.new('sha512') - sha.update(currentHash) - checksum = sha.digest()[0:4] + checksum = double_sha512(storedBinaryData)[0:4] # FIXME: encodeBase58 should take binary data, to reduce conversions # encodeBase58(storedBinaryData + checksum) @@ -207,13 +198,7 @@ def decodeAddress(address): data = unhexlify(hexdata) checksum = data[-4:] - sha = hashlib.new('sha512') - sha.update(data[:-4]) - currentHash = sha.digest() - sha = hashlib.new('sha512') - sha.update(currentHash) - - if checksum != sha.digest()[0:4]: + if checksum != double_sha512(data[:-4])[0:4]: status = 'checksumfailed' return status, 0, 0, '' diff --git a/src/api.py b/src/api.py index de220cc411..536768e89c 100644 --- a/src/api.py +++ b/src/api.py @@ -71,6 +71,8 @@ from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer from struct import pack +from six.moves import queue + import defaults import helper_inbox import helper_sent @@ -82,17 +84,17 @@ import state from addresses import ( addBMIfNotPresent, - calculateInventoryHash, decodeAddress, decodeVarint, varintDecodeError ) from bmconfigparser import BMConfigParser from debug import logger -from helper_sql import SqlBulkExecute, sqlExecute, sqlQuery, sqlStoredProcedure, sql_ready +from helper_sql import ( + SqlBulkExecute, sqlExecute, sqlQuery, sql_ready, sqlStoredProcedure) +from highlevelcrypto import calculateInventoryHash from inventory import Inventory from network.threads import StoppableThread -from six.moves import queue from version import softwareVersion try: # TODO: write tests for XML vulnerabilities diff --git a/src/class_addressGenerator.py b/src/class_addressGenerator.py index 25b0c5df5b..f4a0f00858 100644 --- a/src/class_addressGenerator.py +++ b/src/class_addressGenerator.py @@ -367,10 +367,10 @@ def run(self): highlevelcrypto.makeCryptor( hexlify(potentialPrivEncryptionKey)) shared.myAddressesByHash[ripe] = address - tag = hashlib.sha512(hashlib.sha512( + tag = highlevelcrypto.double_sha512( encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + ripe - ).digest()).digest()[32:] + )[32:] shared.myAddressesByTag[tag] = address if addressVersionNumber == 3: # If this is a chan address, diff --git a/src/class_objectProcessor.py b/src/class_objectProcessor.py index 1bacf63947..86ed2bcc24 100644 --- a/src/class_objectProcessor.py +++ b/src/class_objectProcessor.py @@ -23,7 +23,7 @@ import shared import state from addresses import ( - calculateInventoryHash, decodeAddress, decodeVarint, + decodeAddress, decodeVarint, encodeAddress, encodeVarint, varintDecodeError ) from bmconfigparser import BMConfigParser @@ -450,7 +450,7 @@ def processmsg(self, data): streamNumberAsClaimedByMsg, streamNumberAsClaimedByMsgLength = \ decodeVarint(data[readPosition:readPosition + 9]) readPosition += streamNumberAsClaimedByMsgLength - inventoryHash = calculateInventoryHash(data) + inventoryHash = highlevelcrypto.calculateInventoryHash(data) initialDecryptionSuccessful = False # This is not an acknowledgement bound for me. See if it is a message @@ -580,8 +580,7 @@ def processmsg(self, data): helper_bitcoin.calculateTestnetAddressFromPubkey(pubSigningKey) ) # Used to detect and ignore duplicate messages in our inbox - sigHash = hashlib.sha512( - hashlib.sha512(signature).digest()).digest()[32:] + sigHash = highlevelcrypto.double_sha512(signature)[32:] # calculate the fromRipe. sha = hashlib.new('sha512') @@ -751,7 +750,7 @@ def processbroadcast(self, data): state.numberOfBroadcastsProcessed += 1 queues.UISignalQueue.put(( 'updateNumberOfBroadcastsProcessed', 'no data')) - inventoryHash = calculateInventoryHash(data) + inventoryHash = highlevelcrypto.calculateInventoryHash(data) readPosition = 20 # bypass the nonce, time, and object type broadcastVersion, broadcastVersionLength = decodeVarint( data[readPosition:readPosition + 9]) @@ -885,10 +884,10 @@ def processbroadcast(self, data): ' itself. Ignoring message.' ) elif broadcastVersion == 5: - calculatedTag = hashlib.sha512(hashlib.sha512( + calculatedTag = highlevelcrypto.double_sha512( encodeVarint(sendersAddressVersion) + encodeVarint(sendersStream) + calculatedRipe - ).digest()).digest()[32:] + )[32:] if calculatedTag != embeddedTag: return logger.debug( 'The tag and encryption key used to encrypt this' @@ -918,8 +917,7 @@ def processbroadcast(self, data): return logger.debug('ECDSA verify passed') # Used to detect and ignore duplicate messages in our inbox - sigHash = hashlib.sha512( - hashlib.sha512(signature).digest()).digest()[32:] + sigHash = highlevelcrypto.double_sha512(signature)[32:] fromAddress = encodeAddress( sendersAddressVersion, sendersStream, calculatedRipe) @@ -993,10 +991,10 @@ def possibleNewPubkey(self, address): # Let us create the tag from the address and see if we were waiting # for it. elif addressVersion >= 4: - tag = hashlib.sha512(hashlib.sha512( + tag = highlevelcrypto.double_sha512( encodeVarint(addressVersion) + encodeVarint(streamNumber) + ripe - ).digest()).digest()[32:] + )[32:] if tag in state.neededPubkeys: del state.neededPubkeys[tag] self.sendMessages(address) diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index 49c41c077d..23631d7153 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -25,9 +25,7 @@ import shared import state import tr -from addresses import ( - calculateInventoryHash, decodeAddress, decodeVarint, encodeVarint -) +from addresses import decodeAddress, decodeVarint, encodeVarint from bmconfigparser import BMConfigParser from helper_sql import sqlExecute, sqlQuery from inventory import Inventory @@ -75,18 +73,16 @@ def run(self): queryreturn = sqlQuery( '''SELECT DISTINCT toaddress FROM sent''' ''' WHERE (status='awaitingpubkey' AND folder='sent')''') - for row in queryreturn: - toAddress, = row - # toStatus - _, toAddressVersionNumber, toStreamNumber, toRipe = \ - decodeAddress(toAddress) + for toAddress, in queryreturn: + toAddressVersionNumber, toStreamNumber, toRipe = \ + decodeAddress(toAddress)[1:] if toAddressVersionNumber <= 3: state.neededPubkeys[toAddress] = 0 elif toAddressVersionNumber >= 4: - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( + doubleHashOfAddressData = highlevelcrypto.double_sha512( encodeVarint(toAddressVersionNumber) + encodeVarint(toStreamNumber) + toRipe - ).digest()).digest() + ) # Note that this is the first half of the sha512 hash. privEncryptionKey = doubleHashOfAddressData[:32] tag = doubleHashOfAddressData[32:] @@ -290,7 +286,7 @@ def doPOWForMyV2Pubkey(self, adressHash): payload = self._doPOWDefaults( payload, TTL, log_prefix='(For pubkey message)') - inventoryHash = calculateInventoryHash(payload) + inventoryHash = highlevelcrypto.calculateInventoryHash(payload) objectType = 1 Inventory()[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, '') @@ -379,7 +375,7 @@ def sendOutOrStoreMyV3Pubkey(self, adressHash): payload = self._doPOWDefaults( payload, TTL, log_prefix='(For pubkey message)') - inventoryHash = calculateInventoryHash(payload) + inventoryHash = highlevelcrypto.calculateInventoryHash(payload) objectType = 1 Inventory()[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, '') @@ -452,10 +448,10 @@ def sendOutOrStoreMyV4Pubkey(self, myAddress): # 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( + doubleHashOfAddressData = highlevelcrypto.double_sha512( encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + addressHash - ).digest()).digest() + ) payload += doubleHashOfAddressData[32:] # the tag signature = highlevelcrypto.sign( payload + dataToEncrypt, privSigningKeyHex, self.digestAlg) @@ -471,7 +467,7 @@ def sendOutOrStoreMyV4Pubkey(self, myAddress): payload = self._doPOWDefaults( payload, TTL, log_prefix='(For pubkey message)') - inventoryHash = calculateInventoryHash(payload) + inventoryHash = highlevelcrypto.calculateInventoryHash(payload) objectType = 1 Inventory()[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, @@ -507,7 +503,7 @@ def sendOnionPeerObj(self, peer=None): objectType = protocol.OBJECT_ONIONPEER # FIXME: ideally the objectPayload should be signed objectPayload = encodeVarint(peer.port) + protocol.encodeHost(peer.host) - tag = calculateInventoryHash(objectPayload) + tag = highlevelcrypto.calculateInventoryHash(objectPayload) if Inventory().by_type_and_tag(objectType, tag): return # not expired @@ -521,7 +517,7 @@ def sendOnionPeerObj(self, peer=None): payload = self._doPOWDefaults( payload, TTL, log_prefix='(For onionpeer object)') - inventoryHash = calculateInventoryHash(payload) + inventoryHash = highlevelcrypto.calculateInventoryHash(payload) Inventory()[inventoryHash] = ( objectType, streamNumber, buffer(payload), embeddedTime, buffer(tag) @@ -615,10 +611,10 @@ def sendBroadcast(self): payload += encodeVarint(streamNumber) if addressVersionNumber >= 4: - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( + doubleHashOfAddressData = highlevelcrypto.double_sha512( encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + ripe - ).digest()).digest() + ) tag = doubleHashOfAddressData[32:] payload += tag else: @@ -688,7 +684,7 @@ def sendBroadcast(self): ) continue - inventoryHash = calculateInventoryHash(payload) + inventoryHash = highlevelcrypto.calculateInventoryHash(payload) objectType = 3 Inventory()[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, tag) @@ -797,10 +793,10 @@ def sendMsg(self): if toAddressVersionNumber <= 3: toTag = '' else: - toTag = hashlib.sha512(hashlib.sha512( + toTag = highlevelcrypto.double_sha512( encodeVarint(toAddressVersionNumber) + encodeVarint(toStreamNumber) + toRipe - ).digest()).digest()[32:] + )[32:] if toaddress in state.neededPubkeys or \ toTag in state.neededPubkeys: # We already sent a request for the pubkey @@ -834,11 +830,11 @@ def sendMsg(self): # already contains the toAddress and cryptor # object associated with the tag for this toAddress. if toAddressVersionNumber >= 4: - doubleHashOfToAddressData = hashlib.sha512( - hashlib.sha512( - encodeVarint(toAddressVersionNumber) + encodeVarint(toStreamNumber) + toRipe - ).digest() - ).digest() + doubleHashOfToAddressData = \ + highlevelcrypto.double_sha512( + encodeVarint(toAddressVersionNumber) + + encodeVarint(toStreamNumber) + toRipe + ) # The first half of the sha512 hash. privEncryptionKey = doubleHashOfToAddressData[:32] # The second half of the sha512 hash. @@ -1304,7 +1300,7 @@ def sendMsg(self): ) continue - inventoryHash = calculateInventoryHash(encryptedPayload) + inventoryHash = highlevelcrypto.calculateInventoryHash(encryptedPayload) objectType = 2 Inventory()[inventoryHash] = ( objectType, toStreamNumber, encryptedPayload, embeddedTime, '') @@ -1354,8 +1350,7 @@ def sendMsg(self): # the message in our own inbox. if BMConfigParser().has_section(toaddress): # Used to detect and ignore duplicate messages in our inbox - sigHash = hashlib.sha512(hashlib.sha512( - signature).digest()).digest()[32:] + sigHash = highlevelcrypto.double_sha512(signature)[32:] t = (inventoryHash, toaddress, fromaddress, subject, int( time.time()), message, 'inbox', encoding, 0, sigHash) helper_inbox.insert(t) @@ -1410,16 +1405,13 @@ def requestPubKey(self, toAddress): # neededPubkeys dictionary. But if we are recovering # from a restart of the client then we have to put it in now. - # Note that this is the first half of the sha512 hash. - privEncryptionKey = hashlib.sha512(hashlib.sha512( + doubleHashOfAddressData = highlevelcrypto.double_sha512( encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + ripe - ).digest()).digest()[:32] + ) + privEncryptionKey = doubleHashOfAddressData[:32] # Note that this is the second half of the sha512 hash. - tag = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + ripe - ).digest()).digest()[32:] + tag = doubleHashOfAddressData[32:] if tag not in state.neededPubkeys: # We'll need this for when we receive a pubkey reply: # it will be encrypted and we'll need to decrypt it. @@ -1462,7 +1454,7 @@ def requestPubKey(self, toAddress): payload = self._doPOWDefaults(payload, TTL) - inventoryHash = calculateInventoryHash(payload) + inventoryHash = highlevelcrypto.calculateInventoryHash(payload) objectType = 1 Inventory()[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, '') diff --git a/src/highlevelcrypto.py b/src/highlevelcrypto.py index 9a31ad972e..3c084b1245 100644 --- a/src/highlevelcrypto.py +++ b/src/highlevelcrypto.py @@ -7,6 +7,7 @@ `More discussion. `_ """ +import hashlib from binascii import hexlify import pyelliptic @@ -17,6 +18,18 @@ __all__ = ['encrypt', 'makeCryptor', 'pointMult', 'privToPub', 'sign', 'verify'] +# Hashes + +def double_sha512(data): + """Binary double SHA512 digest""" + return hashlib.sha512(hashlib.sha512(data).digest()).digest() + + +def calculateInventoryHash(data): + """Calculate inventory hash from object data""" + return double_sha512(data)[:32] + + def makeCryptor(privkey): """Return a private `.pyelliptic.ECC` instance""" private_key = a.changebase(privkey, 16, 256, minlen=32) diff --git a/src/network/bmobject.py b/src/network/bmobject.py index 12b997d777..49e3c2de13 100644 --- a/src/network/bmobject.py +++ b/src/network/bmobject.py @@ -6,7 +6,7 @@ import protocol import state -from addresses import calculateInventoryHash +from highlevelcrypto import calculateInventoryHash from inventory import Inventory from network.dandelion import Dandelion diff --git a/src/proofofwork.py b/src/proofofwork.py index 148d6734d4..69b04e2d92 100644 --- a/src/proofofwork.py +++ b/src/proofofwork.py @@ -4,7 +4,6 @@ """ import ctypes -import hashlib import os import sys import tempfile @@ -12,6 +11,7 @@ from struct import pack, unpack from subprocess import call +import highlevelcrypto import openclpow import paths import queues @@ -87,13 +87,20 @@ def _set_idle(): pass +def trial_value(nonce, initialHash): + """Calculate PoW trial value""" + trialValue, = unpack( + '>Q', highlevelcrypto.double_sha512( + pack('>Q', nonce) + initialHash)[0:8]) + return trialValue + + def _pool_worker(nonce, initialHash, target, pool_size): _set_idle() trialValue = float('inf') while trialValue > target: nonce += pool_size - trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512( - pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) + trialValue = trial_value(nonce, initialHash) return [trialValue, nonce] @@ -103,10 +110,9 @@ def _doSafePoW(target, initialHash): trialValue = float('inf') while trialValue > target and state.shutdown == 0: nonce += 1 - trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512( - pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) + trialValue = trial_value(nonce, initialHash) if state.shutdown != 0: - raise StopIteration("Interrupted") # pylint: misplaced-bare-raise + raise StopIteration("Interrupted") logger.debug("Safe PoW done") return [trialValue, nonce] @@ -163,7 +169,7 @@ def _doCPoW(target, initialHash): logger.debug("C PoW start") nonce = bmpow(out_h, out_m) - trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512(pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) + trialValue = trial_value(nonce, initialHash) if state.shutdown != 0: raise StopIteration("Interrupted") logger.debug("C PoW done") @@ -173,7 +179,7 @@ def _doCPoW(target, initialHash): def _doGPUPoW(target, initialHash): logger.debug("GPU PoW start") nonce = openclpow.do_opencl_pow(initialHash.encode("hex"), target) - trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512(pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) + trialValue = trial_value(nonce, initialHash) if trialValue > target: deviceNames = ", ".join(gpu.name for gpu in openclpow.enabledGpus) queues.UISignalQueue.put(( diff --git a/src/protocol.py b/src/protocol.py index 1934d9cc6c..ac00c1e293 100644 --- a/src/protocol.py +++ b/src/protocol.py @@ -269,12 +269,11 @@ def isProofOfWorkSufficient( if payloadLengthExtraBytes < defaults.networkDefaultPayloadLengthExtraBytes: payloadLengthExtraBytes = defaults.networkDefaultPayloadLengthExtraBytes endOfLifeTime, = unpack('>Q', data[8:16]) - TTL = endOfLifeTime - (int(recvTime) if recvTime else int(time.time())) + TTL = endOfLifeTime - int(recvTime if recvTime else time.time()) if TTL < 300: TTL = 300 - POW, = unpack('>Q', hashlib.sha512(hashlib.sha512( - data[:8] + hashlib.sha512(data[8:]).digest() - ).digest()).digest()[0:8]) + POW, = unpack('>Q', highlevelcrypto.double_sha512( + data[:8] + hashlib.sha512(data[8:]).digest())[0:8]) return POW <= 2 ** 64 / ( nonceTrialsPerByte * ( len(data) + payloadLengthExtraBytes diff --git a/src/shared.py b/src/shared.py index 4a654932fc..8adb73b229 100644 --- a/src/shared.py +++ b/src/shared.py @@ -121,7 +121,8 @@ def reloadMyAddressHashes(): if isEnabled: hasEnabledKeys = True # status - addressVersionNumber, streamNumber, hashobj = decodeAddress(addressInKeysFile)[1:] + addressVersionNumber, streamNumber, hashobj = decodeAddress( + addressInKeysFile)[1:] if addressVersionNumber in (2, 3, 4): # Returns a simple 32 bytes of information encoded # in 64 Hex characters, or null if there was an error. @@ -132,9 +133,9 @@ def reloadMyAddressHashes(): myECCryptorObjects[hashobj] = \ highlevelcrypto.makeCryptor(privEncryptionKey) myAddressesByHash[hashobj] = addressInKeysFile - tag = hashlib.sha512(hashlib.sha512( + tag = highlevelcrypto.double_sha512( encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + hashobj).digest()).digest()[32:] + + encodeVarint(streamNumber) + hashobj)[32:] myAddressesByTag[tag] = addressInKeysFile else: logger.error( @@ -174,10 +175,10 @@ def reloadBroadcastSendersForWhichImWatching(): MyECSubscriptionCryptorObjects[hashobj] = \ highlevelcrypto.makeCryptor(hexlify(privEncryptionKey)) else: - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( + doubleHashOfAddressData = highlevelcrypto.double_sha512( encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + hashobj - ).digest()).digest() + ) tag = doubleHashOfAddressData[32:] privEncryptionKey = doubleHashOfAddressData[:32] MyECSubscriptionCryptorObjects[tag] = \ From 15039cea9400ad225019846a09b999dc974d6d04 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Thu, 29 Jul 2021 22:18:16 +0300 Subject: [PATCH 06/14] The test --- src/tests/samples.py | 5 +++++ src/tests/test_crypto.py | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/src/tests/samples.py b/src/tests/samples.py index 57510c3f19..8efae59793 100644 --- a/src/tests/samples.py +++ b/src/tests/samples.py @@ -2,6 +2,11 @@ from binascii import unhexlify +# hello, page 1 of the Specification +sample_double_sha512 = unhexlify( + '0592a10584ffabf96539f3d780d776828c67da1ab5b169e9e8aed838aaecc9ed36d49ff14' + '23c55f019e050c66c6324f53588be88894fef4dcffdb74b98e2b200') + magic = 0xE9BEB4D9 diff --git a/src/tests/test_crypto.py b/src/tests/test_crypto.py index e5bb600adc..3563098d34 100644 --- a/src/tests/test_crypto.py +++ b/src/tests/test_crypto.py @@ -16,6 +16,7 @@ RIPEMD = None from .samples import ( + sample_double_sha512, sample_msg, sample_pubsigningkey, sample_pubencryptionkey, sample_privsigningkey, sample_privencryptionkey, sample_ripe, sample_sig, sample_sig_sha1 @@ -68,6 +69,11 @@ class TestHighlevelcrypto(unittest.TestCase): # self.assertEqual( # highlevelcrypto.sign(sample_msg, sample_privsigningkey), sample_sig) + def test_double_sha512(self): + """Reproduce the example on page 1 of the Specification""" + self.assertEqual( + highlevelcrypto.double_sha512(b'hello'), sample_double_sha512) + def test_verify(self): """Verify sample signatures and newly generated ones""" pubkey_hex = hexlify(sample_pubsigningkey) From 8942f63d0611f108076bfef406454fc0313c0a2f Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Fri, 6 Aug 2021 15:44:35 +0300 Subject: [PATCH 07/14] Moved decodeWalletImportFormat() from shared to highlevelcrypto, not addresses, where it's supposed to be because it uses pyelliptic.arithmetic, addresses.decodeBase58() returns int which needs to be encoded. Defined encodeWalletImportFormat() and replaced all uses. --- src/class_addressGenerator.py | 40 ++++------------ src/class_singleWorker.py | 76 ++++++++++++++---------------- src/highlevelcrypto.py | 29 ++++++++++++ src/shared.py | 88 +++++++++++++---------------------- 4 files changed, 108 insertions(+), 125 deletions(-) diff --git a/src/class_addressGenerator.py b/src/class_addressGenerator.py index f4a0f00858..4a2c20ec21 100644 --- a/src/class_addressGenerator.py +++ b/src/class_addressGenerator.py @@ -15,7 +15,6 @@ from bmconfigparser import BMConfigParser from fallback import RIPEMD160Hash from network import StoppableThread -from pyelliptic import arithmetic from pyelliptic.openssl import OpenSSL from six.moves import configparser, queue @@ -163,20 +162,10 @@ def run(self): address = encodeAddress( addressVersionNumber, streamNumber, ripe) - # 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 = b'\x80' + potentialPrivSigningKey - checksum = hashlib.sha256(hashlib.sha256( - privSigningKey).digest()).digest()[0:4] - privSigningKeyWIF = arithmetic.changebase( - privSigningKey + checksum, 256, 58) - - privEncryptionKey = b'\x80' + potentialPrivEncryptionKey - checksum = hashlib.sha256(hashlib.sha256( - privEncryptionKey).digest()).digest()[0:4] - privEncryptionKeyWIF = arithmetic.changebase( - privEncryptionKey + checksum, 256, 58) + privSigningKeyWIF = highlevelcrypto.encodeWalletImportFormat( + potentialPrivSigningKey) + privEncryptionKeyWIF = highlevelcrypto.encodeWalletImportFormat( + potentialPrivEncryptionKey) BMConfigParser().add_section(address) BMConfigParser().set(address, 'label', label) @@ -300,21 +289,12 @@ def run(self): saveAddressToDisk = False if saveAddressToDisk and live: - # 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 = b'\x80' + potentialPrivSigningKey - checksum = hashlib.sha256(hashlib.sha256( - privSigningKey).digest()).digest()[0:4] - privSigningKeyWIF = arithmetic.changebase( - privSigningKey + checksum, 256, 58) - - privEncryptionKey = b'\x80' + \ - potentialPrivEncryptionKey - checksum = hashlib.sha256(hashlib.sha256( - privEncryptionKey).digest()).digest()[0:4] - privEncryptionKeyWIF = arithmetic.changebase( - privEncryptionKey + checksum, 256, 58) + privSigningKeyWIF = \ + highlevelcrypto.encodeWalletImportFormat( + potentialPrivSigningKey) + privEncryptionKeyWIF = \ + highlevelcrypto.encodeWalletImportFormat( + potentialPrivEncryptionKey) try: BMConfigParser().add_section(address) diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index 23631d7153..c323010711 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -193,15 +193,20 @@ def run(self): self.logger.info("Quitting...") def _getKeysForAddress(self, address): - privSigningKeyBase58 = BMConfigParser().get( - address, 'privsigningkey') - privEncryptionKeyBase58 = BMConfigParser().get( - address, 'privencryptionkey') + try: + privSigningKeyBase58 = BMConfigParser().get( + address, 'privsigningkey') + privEncryptionKeyBase58 = BMConfigParser().get( + address, 'privencryptionkey') + except (configparser.NoSectionError, configparser.NoOptionError): + self.logger.error( + 'Could not read or decode privkey for address %s', address) + raise ValueError - privSigningKeyHex = hexlify(shared.decodeWalletImportFormat( - privSigningKeyBase58)) - privEncryptionKeyHex = hexlify(shared.decodeWalletImportFormat( - privEncryptionKeyBase58)) + privSigningKeyHex = hexlify( + highlevelcrypto.decodeWalletImportFormat(privSigningKeyBase58)) + privEncryptionKeyHex = hexlify( + highlevelcrypto.decodeWalletImportFormat(privEncryptionKeyBase58)) # The \x04 on the beginning of the public keys are not sent. # This way there is only one acceptable way to encode @@ -252,9 +257,7 @@ def doPOWForMyV2Pubkey(self, adressHash): message once it is done with the POW""" # Look up my stream number based on my address hash myAddress = shared.myAddressesByHash[adressHash] - # status - _, addressVersionNumber, streamNumber, adressHash = ( - decodeAddress(myAddress)) + addressVersionNumber, streamNumber = decodeAddress(myAddress)[1:3] # 28 days from now plus or minus five minutes TTL = int(28 * 24 * 60 * 60 + helper_random.randomrandrange(-300, 300)) @@ -267,18 +270,15 @@ def doPOWForMyV2Pubkey(self, adressHash): payload += protocol.getBitfield(myAddress) try: - # privSigningKeyHex, privEncryptionKeyHex - _, _, pubSigningKey, pubEncryptionKey = \ - self._getKeysForAddress(myAddress) - except (configparser.NoSectionError, configparser.NoOptionError) as err: - self.logger.warning("Section or Option did not found: %s", err) - except Exception as err: - self.logger.error( + pubSigningKey, pubEncryptionKey = self._getKeysForAddress( + myAddress)[2:] + except ValueError: + return + except Exception: + return self.logger.error( 'Error within doPOWForMyV2Pubkey. Could not read' ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) - return + ' address. %s\n', exc_info=True) payload += pubSigningKey + pubEncryptionKey @@ -316,9 +316,8 @@ def sendOutOrStoreMyV3Pubkey(self, adressHash): try: myAddress = shared.myAddressesByHash[adressHash] except KeyError: - # The address has been deleted. - self.logger.warning("Can't find %s in myAddressByHash", hexlify(adressHash)) - return + return self.logger.warning( # The address has been deleted. + "Can't find %s in myAddressByHash", hexlify(adressHash)) if BMConfigParser().safeGetBoolean(myAddress, 'chan'): self.logger.info('This is a chan address. Not sending pubkey.') return @@ -349,15 +348,13 @@ def sendOutOrStoreMyV3Pubkey(self, adressHash): # , privEncryptionKeyHex privSigningKeyHex, _, pubSigningKey, pubEncryptionKey = \ self._getKeysForAddress(myAddress) - except (configparser.NoSectionError, configparser.NoOptionError) as err: - self.logger.warning("Section or Option did not found: %s", err) - except Exception as err: - self.logger.error( + except ValueError: + return + except Exception: + return self.logger.error( 'Error within sendOutOrStoreMyV3Pubkey. Could not read' ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) - return + ' address. %s\n', exc_info=True) payload += pubSigningKey + pubEncryptionKey @@ -424,15 +421,13 @@ def sendOutOrStoreMyV4Pubkey(self, myAddress): # , privEncryptionKeyHex privSigningKeyHex, _, pubSigningKey, pubEncryptionKey = \ self._getKeysForAddress(myAddress) - except (configparser.NoSectionError, configparser.NoOptionError) as err: - self.logger.warning("Section or Option did not found: %s", err) - except Exception as err: - self.logger.error( + except ValueError: + return + except Exception: + return self.logger.error( 'Error within sendOutOrStoreMyV4Pubkey. Could not read' ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) - return + ' address. %s\n', exc_info=True) dataToEncrypt += pubSigningKey + pubEncryptionKey @@ -1114,8 +1109,9 @@ def sendMsg(self): ' from the keys.dat file for our own address. %s\n', err) continue - privEncryptionKeyHex = hexlify(shared.decodeWalletImportFormat( - privEncryptionKeyBase58)) + privEncryptionKeyHex = hexlify( + highlevelcrypto.decodeWalletImportFormat( + privEncryptionKeyBase58)) pubEncryptionKeyBase256 = unhexlify(highlevelcrypto.privToPub( privEncryptionKeyHex))[1:] requiredAverageProofOfWorkNonceTrialsPerByte = \ diff --git a/src/highlevelcrypto.py b/src/highlevelcrypto.py index 3c084b1245..6cfd39538a 100644 --- a/src/highlevelcrypto.py +++ b/src/highlevelcrypto.py @@ -30,6 +30,35 @@ def calculateInventoryHash(data): return double_sha512(data)[:32] +# WIF (uses arithmetic ): +def decodeWalletImportFormat(WIFstring): + """ + Convert private key from base58 that's used in the config file to + 8-bit binary string. + """ + fullString = a.changebase(WIFstring, 58, 256) + privkey = fullString[:-4] + if fullString[-4:] != \ + hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]: + raise ValueError('Checksum failed') + elif privkey[0:1] == b'\x80': # checksum passed + return privkey[1:] + + raise ValueError('No hex 80 prefix') + + +# 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 +def encodeWalletImportFormat(privKey): + """ + Convert private key from binary 8-bit string into base58check WIF string. + """ + privKey = b'\x80' + privKey + checksum = hashlib.sha256(hashlib.sha256(privKey).digest()).digest()[0:4] + return a.changebase(privKey + checksum, 256, 58) + + def makeCryptor(privkey): """Return a private `.pyelliptic.ECC` instance""" private_key = a.changebase(privkey, 16, 256, minlen=32) diff --git a/src/shared.py b/src/shared.py index 8adb73b229..3ac4919985 100644 --- a/src/shared.py +++ b/src/shared.py @@ -23,8 +23,6 @@ from debug import logger from helper_sql import sqlQuery -from pyelliptic import arithmetic - myECCryptorObjects = {} MyECSubscriptionCryptorObjects = {} @@ -76,35 +74,6 @@ def isAddressInMyAddressBookSubscriptionsListOrWhitelist(address): return False -def decodeWalletImportFormat(WIFstring): - # pylint: disable=inconsistent-return-statements - """ - Convert private key from base58 that's used in the config file to - 8-bit binary string - """ - fullString = arithmetic.changebase(WIFstring, 58, 256) - privkey = fullString[:-4] - if fullString[-4:] != \ - hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]: - logger.critical( - 'Major problem! When trying to decode one of your' - ' private keys, the checksum failed. Here are the first' - ' 6 characters of the PRIVATE key: %s', - str(WIFstring)[:6] - ) - os._exit(0) # pylint: disable=protected-access - # return "" - elif privkey[0] == '\x80': # checksum passed - return privkey[1:] - - logger.critical( - 'Major problem! When trying to decode one of your private keys,' - ' the checksum passed but the key doesn\'t begin with hex 80.' - ' Here is the PRIVATE key: %s', WIFstring - ) - os._exit(0) # pylint: disable=protected-access - - def reloadMyAddressHashes(): """Reload keys for user's addresses from the config file""" logger.debug('reloading keys from keys.dat file') @@ -118,30 +87,39 @@ def reloadMyAddressHashes(): hasEnabledKeys = False for addressInKeysFile in BMConfigParser().addresses(): isEnabled = BMConfigParser().getboolean(addressInKeysFile, 'enabled') - if isEnabled: - hasEnabledKeys = True - # status - addressVersionNumber, streamNumber, hashobj = decodeAddress( - addressInKeysFile)[1:] - if addressVersionNumber in (2, 3, 4): - # Returns a simple 32 bytes of information encoded - # in 64 Hex characters, or null if there was an error. - privEncryptionKey = hexlify(decodeWalletImportFormat( - BMConfigParser().get(addressInKeysFile, 'privencryptionkey'))) - # It is 32 bytes encoded as 64 hex characters - if len(privEncryptionKey) == 64: - myECCryptorObjects[hashobj] = \ - highlevelcrypto.makeCryptor(privEncryptionKey) - myAddressesByHash[hashobj] = addressInKeysFile - tag = highlevelcrypto.double_sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + hashobj)[32:] - myAddressesByTag[tag] = addressInKeysFile - else: - logger.error( - 'Error in reloadMyAddressHashes: Can\'t handle' - ' address versions other than 2, 3, or 4.' - ) + if not isEnabled: + continue + + hasEnabledKeys = True + + addressVersionNumber, streamNumber, hashobj = decodeAddress( + addressInKeysFile)[1:] + if addressVersionNumber not in (2, 3, 4): + logger.error( + 'Error in reloadMyAddressHashes: Can\'t handle' + ' address versions other than 2, 3, or 4.') + continue + + # Returns a simple 32 bytes of information encoded in 64 Hex characters. + try: + privEncryptionKey = hexlify( + highlevelcrypto.decodeWalletImportFormat( + BMConfigParser().get(addressInKeysFile, 'privencryptionkey') + )) + except ValueError: + logger.error( + 'Error in reloadMyAddressHashes: failed to decode' + ' one of the private keys for address %s', addressInKeysFile) + continue + # It is 32 bytes encoded as 64 hex characters + if len(privEncryptionKey) == 64: + myECCryptorObjects[hashobj] = \ + highlevelcrypto.makeCryptor(privEncryptionKey) + myAddressesByHash[hashobj] = addressInKeysFile + tag = highlevelcrypto.double_sha512( + encodeVarint(addressVersionNumber) + + encodeVarint(streamNumber) + hashobj)[32:] + myAddressesByTag[tag] = addressInKeysFile if not keyfileSecure: fixSensitiveFilePermissions(os.path.join( From 659b292357fc638905792f7c1d77a73e9630e49c Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Thu, 29 Jul 2021 21:37:01 +0300 Subject: [PATCH 08/14] test_wif(): import from highlevelcrypto and add encoding checks --- src/tests/test_addresses.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/tests/test_addresses.py b/src/tests/test_addresses.py index 43b4884fc9..4c4a33d648 100644 --- a/src/tests/test_addresses.py +++ b/src/tests/test_addresses.py @@ -2,7 +2,7 @@ import unittest from binascii import unhexlify -from pybitmessage import addresses, shared +from pybitmessage import addresses, highlevelcrypto from .samples import ( sample_address, sample_daddr3_512, sample_daddr4_512, @@ -65,9 +65,21 @@ def test_wif(self): """Decode WIFs of [chan] bitmessage and check the keys""" self.assertEqual( sample_wif_privsigningkey, - shared.decodeWalletImportFormat( + highlevelcrypto.decodeWalletImportFormat( b'5K42shDERM5g7Kbi3JT5vsAWpXMqRhWZpX835M2pdSoqQQpJMYm')) self.assertEqual( sample_wif_privencryptionkey, - shared.decodeWalletImportFormat( + highlevelcrypto.decodeWalletImportFormat( b'5HwugVWm31gnxtoYcvcK7oywH2ezYTh6Y4tzRxsndAeMi6NHqpA')) + self.assertEqual( + b'5K42shDERM5g7Kbi3JT5vsAWpXMqRhWZpX835M2pdSoqQQpJMYm', + highlevelcrypto.encodeWalletImportFormat( + sample_wif_privsigningkey)) + self.assertEqual( + b'5HwugVWm31gnxtoYcvcK7oywH2ezYTh6Y4tzRxsndAeMi6NHqpA', + highlevelcrypto.encodeWalletImportFormat( + sample_wif_privencryptionkey)) + + with self.assertRaises(ValueError): + highlevelcrypto.decodeWalletImportFormat( + b'5HwugVWm31gnxtoYcvcK7oywH2ezYTh6Y4tzRxsndAeMi6NHq') From b80fb5a6755d06211828639a2301f956b0a6950f Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Wed, 1 Sep 2021 18:42:30 +0300 Subject: [PATCH 09/14] Use proofofwork.trial_value() in tests.test_openclpow --- src/tests/test_openclpow.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/tests/test_openclpow.py b/src/tests/test_openclpow.py index 341beec963..4770072e81 100644 --- a/src/tests/test_openclpow.py +++ b/src/tests/test_openclpow.py @@ -1,10 +1,10 @@ """ Tests for openclpow module """ -import hashlib + import unittest -from struct import pack, unpack -from pybitmessage import openclpow + +from pybitmessage import openclpow, proofofwork class TestOpenClPow(unittest.TestCase): @@ -25,7 +25,5 @@ def test_openclpow(self): "b93f3ffeba0ef2fd08a8dc2f87b68ae5a0dc819ab57f22ad2c4c9c8618a43b3" ).decode("hex") nonce = openclpow.do_opencl_pow(initialHash.encode("hex"), target_) - trialValue, = unpack( - '>Q', hashlib.sha512(hashlib.sha512( - pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) - self.assertLess((nonce - trialValue), target_) + self.assertLess( + nonce - proofofwork.trial_value(nonce, initialHash), target_) From f3ccc361fcac59c10eeea078122c31b9f5e0c669 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Thu, 9 Dec 2021 18:44:24 +0200 Subject: [PATCH 10/14] Move randomBytes to highlevelcrypto --- src/helper_ackPayload.py | 12 ++++++------ src/helper_random.py | 13 ------------- src/highlevelcrypto.py | 11 +++++++++++ src/network/tcp.py | 2 +- 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/helper_ackPayload.py b/src/helper_ackPayload.py index d30f4c0dad..1c5ddf98fd 100644 --- a/src/helper_ackPayload.py +++ b/src/helper_ackPayload.py @@ -22,26 +22,26 @@ def genAckPayload(streamNumber=1, stealthLevel=0): - level 1: a getpubkey request for a (random) dummy key hash - level 2: a standard message, encrypted to a random pubkey """ - if stealthLevel == 2: # Generate privacy-enhanced payload + if stealthLevel == 2: # Generate privacy-enhanced payload # Generate a dummy privkey and derive the pubkey dummyPubKeyHex = highlevelcrypto.privToPub( - hexlify(helper_random.randomBytes(32))) + hexlify(highlevelcrypto.randomBytes(32))) # Generate a dummy message of random length # (the smallest possible standard-formatted message is 234 bytes) - dummyMessage = helper_random.randomBytes( + dummyMessage = highlevelcrypto.randomBytes( helper_random.randomrandrange(234, 801)) # 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) + elif stealthLevel == 1: # Basic privacy payload (random getpubkey) + ackdata = highlevelcrypto.randomBytes(32) acktype = 0 # getpubkey version = 4 else: # Minimum viable payload (non stealth) - ackdata = helper_random.randomBytes(32) + ackdata = highlevelcrypto.randomBytes(32) acktype = 2 # message version = 1 diff --git a/src/helper_random.py b/src/helper_random.py index 2e6a151bba..e6da707ebc 100644 --- a/src/helper_random.py +++ b/src/helper_random.py @@ -1,12 +1,7 @@ """Convenience functions for random operations. Not suitable for security / cryptography operations.""" -import os import random -try: - from pyelliptic.openssl import OpenSSL -except ImportError: - from .pyelliptic.openssl import OpenSSL NoneType = type(None) @@ -16,14 +11,6 @@ def seed(): random.seed() -def randomBytes(n): - """Method randomBytes.""" - try: - return os.urandom(n) - except NotImplementedError: - return OpenSSL.rand(n) - - def randomshuffle(population): """Method randomShuffle. diff --git a/src/highlevelcrypto.py b/src/highlevelcrypto.py index 6cfd39538a..610596b524 100644 --- a/src/highlevelcrypto.py +++ b/src/highlevelcrypto.py @@ -8,6 +8,7 @@ """ import hashlib +import os from binascii import hexlify import pyelliptic @@ -59,6 +60,16 @@ def encodeWalletImportFormat(privKey): return a.changebase(privKey + checksum, 256, 58) +# Random + +def randomBytes(n): + """Get n random bytes""" + try: + return os.urandom(n) + except NotImplementedError: + return OpenSSL.rand(n) + + def makeCryptor(privkey): """Return a private `.pyelliptic.ECC` instance""" private_key = a.changebase(privkey, 16, 256, minlen=32) diff --git a/src/network/tcp.py b/src/network/tcp.py index ff7783782b..77e8ba6503 100644 --- a/src/network/tcp.py +++ b/src/network/tcp.py @@ -17,7 +17,7 @@ import protocol import state from bmconfigparser import BMConfigParser -from helper_random import randomBytes +from highlevelcrypto import randomBytes from inventory import Inventory from network.advanceddispatcher import AdvancedDispatcher from network.assemble import assemble_addr From bbede1d449b65afcd53b99f8a5684743316c1d8c Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Thu, 9 Dec 2021 18:44:57 +0200 Subject: [PATCH 11/14] A dummy test for randomBytes --- src/tests/test_crypto.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/tests/test_crypto.py b/src/tests/test_crypto.py index 3563098d34..024fa23fcf 100644 --- a/src/tests/test_crypto.py +++ b/src/tests/test_crypto.py @@ -74,6 +74,14 @@ def test_double_sha512(self): self.assertEqual( highlevelcrypto.double_sha512(b'hello'), sample_double_sha512) + def test_randomBytes(self): + """Dummy checks for random bytes""" + for n in (8, 32, 64): + data = highlevelcrypto.randomBytes(n) + self.assertEqual(len(data), n) + self.assertNotEqual(len(set(data)), 1) + self.assertNotEqual(data, highlevelcrypto.randomBytes(n)) + def test_verify(self): """Verify sample signatures and newly generated ones""" pubkey_hex = hexlify(sample_pubsigningkey) From 7e21dd7ca16290b4512300d43b44df4b5f5232b9 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Thu, 9 Dec 2021 19:37:15 +0200 Subject: [PATCH 12/14] Define functions for generating keys in the highlevelcrypto --- src/class_addressGenerator.py | 36 ++++++++++++++--------------------- src/highlevelcrypto.py | 16 ++++++++++++++++ 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/class_addressGenerator.py b/src/class_addressGenerator.py index 4a2c20ec21..06ca16d4ac 100644 --- a/src/class_addressGenerator.py +++ b/src/class_addressGenerator.py @@ -15,7 +15,6 @@ from bmconfigparser import BMConfigParser from fallback import RIPEMD160Hash from network import StoppableThread -from pyelliptic.openssl import OpenSSL from six.moves import configparser, queue @@ -128,17 +127,13 @@ def run(self): # the \x00 or \x00\x00 bytes thus making the address shorter. startTime = time.time() numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix = 0 - potentialPrivSigningKey = OpenSSL.rand(32) - potentialPubSigningKey = highlevelcrypto.pointMult( - potentialPrivSigningKey) + privSigningKey, pubSigningKey = highlevelcrypto.random_keys() while True: numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix += 1 - potentialPrivEncryptionKey = OpenSSL.rand(32) - potentialPubEncryptionKey = highlevelcrypto.pointMult( - potentialPrivEncryptionKey) + potentialPrivEncryptionKey, potentialPubEncryptionKey = \ + highlevelcrypto.random_keys() sha = hashlib.new('sha512') - sha.update( - potentialPubSigningKey + potentialPubEncryptionKey) + sha.update(pubSigningKey + potentialPubEncryptionKey) ripe = RIPEMD160Hash(sha.digest()).digest() if ( ripe[:numberOfNullBytesDemandedOnFrontOfRipeHash] @@ -163,7 +158,7 @@ def run(self): addressVersionNumber, streamNumber, ripe) privSigningKeyWIF = highlevelcrypto.encodeWalletImportFormat( - potentialPrivSigningKey) + privSigningKey) privEncryptionKeyWIF = highlevelcrypto.encodeWalletImportFormat( potentialPrivEncryptionKey) @@ -235,18 +230,15 @@ def run(self): numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix = 0 while True: numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix += 1 - potentialPrivSigningKey = hashlib.sha512( - deterministicPassphrase - + encodeVarint(signingKeyNonce) - ).digest()[:32] - potentialPrivEncryptionKey = hashlib.sha512( - deterministicPassphrase - + encodeVarint(encryptionKeyNonce) - ).digest()[:32] - potentialPubSigningKey = highlevelcrypto.pointMult( - potentialPrivSigningKey) - potentialPubEncryptionKey = highlevelcrypto.pointMult( - potentialPrivEncryptionKey) + potentialPrivSigningKey, potentialPubSigningKey = \ + highlevelcrypto.deterministic_keys( + deterministicPassphrase, + encodeVarint(signingKeyNonce)) + potentialPrivEncryptionKey, potentialPubEncryptionKey = \ + highlevelcrypto.deterministic_keys( + deterministicPassphrase, + encodeVarint(encryptionKeyNonce)) + signingKeyNonce += 2 encryptionKeyNonce += 2 sha = hashlib.new('sha512') diff --git a/src/highlevelcrypto.py b/src/highlevelcrypto.py index 610596b524..cd1057636f 100644 --- a/src/highlevelcrypto.py +++ b/src/highlevelcrypto.py @@ -70,6 +70,22 @@ def randomBytes(n): return OpenSSL.rand(n) +# Keys + +def random_keys(): + """Return a pair of keys, private and public""" + priv = randomBytes(32) + pub = pointMult(priv) + return priv, pub + + +def deterministic_keys(passphrase, nonce): + """Generate keys from *passphrase* and *nonce* (encoded as varint)""" + priv = hashlib.sha512(passphrase + nonce).digest()[:32] + pub = pointMult(priv) + return priv, pub + + def makeCryptor(privkey): """Return a private `.pyelliptic.ECC` instance""" private_key = a.changebase(privkey, 16, 256, minlen=32) From 46c15e815d803be25aead01ca77d4ef813c972b0 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Thu, 9 Dec 2021 19:46:02 +0200 Subject: [PATCH 13/14] Tests for keys generation this implementation for deterministic keys requires a passphrase of type bytes --- src/tests/samples.py | 5 +++-- src/tests/test_crypto.py | 20 ++++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/tests/samples.py b/src/tests/samples.py index 8efae59793..526077329c 100644 --- a/src/tests/samples.py +++ b/src/tests/samples.py @@ -33,9 +33,10 @@ 94730058721143827257669456336351159718085716196507891067256111928318063085006 ) -sample_seed = 'TIGER, tiger, burning bright. In the forests of the night' -# Deterministic addresses with stream 1 and versions 3, 4 +sample_seed = b'TIGER, tiger, burning bright. In the forests of the night' +# RIPE hash on step 22 with signing key nonce 42 sample_deterministic_ripe = b'00cfb69416ae76f68a81c459de4e13460c7d17eb' +# Deterministic addresses with stream 1 and versions 3, 4 sample_deterministic_addr3 = 'BM-2DBPTgeSawWYZceFD69AbDT5q4iUWtj1ZN' sample_deterministic_addr4 = 'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK' sample_daddr3_512 = 18875720106589866286514488037355423395410802084648916523381 diff --git a/src/tests/test_crypto.py b/src/tests/test_crypto.py index 024fa23fcf..44b53e6e5e 100644 --- a/src/tests/test_crypto.py +++ b/src/tests/test_crypto.py @@ -16,10 +16,10 @@ RIPEMD = None from .samples import ( - sample_double_sha512, + sample_deterministic_ripe, sample_double_sha512, sample_msg, sample_pubsigningkey, sample_pubencryptionkey, sample_privsigningkey, sample_privencryptionkey, sample_ripe, - sample_sig, sample_sig_sha1 + sample_seed, sample_sig, sample_sig_sha1 ) @@ -82,6 +82,22 @@ def test_randomBytes(self): self.assertNotEqual(len(set(data)), 1) self.assertNotEqual(data, highlevelcrypto.randomBytes(n)) + def test_random_keys(self): + """Dummy checks for random keys""" + priv, pub = highlevelcrypto.random_keys() + self.assertEqual(len(priv), 32) + self.assertEqual(highlevelcrypto.pointMult(priv), pub) + + def test_deterministic_keys(self): + """Generate deterministic keys, make ripe and compare it to sample""" + # encodeVarint(42) = b'*' + sigkey = highlevelcrypto.deterministic_keys(sample_seed, b'*')[1] + enkey = highlevelcrypto.deterministic_keys(sample_seed, b'+')[1] + self.assertEqual( + sample_deterministic_ripe, + hexlify(TestHashlib._hashdigest( + hashlib.sha512(sigkey + enkey).digest()))) + def test_verify(self): """Verify sample signatures and newly generated ones""" pubkey_hex = hexlify(sample_pubsigningkey) From 803937290ff3434ee0ce24052adb02461ffee216 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Fri, 10 Dec 2021 18:44:26 +0200 Subject: [PATCH 14/14] Started a dummy test for random keys in pyelliptic.ECC() --- src/pyelliptic/tests/test_openssl.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/pyelliptic/tests/test_openssl.py b/src/pyelliptic/tests/test_openssl.py index cb78927760..ef411ef64e 100644 --- a/src/pyelliptic/tests/test_openssl.py +++ b/src/pyelliptic/tests/test_openssl.py @@ -4,9 +4,10 @@ import unittest try: + from pyelliptic.ecc import ECC from pyelliptic.openssl import OpenSSL except ImportError: - from pybitmessage.pyelliptic import OpenSSL + from pybitmessage.pyelliptic import ECC, OpenSSL try: OpenSSL.BN_bn2binpad @@ -55,3 +56,10 @@ def test_padding(self): if b.raw != c.raw.rjust(OpenSSL.BN_num_bytes(n), b'\x00'): bad += 1 self.assertEqual(bad, 0) + + def test_random_keys(self): + """A dummy test for random keys in ECC object""" + eccobj = ECC(curve='secp256k1') + self.assertEqual(len(eccobj.privkey), 32) + pubkey = eccobj.get_pubkey() + self.assertEqual(pubkey[:4], b'\x02\xca\x00\x20')