Introduction to GNUnet GNS

So, recently I saw some old-ish discussion on hacker news anout GNS, and mostly the misunderstanding on how it work. So this is my attempt to explain it. Espically to developers whom have not dabbled into the world of decentralized systems.

In this post, I'll assume that you are already

What is GNS?

GNS is GNU's solution to DNS. DNS being the name system that everyone uses to resolve human-remanable names to IP addresses. However, DNS has it's problems. Like your DNS provider knows which sites you visit, can easily censor you and sites are vulnerable to spoofing if DNSSEC is not enabled. Even today, with the advent of DNSSEC and DoH/DNSCrypt, your DNS provider still knows which sites you visit and can setup RPZ to stop you from visiting sites they don't like.

Sure, all the problems mentioned can be solve by setting up your own recursive resolver. But that still assumes ICANN is not acting evil. And that you can trust the root servers. And your country does not censor DoH/DNSCrypt on the network level. None of these can be 100% guaranteed. Even though the chance is really really low.

GNS is designed as a 0 trust system. It does not assume that any of the servers are not evil. And assumes no central authority. Everything is decentralized and distributed. There does exist a central authority - GANA. But it's only purpose is to assign new GNS record types if new demand comes. It does not govern names or resolution in any way.

How to use GNS?

The easiest way to access GNS is through the command line. You can use the gnunet-gns command lookup names. By default there's 2 "zones" shipped under GNS. gnunet.org and .pin. The pin zone is something special that we will discuss later. The gnunet.org zone simply points to the GNUnet website itself. You can lookup the gnunet.org zone by running gnunet-gns --u gnunet.org. And specsify the record type with -t. You should see something like this:

❯ gnunet-arm -s # Remember to start GNUnet first

❯ gnunet-gns --lookup=www.gnunet.org -t AAAA
www.gnunet.org:
Got `AAAA' record: 2a07:6b47:100:464::9357:ffda
Got `AAAA' record: 2a07:6b47:100:464::9357:ffdb

❯ gnunet-gns --lookup=www.gnunet.org -t A
www.gnunet.org:
Got `A' record: 147.87.255.218

Congrats. You have looked up a name on GNUnet! Are there other domains one can lookup? No. There is none. See, GNS decided that in order to avoid the dilemma of either having a central authority with can go rogue or have no authority but have tons of domain squatters, they decided to not have root zones besides the 2 mentioned above. Instead, users are responsible to decide who they trust and import their zones accordingly. "Zero trust" remember?

So how do I import a zone?

Not so hurry, before importing a zone. You need your own zone first. This is easy enough. Just 2 steps

  • Create an Ego (Identity) with the same name as your zone.
  • Insert a name and record into your zone.

❯ gnunet-identity -C bob # Create an ego with the name bob
❯ gnunet-namestore -z bob -a -n server1 -t A -V 192.168.0.1 -e 20min -p

Now, you have a local zone bob with a record server1 pointing to 192.168.0.1. This can be verified 2 ways. One by running gnunet-gns -u server1.bob -t A. Two by using gnunet-namestore -D -z bob. The latter will show you all records in your zone.

❯ gnunet-gns -u server1.bob -t A
server1.bob:
Got `A' record: 192.168.0.1

❯ gnunet-namestore -D -z bob
server1.bob:
        A: 192.168.0.1 (1 m)  PUBLIC

To import someone else's zone. You need to know their public key. I'll provide mine as an example. Here. 000G0032GEEP0Z0HZB2DPY6R38EFTQTEY62QQGB56RY515KQB7CC5KFAQM. It has a record gemini pointing to my Gemini capsule. To import my zone into bob. You insert a PKEY (short for public key) record into bob and give my key a name. In my example, I'll use clehaxze, my username.

