From 5c1dcc56457a3cae4680a9ce3a3d1366f75fe551 Mon Sep 17 00:00:00 2001
From: Dmitri Bogomolov <4glitch@gmail.com>
Date: Thu, 9 Dec 2021 19:37:15 +0200
Subject: [PATCH 1/3] Define functions for generating keys in the
 highlevelcrypto

---
 src/class_addressGenerator.py |  36 ++++------
 src/highlevelcrypto.py        | 132 +++++++++++++++++++---------------
 2 files changed, 90 insertions(+), 78 deletions(-)

diff --git a/src/class_addressGenerator.py b/src/class_addressGenerator.py
index c32c3994..929ac364 100644
--- a/src/class_addressGenerator.py
+++ b/src/class_addressGenerator.py
@@ -16,7 +16,6 @@ from addresses import decodeAddress, encodeAddress, encodeVarint
 from bmconfigparser import config
 from fallback import RIPEMD160Hash
 from network import StoppableThread
-from pyelliptic.openssl import OpenSSL
 from tr import _translate
 
 
@@ -129,17 +128,13 @@ class addressGenerator(StoppableThread):
                 # 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]
@@ -164,7 +159,7 @@ class addressGenerator(StoppableThread):
                     addressVersionNumber, streamNumber, ripe)
 
                 privSigningKeyWIF = highlevelcrypto.encodeWalletImportFormat(
-                    potentialPrivSigningKey)
+                    privSigningKey)
                 privEncryptionKeyWIF = highlevelcrypto.encodeWalletImportFormat(
                     potentialPrivEncryptionKey)
 
@@ -238,18 +233,15 @@ class addressGenerator(StoppableThread):
                     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 e497c31c..d7af85de 100644
--- a/src/highlevelcrypto.py
+++ b/src/highlevelcrypto.py
@@ -17,10 +17,10 @@ from pyelliptic import arithmetic as a
 
 
 __all__ = [
-    'decodeWalletImportFormat', 'encodeWalletImportFormat',
-    'double_sha512', 'calculateInventoryHash',
+    'decodeWalletImportFormat', 'deterministic_keys',
+    'double_sha512', 'calculateInventoryHash', 'encodeWalletImportFormat',
     'encrypt', 'makeCryptor', 'pointMult', 'privToPub', 'randomBytes',
-    'sign', 'verify']
+    'random_keys', 'sign', 'verify']
 
 
 # WIF (uses arithmetic ):
@@ -74,6 +74,77 @@ def calculateInventoryHash(data):
     return double_sha512(data)[:32]
 
 
+# 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 hexToPubkey(pubkey):
+    """Convert a pubkey from hex to binary"""
+    pubkey_raw = a.changebase(pubkey[2:], 16, 256, minlen=64)
+    pubkey_bin = b'\x02\xca\x00 ' + pubkey_raw[:32] + b'\x00 ' + pubkey_raw[32:]
+    return pubkey_bin
+
+
+def privToPub(privkey):
+    """Converts hex private key into hex public key"""
+    private_key = a.changebase(privkey, 16, 256, minlen=32)
+    public_key = pointMult(private_key)
+    return hexlify(public_key)
+
+
+def pointMult(secret):
+    """
+    Does an EC point multiplication; turns a private key into a public key.
+
+    Evidently, this type of error can occur very rarely:
+
+    >>> File "highlevelcrypto.py", line 54, in pointMult
+    >>>  group = OpenSSL.EC_KEY_get0_group(k)
+    >>> WindowsError: exception: access violation reading 0x0000000000000008
+    """
+    while True:
+        try:
+            k = OpenSSL.EC_KEY_new_by_curve_name(
+                OpenSSL.get_curve('secp256k1'))
+            priv_key = OpenSSL.BN_bin2bn(secret, 32, None)
+            group = OpenSSL.EC_KEY_get0_group(k)
+            pub_key = OpenSSL.EC_POINT_new(group)
+
+            OpenSSL.EC_POINT_mul(group, pub_key, priv_key, None, None, None)
+            OpenSSL.EC_KEY_set_private_key(k, priv_key)
+            OpenSSL.EC_KEY_set_public_key(k, pub_key)
+
+            size = OpenSSL.i2o_ECPublicKey(k, None)
+            mb = OpenSSL.create_string_buffer(size)
+            OpenSSL.i2o_ECPublicKey(k, OpenSSL.byref(OpenSSL.pointer(mb)))
+
+            return mb.raw
+
+        except Exception:
+            import traceback
+            import time
+            traceback.print_exc()
+            time.sleep(0.2)
+        finally:
+            OpenSSL.EC_POINT_free(pub_key)
+            OpenSSL.BN_free(priv_key)
+            OpenSSL.EC_KEY_free(k)
+
+
+# Encryption
+
 def makeCryptor(privkey, curve='secp256k1'):
     """Return a private `.pyelliptic.ECC` instance"""
     private_key = a.changebase(privkey, 16, 256, minlen=32)
@@ -84,26 +155,12 @@ def makeCryptor(privkey, curve='secp256k1'):
     return cryptor
 
 
-def hexToPubkey(pubkey):
-    """Convert a pubkey from hex to binary"""
-    pubkey_raw = a.changebase(pubkey[2:], 16, 256, minlen=64)
-    pubkey_bin = b'\x02\xca\x00 ' + pubkey_raw[:32] + b'\x00 ' + pubkey_raw[32:]
-    return pubkey_bin
-
-
 def makePubCryptor(pubkey):
     """Return a public `.pyelliptic.ECC` instance"""
     pubkey_bin = hexToPubkey(pubkey)
     return pyelliptic.ECC(curve='secp256k1', pubkey=pubkey_bin)
 
 
-def privToPub(privkey):
-    """Converts hex private key into hex public key"""
-    private_key = a.changebase(privkey, 16, 256, minlen=32)
-    public_key = pointMult(private_key)
-    return hexlify(public_key)
-
-
 def encrypt(msg, hexPubkey):
     """Encrypts message with hex public key"""
     return pyelliptic.ECC(curve='secp256k1').encrypt(
@@ -120,6 +177,8 @@ def decryptFast(msg, cryptor):
     return cryptor.decrypt(msg)
 
 
+# Signatures
+
 def _choose_digest_alg(name):
     """
     Choose openssl digest constant by name raises ValueError if not appropriate
@@ -160,42 +219,3 @@ def verify(msg, sig, hexPubkey, digestAlg=None):
             sig, msg, digest_alg=_choose_digest_alg(digestAlg))
     except:
         return False
-
-
-def pointMult(secret):
-    """
-    Does an EC point multiplication; turns a private key into a public key.
-
-    Evidently, this type of error can occur very rarely:
-
-    >>> File "highlevelcrypto.py", line 54, in pointMult
-    >>>  group = OpenSSL.EC_KEY_get0_group(k)
-    >>> WindowsError: exception: access violation reading 0x0000000000000008
-    """
-    while True:
-        try:
-            k = OpenSSL.EC_KEY_new_by_curve_name(
-                OpenSSL.get_curve('secp256k1'))
-            priv_key = OpenSSL.BN_bin2bn(secret, 32, None)
-            group = OpenSSL.EC_KEY_get0_group(k)
-            pub_key = OpenSSL.EC_POINT_new(group)
-
-            OpenSSL.EC_POINT_mul(group, pub_key, priv_key, None, None, None)
-            OpenSSL.EC_KEY_set_private_key(k, priv_key)
-            OpenSSL.EC_KEY_set_public_key(k, pub_key)
-
-            size = OpenSSL.i2o_ECPublicKey(k, None)
-            mb = OpenSSL.create_string_buffer(size)
-            OpenSSL.i2o_ECPublicKey(k, OpenSSL.byref(OpenSSL.pointer(mb)))
-
-            return mb.raw
-
-        except Exception:
-            import traceback
-            import time
-            traceback.print_exc()
-            time.sleep(0.2)
-        finally:
-            OpenSSL.EC_POINT_free(pub_key)
-            OpenSSL.BN_free(priv_key)
-            OpenSSL.EC_KEY_free(k)
-- 
2.45.1


From c51b2875df0592c47ccbc9da4689114ae16357d6 Mon Sep 17 00:00:00 2001
From: Dmitri Bogomolov <4glitch@gmail.com>
Date: Thu, 9 Dec 2021 19:46:02 +0200
Subject: [PATCH 2/3] Tests for keys generation

this implementation for deterministic keys requires a passphrase of type bytes
---
 src/tests/samples.py     |  3 ++-
 src/tests/test_crypto.py | 20 ++++++++++++++++++--
 2 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/src/tests/samples.py b/src/tests/samples.py
index d96187ca..9f6c9d5b 100644
--- a/src/tests/samples.py
+++ b/src/tests/samples.py
@@ -43,8 +43,9 @@ sample_point = (
 )
 
 sample_seed = b'TIGER, tiger, burning bright. In the forests of the night'
-# Deterministic addresses with stream 1 and versions 3, 4
+# 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 0a6f9f96..9fd6ffa6 100644
--- a/src/tests/test_crypto.py
+++ b/src/tests/test_crypto.py
@@ -17,10 +17,10 @@ except ImportError:
     RIPEMD160 = None
 
 from .samples import (
-    sample_double_sha512, sample_hash_data,
+    sample_deterministic_ripe, sample_double_sha512, sample_hash_data,
     sample_msg, sample_pubsigningkey, sample_pubencryptionkey,
     sample_privsigningkey, sample_privencryptionkey, sample_ripe,
-    sample_sig, sample_sig_sha1
+    sample_seed, sample_sig, sample_sig_sha1
 )
 
 
@@ -81,6 +81,22 @@ class TestHighlevelcrypto(unittest.TestCase):
             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_signatures(self):
         """Verify sample signatures and newly generated ones"""
         pubkey_hex = hexlify(sample_pubsigningkey)
-- 
2.45.1


From 0ed566500f593489e4e4ab5cb9075898003c3c48 Mon Sep 17 00:00:00 2001
From: Lee Miller <lee.miller@tutanota.com>
Date: Sun, 14 Apr 2024 03:12:34 +0300
Subject: [PATCH 3/3] Use fallback.RIPEMD160Hash() in the test for
 deterministic keys

---
 src/tests/test_crypto.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/tests/test_crypto.py b/src/tests/test_crypto.py
index 9fd6ffa6..e518d7fd 100644
--- a/src/tests/test_crypto.py
+++ b/src/tests/test_crypto.py
@@ -8,7 +8,7 @@ import unittest
 from abc import ABCMeta, abstractmethod
 from binascii import hexlify
 
-from pybitmessage import highlevelcrypto
+from pybitmessage import highlevelcrypto, fallback
 
 
 try:
@@ -94,8 +94,8 @@ class TestHighlevelcrypto(unittest.TestCase):
         enkey = highlevelcrypto.deterministic_keys(sample_seed, b'+')[1]
         self.assertEqual(
             sample_deterministic_ripe,
-            hexlify(TestHashlib._hashdigest(
-                hashlib.sha512(sigkey + enkey).digest())))
+            hexlify(fallback.RIPEMD160Hash(
+                hashlib.sha512(sigkey + enkey).digest()).digest()))
 
     def test_signatures(self):
         """Verify sample signatures and newly generated ones"""
-- 
2.45.1