[tor-commits] [stem/master] Simplify HSv3 blinding

atagar at torproject.org atagar at torproject.org
Sun Nov 17 23:40:39 UTC 2019


commit 838f6209babae74279dec100df13c3f594425a8f
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun Nov 17 14:24:51 2019 -0800

    Simplify HSv3 blinding
    
    I won't pretend to understand this math. A smarter mind than mine (asn's) came
    up with this crypto. Just massaging it into a form I find easier to understand.
---
 stem/descriptor/certificate.py            |  6 +--
 stem/descriptor/hidden_service.py         | 73 ++++++++++++++++++++++++-------
 stem/descriptor/hsv3_crypto.py            | 66 ----------------------------
 stem/{descriptor => util}/slow_ed25519.py |  8 ++--
 test/settings.cfg                         |  2 +
 5 files changed, 64 insertions(+), 91 deletions(-)

diff --git a/stem/descriptor/certificate.py b/stem/descriptor/certificate.py
index 74a4e08c..fe94b52d 100644
--- a/stem/descriptor/certificate.py
+++ b/stem/descriptor/certificate.py
@@ -291,15 +291,13 @@ class Ed25519CertificateV1(Ed25519Certificate):
   :var bytes signature: certificate signature
 
   :param bytes signature: pre-calculated certificate signature
-  :param cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey: certificate signing key
+  :param cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey signing_key: certificate signing key
   """
 
   def __init__(self, cert_type = None, expiration = None, key_type = None, key = None, extensions = None, signature = None, signing_key = None):
     super(Ed25519CertificateV1, self).__init__(1)
 
-    if not signature and not signing_key:
-      raise ValueError('Certificate signature or signing key is required')
-    elif cert_type is None:
+    if cert_type is None:
       raise ValueError('Certificate type is required')
     elif key is None:
       raise ValueError('Certificate key is required')
diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py
index cdb8d645..3af2fa0c 100644
--- a/stem/descriptor/hidden_service.py
+++ b/stem/descriptor/hidden_service.py
@@ -21,6 +21,8 @@ These are only available through the Controller's
   BaseHiddenServiceDescriptor - Common parent for hidden service descriptors
     |- HiddenServiceDescriptorV2 - Version 2 hidden service descriptor
     +- HiddenServiceDescriptorV3 - Version 3 hidden service descriptor
+         |- address_from_identity_key - convert an identity key to address
+         |- identity_key_from_address - convert an address to identity key
          +- decrypt - decrypt and parse encrypted layers
 
   OuterLayer - First encrypted layer of a hidden service v3 descriptor
@@ -41,7 +43,6 @@ import time
 
 import stem.client.datatype
 import stem.descriptor.certificate
-import stem.descriptor.hsv3_crypto
 import stem.prereq
 import stem.util
 import stem.util.connection
@@ -50,6 +51,7 @@ import stem.util.tor_tools
 
 from stem.client.datatype import CertType
 from stem.descriptor.certificate import ExtensionType, Ed25519Extension, Ed25519Certificate, Ed25519CertificateV1
+from stem.util import slow_ed25519
 
 from stem.descriptor import (
   PGP_BLOCK_END,
@@ -903,7 +905,7 @@ class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor):
   }
 
   @classmethod
-  def content(cls, attr = None, exclude = (), sign = False, inner_layer = None, outer_layer = None, identity_key = None, signing_key = None, signing_cert = None, revision_counter = None, blinding_param = None):
+  def content(cls, attr = None, exclude = (), sign = False, inner_layer = None, outer_layer = None, identity_key = None, signing_key = None, signing_cert = None, revision_counter = None, blinding_nonce = None):
     """
     Hidden service v3 descriptors consist of three parts:
 
@@ -933,7 +935,7 @@ class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor):
     :param stem.descriptor.Ed25519CertificateV1 signing_cert: certificate
       signing this descriptor
     :param int revision_counter: descriptor revision number
