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.
put oxVersion()
put oxTransportInfo() into tInfo
put tInfo["offlineAddress"]
put tInfo["deterministicOnion"]
put oxIsReady() is among the items of "true,false"
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:
put "duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion" into tAddress
put oxIsValidAddress(tAddress)
put "e" into char 1 of tAddress
put oxIsValidAddress(tAddress)
put oxIsValidAddress("expyuzz4wqqyqhjn.onion")
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:
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
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:
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)
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::
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:"
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
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
duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion true
Words in this lesson
oxVersionoxTransportInfooxIsReadyoxIsValidAddressoxPublicKeyFromAddressoxAddressFromPublicKeysxSignKeypairFromSeedoxDialoxSetStreamCallbackoxWriteoxStreamStateoxCloseStreamoxConnectControloxCreateServiceoxCreateServiceFromSeedoxShutdownEach one finds every lesson that uses it. The Dictionary has the whole story of each, with its syntax and more examples.