Im trying to implement a simple C# application that is based on Tor protocol, without identity changes every few minutes etc. just to create a circuit and make a request. I created the onion hidden service, and I want to try make a HTTP request to it and receive an answer first. I have read all the specs, and i looked up the Tor source code, but its really hard, because it is using very low-level C. So far what I understood from the technical point of view is this, if i want to fetch the main page of my onion hidden service I have to:

  1. Choose a random directory server that is serving onion routers microdescriptors (there are 10 hardcoded, but 1 is serving only the bridges)
  2. Connect to dir server, fetch a consensus document
    /tor/status-vote/current/consensus/ using HTTP protocol, close the
  3. Parse the list of onion routers, construct the circuit from 3 nodes.
  4. Parse the entry node with the attributes Fast Guard HSDir Running Stable Valid.
  5. Parse the exit node with the attributes Fast Exit HSDir Running Stable Valid.
  6. Parse the random middle node? Is it just a OR in the list without Exit or Guard attributes? Or any OR with attributes Exit or Guard can also be used as a middle node?
  7. Create a TCP socket and connect to the parsed entry node with the given IP port. Then create a cell packet with the command CREATE with the payload of Diffie-Hellman handshake. Receive a command CREATED wit the encryption key.
  8. Form a relay cell packet with command RELAY_EXTEND containing the IP port of the middle node, and encrypted half handshake for the middle relay, with the key we parsed from previous action, send it to the connected entry node, receive an answer as RELAY_EXTENDED packet, parse new encryption key from it. And use it later to encrypt last diffie hellman handshake payload.
  9. Do the previous action, send RELAY_EXTEND, to the guard node, but with the address of the exit node, and encrypted diffie hellman handshake with the key we received from middle node.
  10. I guess that is it? The circuit is ready? Now i can use a relay cell packets,sending them to my guard node socket connection, encrypted with the key i received from exit node with RELAY_BEGIN to connect to hosts, RELAY_DATA to send data to them?
  • Yes, this is it but some addition to step 6 - you need to use Valid flagged relays, also Stable flag is a preference there, but not a requirement, actually. You don't look to Exit and Guard attributes at all: they have a different purpose and absolutely unrelated to any matter of middle nodes. So pick up a valid node, if you can't connect - in your while-cycle make a preference for a Stable flag. Feel free to ask further questions!
    – Alexey Vesnin
    Jul 22, 2016 at 18:37
  • Thanks for the answer. As i understood i alsoo need to do a tls handhske at step 7 with the guard entry node, before sending any cell packets.
    – Vlad
    Jul 23, 2016 at 2:09
  • Yes, you do need to do TLS.
    – Alexey Vesnin
    Jul 23, 2016 at 2:28
  • I have a question, i was able to connect to the guard node ip:orport using openssl v3 and TSLv1 methods for negotiation. I got an onion key from the /tor/micro/d/<descriptor id>, which is a public key which i used to encrypt TAP handshake. I then send the cell to the node, but i do not get any answer, SSL_read always returns 0. In the documentations there are cells that are used for initial handshake: 4.1. Negotiating versions with VERSIONS cells, so is it actually a mandatory and it needs to be done before building the curcuit?
    – Vlad
    Jul 29, 2016 at 22:46
  • yes, it must be done.
    – Alexey Vesnin
    Jul 30, 2016 at 3:09

2 Answers 2


Step 7 is a TLS socket, not just raw TCP.

"I created the onion hidden service, and i want to try make a http request to it", there are additional steps required for onion services which you'll know from reading the specs but aren't covered in your list. (Get the list of HSDirs, find the correct HSDirs for the onion, fetch the descriptor, decode the introduction points, build a rendezvous circuit, request a meet at the rendezvous through an introduction point, etc.)

"its really hard, because it is using very low-level C", if you're unfamiliar with C there's Orchid (Java), gotor (Go), pycepa (Python), and other implementations of the OR protocol available.

  • Yes i already looked up hidden service resolving. I got a better way with sources, i compiled it using msvc and i set up the debugger to see what happens step by step.
    – Vlad
    Jul 23, 2016 at 2:00

Your algorithm correct only for connection to clear-net hosts not for hidden services. For connecting to hidden services after step 9 but before 10 you also must do another things:

  • 9.1) Send ESTABLISH_RENDEZVOUS cell with some random cookie and wait for RENDEZVOUS_ESTABLISHED back

  • 9.2) Enumerate ResposibleDirs nodes which you can get by searching HsDirs from consensus and descriptor_id of the hidden_service

  • 9.3) From ResponsibleDirs you can get list of Introduction Points by fetching descriptor (this descriptor must be fetched through new special circuit)

  • 9.4) After that you must create new special circuit for connecting to Introduction Point and send them INTRODUCE1 cell with your random cookie and service descriptor cookie

  • 9.5) When some of introduction point returns you INTRODUCE_ACK you can extend you main circuit (which you create with your first 9 steps) and after it try connect with step 10 to hidden service.

This also valid only for hidden service v2 specification (small onion addresses). For more information you can read at tor specification: https://gitweb.torproject.org/torspec.git/tree/rend-spec-v2.txt

For more simple implementation details you can read at Torpy (python tor client implementation) code extend_to_hidden method: https://github.com/torpyorg/torpy/blob/master/torpy/circuit.py#L547

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .