Browse Source

Relays in Single-Pass Walking Onions pick the next relay according to a (simulated) VRF

Ian Goldberg 4 years ago
parent
commit
726a57d749
1 changed files with 43 additions and 21 deletions
  1. 43 21
      relay.py

+ 43 - 21
relay.py

@@ -10,25 +10,46 @@ import nacl.signing
 import nacl.public
 import nacl.hash
 import nacl.bindings
-import hashlib
-
-from collections import namedtuple
 
 import network
 import dirauth
 
-VRFOutput = namedtuple('VRFOutput', 'output proof')
+# Simulated VRF.  The output and proof are of the correct size (32 bytes
+# for the output, 48 bytes for the proof), and we charge the right
+# number of group operations (1 keygen, 2 DH for proving, 3 DH for
+# verifiying -- that last number charges 1 DH for a (s*G+c*Y) multiexp,
+# but 2 for a (s*A+c*B) multiexp)
+class VRF:
+    @staticmethod
+    def get_output(vrf_privkey, vrf_input, perfstats):
+        """Given the VRF private key and the input, produce
+        the output value and the proof."""
+        # nacl can't do the group operations we need to actually
+        # compute the VRF, so we just fake it for the simulation.  The
+        # output value is sha256(privkey, input), and the proof is
+        # sha256(pubkey, input, output) + 16 bytes of 0.
+        # ***THIS IS NOT A REAL VRF!***
+        val = nacl.hash.sha256(bytes(vrf_privkey) + vrf_input)
+        vrf_pubkey = vrf_privkey.public_key
+        proof = nacl.hash.sha256(bytes(vrf_pubkey) + val + vrf_input) + \
+                bytes(16)
+        perfstats.keygens += 1
+        perfstats.dhs += 2
+        return val, proof
 
-class VRF():
-    def __init__(self, private_key, relaypicker):
-        self.private_key = private_key
-        self.relaypicker = relaypicker
+    @staticmethod
+    def check_output(vrf_pubkey, vrf_input, vrf_output, perfstats):
+        """Given the VRF public key, the input, and the claimed output
+        and proof, raise an exception if the proof fails to check.
+        Returns the VRF output."""
+        # Again, NOT A REAL VRF!
+        val, proof = vrf_output
+        if nacl.hash.sha256(vrf_pubkey + val + vrf_input) + bytes(16) != \
+                proof:
+            raise ValueError("VRF proof did not verify")
+        perfstats.dhs += 3
+        return val
 
-    def get_output(self, vrf_input):
-        output = self.relaypicker.pick_weighted_relay_index()
-        m = hashlib.sha256()
-        m.update(str(output).encode())
-        return VRFOutput(output, m.digest())
 
 class RelayNetMsg(network.NetMsg):
     """The subclass of NetMsg for messages between relays and either
@@ -989,21 +1010,22 @@ class RelayChannelManager(ChannelManager):
             deckey = nacl.hash.sha256(secret + b'upstream')
             createdkey = nacl.hash.sha256(secret + b'created')
 
-            # here, we will directly extend the circuit ourselves, after
+            # Here, we will directly extend the circuit ourselves, after
             # determining the next relay using the client's path selection
             # key in conjunction with our own
             pathsel_rand, blinded_client_path_selection_key = \
                     Sphinx.server(nacl.public.PublicKey(msg.clipathselkey),
-                    self.path_selection_key, b'pathsel', False, self.perfstats)
+                    self.onionkey, b'pathsel', False, self.perfstats)
 
-            logging.debug("RelayChannelManager: Unimplemented! need to translate idx into endive index")
-            logging.debug("RelayChannelManager: Unimplemented! need to pick the next relay using the shared secret between the client and the relay.")
+            # Simulate the VRF output for now (but it has the right
+            # size, and charges the right number of group operations to
+            # the perfstats)
+            vrf_output = VRF.get_output(self.path_selection_key,
+                    pathsel_rand, self.perfstats)
 
-            # simulate the VRF output for now
-            vrf_output = VRF(self.path_selection_key,
-                    self.relaypicker).get_output(pathsel_rand)
+            index = int.from_bytes(vrf_output[0][:4], 'big', signed=False)
 
-            next_hop = self.relaypicker.pick_relay_by_uniform_index(vrf_output.output)
+            next_hop = self.relaypicker.pick_relay_by_uniform_index(index)
             if next_hop == None:
                 logging.debug("Client requested extending the circuit to a relay index that results in None, aborting. my circid: %s", str(circhandler.circid))
                 circhandler.close()