❯ gnunet-namestore -z bob -a -n clehaxze -t PKEY -V 000G0032GEEP0Z0HZB2DPY6R38EFTQTEY62QQGB56RY515KQB7CC5KFAQM -e 20min -p
❯ gnunet-gns -u gemini.clehaxze.bob -t A
gemini.clehaxze.bob:
Got `A' record: 61.61.239.115

Done! That's how you build out your own GNS zone. And that's how a zero trust name system works. You can import anyone's zone and they can import yours. Anyone can also import your zone or lookup your public records. But you are the only one who can insert records into your zone. Now, I want to share some more tricky stuff. First, you can lookup names direcly using the public key. It's less common to do so as it defeats the purpose of a name system. But libgnunetchat and some programs does this so you can store configurations and preferences on GNS and have them accessible from anywhere. To lookup the same "gemini" record form my public key. Run

❯ gnunet-gns -u gemini.000G0032GEEP0Z0HZB2DPY6R38EFTQTEY62QQGB56RY515KQB7CC5KFAQM -t A
gemini.000G0032GEEP0Z0HZB2DPY6R38EFTQTEY62QQGB56RY515KQB7CC5KFAQM:
Got `A' record: 61.61.239.115

How GNS works?

Unlike DNS, GNS is not a client-server architecture. You don't run a "GNS server" when you create a record. Instead GNS uses some cleaver cryptography and the GNUnet DHT (Distributed Hash Table) to enable private and anonymous name lookup.

What is a DHT?

For those who don't know. A DHT allows nodes in the DHT network to 1. set a value given a key. And 2. Retrieve a value given a key. Just like when you use a dictionary in Python or JavaScript. However setting a value has a global effect. Setting the key "hello" to "world" (hash_table["hello"] = "world") will make the value "world" available to all nodes across the network. And retrieving a value is also global.

In fact, the default GNUnet installation comes with commands to mess with the DHT. You can set a value with gnunet-dht-put and retrieve a value with gnunet-dht-get.

❯ gnunet-dht-put -k hello -V world

❯ # On another node, and wait a few seconds for the value to propagate
❯ gnunet-dht-get -k hello
Result 0, type 8:
world

Readers familare with DHT might ask: Isn't that thing easy to attack and censor? Kind of. The GNUnet's variant of DHT - the R5N DHT is resistant to routing attacks by randomizing the route to both set and get values from the DHT as well as sending multiple requests, hoping one works. This reduces the chance of a node able to censor a value or even just malfunctioning nodes affecting the network. However, it does not prevent nodes from sending fake data. That GNS has to deal with - and will be explained later.

Publishing and looking up records

Essentially, GNS uses the DHT to store and retrieve records. This way both the zone owner does not know who looked up name within. Nor does the client know which node owns the zone. However, it does it in a cleaver way that records are verified to be authentic and not tampered with. You can read the GNS RFC draft for detailed information.

But here is a quick summary. When you insert a public record into your zone. GNS derives the record key key = KDF(public-key, label). KDF literally means Key Derivation Function. It converts a key and some data into a new key.

Or, something like this in Python

key = KDF(public_key, label)
record = GNSRecord(
    data = encrypt(data, KDF2(key, label)),
    signature = sign(data, key),
    expiration = expiration
)
dht.set(key, record)

Now when you lookup a record. GNS will derrive the key and retrieve the record from the DHT. It will then verify the record is authentic by checking the signature. If the signature is valid, it decrypts the data and return it to you. If the signature is invalid, it will return an error.

key = KDF(public_key, label)
record = dht.get(key)
if not verify(record.data, record.signature, key):
    raise Exception("Invalid signature")
return decrypt(record.data, key)

This cleaver use of the DHT have some benefits. First, record publisher and lookup are anonymous. As far as the DHT is concerned, you are just setting and getting GNS records. It's hard to infer weather you are publishing or just replicating records for caching. Second, it decouples the record publisher and the record lookup. Improving privacy. Finally, unlike plain DHT, GNS records are encrypted and signed. Attackers cannot tamper with records without being detected.

Also, since nodes on the network can verify the record without able to decrypt it. Nodes will stop propagating invalid records. Preventing attackers from flooding the network with fake records. Thus poisoning attacks are not possible.

Author's profile. Photo taken in VRChat by my friend Tast+
Martin Chang
Systems software, HPC, GPGPU and AI. I mostly write stupid C++ code. Sometimes does AI research. Chronic VRChat addict

I run TLGS, a major search engine on Gemini. Used by Buran by default.


  • marty1885 \at protonmail.com
  • Matrix: @clehaxze:matrix.clehaxze.tw
  • Jami: a72b62ac04a958ca57739247aa1ed4fe0d11d2df