LessonsExtensions: Built inLesson 69

Tor with OnionXT

Reach and run onion services through Tor with OnionXT: what an onion address is, checking one, the public key inside it, an address of your own from a seed, dialling, and the services others dial.

  • Extensions
  • Lesson 69 of 83
  • About 10 minutes
  • 6 examples

Who talks to whom

Tor hides who talks to whom: a connection hops through three computers, and none of them knows both ends. An onion service goes further, a program reachable only inside Tor at an onion address, whose own location stays hidden too. OnionXT, the xTalk Suite's Tor library, is written in xTalk and drives a tor program running beside your app (Tor Browser has one); its handlers begin with ox.

Everything in this lesson works without Tor, so every example runs here. Dialling and running services need Tor itself, and the last section says how they go.

ExampleRun it in the playground
put oxVersion()
put oxTransportInfo() into tInfo
put tInfo["offlineAddress"]
put tInfo["deterministicOnion"]
put oxIsReady() is among the items of "true,false"
Output (yours will differ: it changes from run to run)
1.0.0
true
true
true

oxVersion gives OnionXT's version, and Tor's after it when Tor is running. oxTransportInfo says what works here: with SodiumXT loaded, addresses can be made and checked offline. oxIsReady is true only once Tor is running and connected.

Onion addresses

An onion address is 56 letters and digits, then .onion. Nobody registers it: it is made from a key, and has a version and a checksum built in. oxIsValidAddress checks all of it, so a typo is caught before you try to connect. The old 16-letter addresses (version 2) stopped working in 2021, so they are not valid either:

ExampleRun it in the playground
put "duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion" into tAddress
put oxIsValidAddress(tAddress)
put "e" into char 1 of tAddress
put oxIsValidAddress(tAddress)
put oxIsValidAddress("expyuzz4wqqyqhjn.onion")
Output
true
false
false

The address is a public key, 32 bytes, written in base32 (the letters a to z and the digits 2 to 7) with the checksum and version after it. oxPublicKeyFromAddress takes the key out, and oxAddressFromPublicKey turns a key back into its address. Only whoever holds the matching secret key can run that address, which is how Tor knows it has reached the right place:

ExampleRun it in the playground
put "duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion" into tAddress
put oxPublicKeyFromAddress(tAddress) into tKey
put the number of bytes in tKey
put sxBin2Hex(tKey)
put oxAddressFromPublicKey(tKey) is tAddress
Output
32
1d04a1d04a338c6e6ae970bfabee49049d6702250984ca950c01673f4ec034ad
true

An address of your own

Your app can have an address of its own, the same every time it starts. It comes from a seed, 32 secret bytes: SodiumXT's sxSignKeypairFromSeed makes a key pair from it, oxAddressFromPublicKey turns the public key into the address without any need for Tor, and oxCreateServiceFromSeed later asks Tor to run it. Keep the seed secret, because anyone who has it can run your address:

ExampleRun it in the playground
local tPublic, tSecret
-- for learning: a real app uses sxRandomBytes(32), kept safe
put sxHash(textEncode("my seed", "UTF-8"), 32) into tSeed
sxSignKeypairFromSeed tSeed, tPublic, tSecret
put oxAddressFromPublicKey(tPublic) into tAddress
put tAddress
put oxIsValidAddress(tAddress)
Output
cyhitagktc4hrm3w62o62zveuuyruejtwy4m45uaejjdnyogby6dd4ad.onion
true

Dialling and serving

To reach an address, dial it through Tor. oxDial takes a host (an onion address, or an ordinary name) and a port, and gives a stream handle at once; Tor then connects, and the handler you named with oxSetStreamCallback hears "open", then "data" as it arrives, or "error". oxWrite sends, oxStreamState says how a stream is, and oxCloseStream ends it. An onion service lets others dial you: after oxConnectControl reaches Tor's control port, oxCreateService makes a new address each time and oxCreateServiceFromSeed keeps one, and Tor forwards each visitor to a port on this computer. oxShutdown closes everything when your app quits.

Without Tor, the calls still check what they are given, and say what is wrong in the result, starting with OnionXT::

ExampleRun it in the playground
oxDial empty, 80
put the result begins with "OnionXT:"
oxDial "duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion", 0
put the result begins with "OnionXT:"
put oxStreamState(999999)
oxCreateService 80, 0
put the result begins with "OnionXT:"
oxCreateServiceFromSeed "too short", 80, 8080
put the result begins with "OnionXT:"
Output
true
true
unknown
true
true

Try it yourself

Write a function cleanOnion for a field where people paste addresses: it trims spaces, makes the letters small, adds .onion if it is missing, and returns the address if it is valid, or empty if not.

Show a solution
ExampleRun it in the playground
function cleanOnion pText
   put toLower(word 1 to -1 of pText) into pText
   if not (pText ends with ".onion") then put ".onion" after pText
   if oxIsValidAddress(pText) then return pText
   return empty
end cleanOnion

put cleanOnion("  DUCKDUCKGOGG42XJOC72X3SJASOWOARFBGCMVFIMAFTT6TWAGSWZCZAD  ")
put cleanOnion("expyuzz4wqqyqhjn") is empty
Output
duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion
true

Words in this lesson

Each one finds every lesson that uses it. The Dictionary has the whole story of each, with its syntax and more examples.