-    :param bytes blinding_param: 32 byte blinding factor to derive the blinding key
+    :param bytes blinding_nonce: 32 byte blinding factor to derive the blinding key
 
     :returns: **str** with the content of a descriptor
 
@@ -953,10 +955,10 @@ class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor):
     identity_key = identity_key if identity_key else Ed25519PrivateKey.generate()
     signing_key = signing_key if signing_key else Ed25519PrivateKey.generate()
     revision_counter = revision_counter if revision_counter else int(time.time())
-    blinding_param = blinding_param if blinding_param else os.urandom(32)
+    blinding_nonce = blinding_nonce if blinding_nonce else os.urandom(32)
 
-    blinded_key = stem.descriptor.hsv3_crypto.HSv3PrivateBlindedKey(identity_key, blinding_param = blinding_param)
-    subcredential = HiddenServiceDescriptorV3._subcredential(identity_key, blinded_key.blinded_pubkey)
+    blinded_key = _blinded_pubkey(identity_key, blinding_nonce)
+    subcredential = HiddenServiceDescriptorV3._subcredential(identity_key, blinded_key)
     custom_sig = attr.pop('signature') if (attr and 'signature' in attr) else None
 
     if not outer_layer:
@@ -965,23 +967,21 @@ class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor):
         inner_layer = inner_layer,
         revision_counter = revision_counter,
         subcredential = subcredential,
-        blinded_key = blinded_key.blinded_pubkey,
+        blinded_key = blinded_key,
       )
 
     if not signing_cert:
-      signing_cert = Ed25519CertificateV1(
-        cert_type = CertType.HS_V3_DESC_SIGNING,
-        key = signing_key,
-        extensions = [Ed25519Extension(ExtensionType.HAS_SIGNING_KEY, None, blinded_key.blinded_pubkey)],
-        signing_key = blinded_key,
-      )
+      extensions = [Ed25519Extension(ExtensionType.HAS_SIGNING_KEY, None, blinded_key)]
+
+      signing_cert = Ed25519CertificateV1(cert_type = CertType.HS_V3_DESC_SIGNING, key = signing_key, extensions = extensions)
+      signing_cert.signature = _blinded_sign(signing_cert.pack(), identity_key, blinded_key, blinding_nonce)
 
     desc_content = _descriptor_content(attr, exclude, (
       ('hs-descriptor', '3'),
       ('descriptor-lifetime', '180'),
       ('descriptor-signing-key-cert', '\n' + signing_cert.to_base64(pem = True)),
       ('revision-counter', str(revision_counter)),
-      ('superencrypted', b'\n' + outer_layer._encrypt(revision_counter, subcredential, blinded_key.blinded_pubkey)),
+      ('superencrypted', b'\n' + outer_layer._encrypt(revision_counter, subcredential, blinded_key)),
     ), ()) + b'\n'
 
     if custom_sig:
@@ -993,8 +993,8 @@ class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor):
     return desc_content
 
   @classmethod
-  def create(cls, attr = None, exclude = (), validate = True, sign = False, inner_layer = None, outer_layer = None, identity_key = None, signing_key = None, signing_cert = None, revision_counter = None, blinding_param = None):
-    return cls(cls.content(attr, exclude, sign, inner_layer, outer_layer, identity_key, signing_key, signing_cert, revision_counter, blinding_param), validate = validate)
+  def create(cls, attr = None, exclude = (), validate = True, sign = False, inner_layer = None, outer_layer = None, identity_key = None, signing_key = None, signing_cert = None, revision_counter = None, blinding_nonce = None):
+    return cls(cls.content(attr, exclude, sign, inner_layer, outer_layer, identity_key, signing_key, signing_cert, revision_counter, blinding_nonce), validate = validate)
 
   def __init__(self, raw_contents, validate = False):
     super(HiddenServiceDescriptorV3, self).__init__(raw_contents, lazy_load = not validate)
@@ -1294,6 +1294,47 @@ class InnerLayer(Descriptor):
       self._entries = entries
 
 
+def _blinded_pubkey(identity_key, blinding_nonce):
+  mult = 2 ** (slow_ed25519.b - 2) + sum(2 ** i * slow_ed25519.bit(blinding_nonce, i) for i in range(3, slow_ed25519.b - 2))
+  P = slow_ed25519.decodepoint(stem.util._pubkey_bytes(identity_key))
+  return slow_ed25519.encodepoint(slow_ed25519.scalarmult(P, mult))
+
+
+def _blinded_sign(msg, identity_key, blinded_key, blinding_nonce):
+  from cryptography.hazmat.primitives import serialization
+
+  identity_key_bytes = identity_key.private_bytes(
+    encoding = serialization.Encoding.Raw,
+    format = serialization.PrivateFormat.Raw,
+    encryption_algorithm = serialization.NoEncryption(),
+  )
+
+  # pad private identity key into an ESK (encrypted secret key)
+
+  h = slow_ed25519.H(identity_key_bytes)
+  a = 2 ** (slow_ed25519.b - 2) + sum(2 ** i * slow_ed25519.bit(h, i) for i in range(3, slow_ed25519.b - 2))
+  k = b''.join([h[i:i + 1] for i in range(slow_ed25519.b // 8, slow_ed25519.b // 4)])
+  esk = slow_ed25519.encodeint(a) + k
+
+  # blind the ESK with this nonce
+
+  mult = 2 ** (slow_ed25519.b - 2) + sum(2 ** i * slow_ed25519.bit(blinding_nonce, i) for i in range(3, slow_ed25519.b - 2))
+  s = slow_ed25519.decodeint(esk[:32])
+  s_prime = (s * mult) % slow_ed25519.l
+  k = esk[32:]
+  k_prime = slow_ed25519.H(b'Derive temporary signing key hash input' + k)[:32]
+  blinded_esk = slow_ed25519.encodeint(s_prime) + k_prime
+
+  # finally, sign the message
+
+  a = slow_ed25519.decodeint(blinded_esk[:32])
+  r = slow_ed25519.Hint(b''.join([blinded_esk[i:i + 1] for i in range(slow_ed25519.b // 8, slow_ed25519.b // 4)]) + msg)
+  R = slow_ed25519.scalarmult(slow_ed25519.B, r)
+  S = (r + slow_ed25519.Hint(slow_ed25519.encodepoint(R) + blinded_key + msg) * a) % slow_ed25519.l
+
+  return slow_ed25519.encodepoint(R) + slow_ed25519.encodeint(S)
+
+
 # TODO: drop this alias in stem 2.x
 
 HiddenServiceDescriptor = HiddenServiceDescriptorV2
diff --git a/stem/descriptor/hsv3_crypto.py b/stem/descriptor/hsv3_crypto.py
deleted file mode 100644
index 0186ba90..00000000
--- a/stem/descriptor/hsv3_crypto.py
+++ /dev/null
@@ -1,66 +0,0 @@
-from stem.descriptor import slow_ed25519
-
-
-"""
-HSv3 Key blinding
-
-Expose classes for HSv3 blinded keys which can mimic the hazmat ed25519
-public/private key classes, so that we can use them interchangeably in the
-certificate module.
-
-- HSv3PublicBlindedKey: represents the public blinded ed25519 key of an onion
-  service and should expose a public_bytes() method and a verify() method.
-
-- HSv3PrivateBlindedKey: represents the private part of a blinded ed25519 key
-  of an onion service and should expose a public_key() method and a sign() method.
-"""
-
-
-def blindESK(esk, param):
-  mult = 2 ** (slow_ed25519.b - 2) + sum(2 ** i * slow_ed25519.bit(param, i) for i in range(3, slow_ed25519.b - 2))
-  s = slow_ed25519.decodeint(esk[:32])
-  s_prime = (s * mult) % slow_ed25519.l
-  k = esk[32:]
-  assert(len(k) == 32)
-  k_prime = slow_ed25519.H(b'Derive temporary signing key hash input' + k)[:32]
-  return slow_ed25519.encodeint(s_prime) + k_prime
-
-
-def blindPK(pk, param):
-  mult = 2 ** (slow_ed25519.b - 2) + sum(2 ** i * slow_ed25519.bit(param, i) for i in range(3, slow_ed25519.b - 2))
-  P = slow_ed25519.decodepoint(pk)
-  return slow_ed25519.encodepoint(slow_ed25519.scalarmult(P, mult))
-
-
-def expandSK(sk):
-  h = slow_ed25519.H(sk)
-  a = 2 ** (slow_ed25519.b - 2) + sum(2 ** i * slow_ed25519.bit(h, i) for i in range(3, slow_ed25519.b - 2))
-  k = b''.join([h[i:i + 1] for i in range(slow_ed25519.b // 8, slow_ed25519.b // 4)])
-  assert len(k) == 32
-  return slow_ed25519.encodeint(a) + k
-
-
-def signatureWithESK(m, h, pk):
-  a = slow_ed25519.decodeint(h[:32])
-  r = slow_ed25519.Hint(b''.join([h[i:i + 1] for i in range(slow_ed25519.b // 8, slow_ed25519.b // 4)]) + m)
-  R = slow_ed25519.scalarmult(slow_ed25519.B, r)
-  S = (r + slow_ed25519.Hint(slow_ed25519.encodepoint(R) + pk + m) * a) % slow_ed25519.l
-
-  return slow_ed25519.encodepoint(R) + slow_ed25519.encodeint(S)
-
-
-class HSv3PrivateBlindedKey(object):
-  def __init__(self, hazmat_private_key, blinding_param):
-    from cryptography.hazmat.primitives import serialization
-
-    secret_seed = hazmat_private_key.private_bytes(encoding = serialization.Encoding.Raw, format = serialization.PrivateFormat.Raw, encryption_algorithm = serialization.NoEncryption())
-    assert(len(secret_seed) == 32)
-
-    expanded_identity_priv_key = expandSK(secret_seed)
-    identity_public_key = slow_ed25519.publickey(secret_seed)
-
-    self.blinded_secret_key = blindESK(expanded_identity_priv_key, blinding_param)
-    self.blinded_pubkey = blindPK(identity_public_key, blinding_param)
-
-  def sign(self, msg):
-    return signatureWithESK(msg, self.blinded_secret_key, self.blinded_pubkey)
diff --git a/stem/descriptor/slow_ed25519.py b/stem/util/slow_ed25519.py
similarity index 94%
rename from stem/descriptor/slow_ed25519.py
rename to stem/util/slow_ed25519.py
index ffca5b02..fb617464 100644
--- a/stem/descriptor/slow_ed25519.py
+++ b/stem/util/slow_ed25519.py
@@ -1,12 +1,10 @@
-# This is the ed25519 implementation from
-#     http://ed25519.cr.yp.to/python/ed25519.py .
-# It is in the public domain.
+# Public domain ed25519 implementation from...
+#
+#   http://ed25519.cr.yp.to/python/ed25519.py
 #
 # It isn't constant-time.  Don't use it except for testing.  Also, see
 # warnings about how very slow it is.  Only use this for generating
 # test vectors, I'd suggest.
-#
-# Don't edit this file.  Mess with ed25519_ref.py
 
 import hashlib
 
diff --git a/test/settings.cfg b/test/settings.cfg
index 8fe79d2a..5443df41 100644
--- a/test/settings.cfg
+++ b/test/settings.cfg
@@ -177,6 +177,8 @@ pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.m
 pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.networkstatus
 pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.server_descriptor
 pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.tordnsel
+pycodestyle.ignore stem/util/slow_ed25519.py => E741: l = 2 ** 252 + 27742317777372353535851937790883648493
+pycodestyle.ignore stem/util/slow_ed25519.py => E741: I = expmod(2, (q - 1) // 4, q)
 pycodestyle.ignore test/unit/util/connection.py => W291: _tor     tor        15843   10 pipe 0x0 state:
 pycodestyle.ignore test/unit/util/connection.py => W291: _tor     tor        15843   11 pipe 0x0 state:
 





More information about the tor-commits mailing list