gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[gnunet-go] branch master updated: Improved handling of pending DHT resu


From: gnunet
Subject: [gnunet-go] branch master updated: Improved handling of pending DHT results.
Date: Mon, 18 Jul 2022 11:18:25 +0200

This is an automated email from the git hooks/post-receive script.

bernd-fix pushed a commit to branch master
in repository gnunet-go.

The following commit(s) were added to refs/heads/master by this push:
     new f425c2a  Improved handling of pending DHT results.
f425c2a is described below

commit f425c2aeef06d1a6105678c8b058bdde65a26e78
Author: Bernd Fix <brf@hoi-polloi.org>
AuthorDate: Mon Jul 18 11:16:34 2022 +0200

    Improved handling of pending DHT results.
---
 README.md                                          |  52 ++-
 src/gnunet/build.sh                                |   2 +-
 src/gnunet/cmd/gnunet-service-dht-go/main.go       |  33 +-
 src/gnunet/cmd/gnunet-service-gns-go/main.go       |   9 +-
 .../cmd/gnunet-service-revocation-go/main.go       |   9 +-
 src/gnunet/cmd/peer_mockup/main.go                 |  40 +-
 src/gnunet/cmd/revoke-zonekey/main.go              |  80 ++--
 src/gnunet/cmd/vanityid/main.go                    |  10 +-
 src/gnunet/config/config.go                        |  56 +--
 src/gnunet/config/config_test.go                   |   2 +-
 src/gnunet/config/gnunet-config.json               |  14 +-
 src/gnunet/core/core.go                            | 186 +++-----
 src/gnunet/core/core_test.go                       |  32 +-
 src/gnunet/core/event.go                           |  17 +
 src/gnunet/core/hello_test.go                      |   1 -
 src/gnunet/core/peer.go                            |  13 +-
 src/gnunet/core/peer_test.go                       |   8 +-
 src/gnunet/crypto/gns.go                           | 109 +++--
 src/gnunet/crypto/gns_edkey.go                     |  46 +-
 src/gnunet/crypto/gns_pkey.go                      |  45 +-
 src/gnunet/crypto/gns_test.go                      |  18 +-
 src/gnunet/crypto/hash.go                          |  13 +-
 src/gnunet/crypto/key_exchange_test.go             |  52 +--
 src/gnunet/crypto/keys_test.go                     |   2 +-
 src/gnunet/enums/blocktype_string.go               |   2 +-
 src/gnunet/enums/dht.go                            |  14 +-
 src/gnunet/enums/dht_block_type.go                 |   3 +-
 src/gnunet/enums/gns.go                            |   3 +
 src/gnunet/enums/gns_type.go                       |   1 +
 src/gnunet/enums/gnunet-dht.tpl                    |   3 +-
 src/gnunet/enums/gnunet-gns.tpl                    |   1 +
 src/gnunet/enums/gnunet-signature.tpl              |   1 +
 src/gnunet/enums/signature_purpose.go              |   1 +
 src/gnunet/go.mod                                  |   3 +-
 src/gnunet/go.sum                                  |   6 +-
 src/gnunet/message/factory.go                      |  12 +-
 src/gnunet/message/msg_core.go                     |  30 +-
 src/gnunet/message/msg_dht_p2p.go                  | 473 +++++++++++++++++++
 src/gnunet/message/msg_hello.go                    |  20 +-
 src/gnunet/message/msg_hello_dht.go                | 167 -------
 src/gnunet/message/msg_namecache.go                |   2 +-
 src/gnunet/message/msg_transport.go                |  13 +-
 src/gnunet/message/types.go                        |   5 +-
 src/gnunet/service/client.go                       |   1 -
 src/gnunet/service/connection.go                   |  42 +-
 src/gnunet/service/dht/blocks/filters.go           | 296 ++++++++++++
 src/gnunet/service/dht/blocks/filters_test.go      | 110 +++++
 src/gnunet/service/dht/blocks/generic.go           |  64 ++-
 src/gnunet/service/dht/blocks/generic_test.go      |  67 ---
 src/gnunet/service/dht/blocks/gns.go               |  31 +-
 src/gnunet/service/dht/blocks/handlers.go          |  80 ++++
 src/gnunet/service/dht/blocks/hello.go             | 253 +++++++++--
 src/gnunet/service/dht/blocks/types.go             |  26 --
 src/gnunet/service/dht/bloomfilter.go              | 123 -----
 src/gnunet/service/dht/messages.go                 | 380 ++++++++++++++++
 src/gnunet/service/dht/module.go                   | 155 +++++--
 src/gnunet/service/dht/resulthandler.go            | 351 ++++++++++++++
 src/gnunet/service/dht/routingtable.go             | 305 ++++++++-----
 src/gnunet/service/dht/routingtable_test.go        |  42 +-
 src/gnunet/service/dht/rpc.go                      |  50 +-
 src/gnunet/service/dht/service.go                  |  56 +--
 src/gnunet/service/gns/block_handler.go            |   6 +-
 src/gnunet/service/gns/dns.go                      |   4 +-
 src/gnunet/service/gns/module.go                   |  30 +-
 src/gnunet/service/gns/rpc.go                      |   4 +-
 src/gnunet/service/gns/service.go                  |  22 +-
 src/gnunet/service/module.go                       |  20 +-
 src/gnunet/service/namecache/module.go             |   9 +-
 src/gnunet/service/revocation/module.go            |   7 +-
 src/gnunet/service/revocation/pow.go               |  27 +-
 src/gnunet/service/revocation/pow_test.go          |   1 -
 src/gnunet/service/revocation/rpc.go               |   4 +-
 src/gnunet/service/revocation/service.go           |   9 +-
 src/gnunet/service/rpc.go                          |  29 +-
 src/gnunet/service/service.go                      |   3 +-
 src/gnunet/service/store.go                        | 502 ---------------------
 src/gnunet/{util => service/store}/database.go     |  63 +--
 src/gnunet/service/{dht => store}/dhtstore_test.go |  15 +-
 src/gnunet/service/store/store.go                  | 278 ++++++++++++
 src/gnunet/service/store/store_fs.go               | 287 ++++++++++++
 src/gnunet/service/store/store_fs_meta.go          | 174 +++++++
 src/gnunet/service/store/store_fs_meta.sql         |  29 ++
 src/gnunet/test/gnunet-dhtu/main.go                | 206 ---------
 src/gnunet/transport/endpoint.go                   |  31 +-
 src/gnunet/transport/reader_writer.go              |  41 +-
 src/gnunet/transport/responder.go                  |   8 +
 src/gnunet/transport/transport.go                  |  32 +-
 src/gnunet/util/address.go                         |  56 +--
 src/gnunet/util/address_test.go                    |  13 +-
 src/gnunet/util/array.go                           |   5 +
 src/gnunet/util/base32.go                          |   4 +-
 src/gnunet/util/base32_test.go                     |   2 +-
 src/gnunet/util/fs.go                              |   2 +-
 src/gnunet/util/{misc.go => map.go}                |  78 ++--
 src/gnunet/util/misc.go                            | 105 +----
 src/gnunet/util/peer.go                            |  94 ++++
 src/gnunet/util/peer_id.go                         |  59 ---
 src/gnunet/util/rnd.go                             |  12 +-
 src/gnunet/util/time.go                            |  17 +-
 99 files changed, 4063 insertions(+), 2266 deletions(-)

diff --git a/README.md b/README.md
index 3373608..d4575e5 100644
--- a/README.md
+++ b/README.md
@@ -137,6 +137,56 @@ chown gnunet:gnunet 
/var/lib/gnunet/.local/share/gnunet/private_key.ecc
 chmod 600 /var/lib/gnunet/.local/share/gnunet/private_key.ecc
 ```
 
-For gnunet-go configuration files you need to paste the result of
+For `gnunet-go` configuration files you need to paste the result of
 `echo "<hex.seed>" | xxd -r -p | base64` into the `PrivateSeed` field in the
 `NodeConfig` section.
+
+## Using gnunet-go in your own projects
+
+`gnunet-go` is not a standard Go module for direct use (via go.mod) in other
+packages, but designed as a stand-alone application. The rationale behind was
+to **not** hard link the code to a single Git provider.
+
+If you are interested in using (parts of) `gnunet-go` in your own projects, the
+following step-by-step instructions show the easiest route.
+
+* `git clone https://github.com/bfix/gnunet-go` into folder
+`/home/user/gnunet-go` (or any other folder if you adjust the instructions
+accordingly).
+
+* create project folder and change into it
+* run `go mod init test` (replace test with the name of your project)
+* create a simple test `main.go`
+
+```go
+package main
+
+import (
+    "crypto/rand"
+    "fmt"
+    "gnunet/util"
+)
+
+func main() {
+    a := make([]byte, 32)
+    rand.Read(a)
+    fmt.Println(util.EncodeBinaryToString(a))
+}
+```
+
+* edit `go.mod` and add at end of file:
+
+```bash
+require gnunet v0.1.27
+
+replace gnunet v0.1.27 => /home/user/gnunet-go/src/gnunet
+```
+
+* run `go mod tidy`
+* build test program: `go build`
+* run test program: `./test`
+
+The only disadvantage of this approach is that you have to update the source
+code for `gnunet-go` yourself. From time to time or on demand, do a `git pull`
+followed by a `go mod tidy` described above. No version control is supported
+either because the dependency for `gnunet-go` is redirected to a local folder.
diff --git a/src/gnunet/build.sh b/src/gnunet/build.sh
index 39ecbaf..20f6933 100755
--- a/src/gnunet/build.sh
+++ b/src/gnunet/build.sh
@@ -1,4 +1,4 @@
 #!/bin/bash
 
-go generate ./...
+[ "$1" = "withgen" ] && go generate ./...
 go install -v -gcflags "-N -l" ./...
diff --git a/src/gnunet/cmd/gnunet-service-dht-go/main.go 
b/src/gnunet/cmd/gnunet-service-dht-go/main.go
index ef6da91..8e23ac2 100644
--- a/src/gnunet/cmd/gnunet-service-dht-go/main.go
+++ b/src/gnunet/cmd/gnunet-service-dht-go/main.go
@@ -21,7 +21,6 @@ package main
 import (
        "context"
        "flag"
-       "net/rpc"
        "os"
        "os/signal"
        "strings"
@@ -58,7 +57,7 @@ func main() {
        flag.StringVar(&cfgFile, "c", "gnunet-config.json", "GNUnet 
configuration file")
        flag.StringVar(&socket, "s", "", "GNS service socket")
        flag.StringVar(&param, "p", "", "socket parameters (<key>=<value>,...)")
-       flag.IntVar(&logLevel, "L", logger.INFO, "DHT log level (default: 
INFO)")
+       flag.IntVar(&logLevel, "L", logger.DBG, "DHT log level (default: DBG)")
        flag.StringVar(&rpcEndp, "R", "", "JSON-RPC endpoint (default: none)")
        flag.Parse()
 
@@ -71,7 +70,7 @@ func main() {
        // apply configuration
        logger.SetLogLevel(logLevel)
        if len(socket) == 0 {
-               socket = config.Cfg.GNS.Service.Socket
+               socket = config.Cfg.DHT.Service.Socket
        }
        params := make(map[string]string)
        if len(param) > 0 {
@@ -80,7 +79,7 @@ func main() {
                        params[kv[0]] = kv[1]
                }
        } else {
-               params = config.Cfg.GNS.Service.Params
+               params = config.Cfg.DHT.Service.Params
        }
 
        // instantiate core service
@@ -93,8 +92,8 @@ func main() {
        defer c.Shutdown()
 
        // start a new DHT service
-       var dhtSrv service.Service
-       if dhtSrv, err = dht.NewService(ctx, c); err != nil {
+       var dhtSrv *dht.Service
+       if dhtSrv, err = dht.NewService(ctx, c, config.Cfg.DHT); err != nil {
                logger.Printf(logger.ERROR, "[dht] failed to create DHT 
service: %s\n", err.Error())
                return
        }
@@ -104,6 +103,14 @@ func main() {
                return
        }
 
+       // hande network size estimation: if a fixed number of peers are present
+       // in the network config, use that value; otherwise utilize the NSE
+       // algorithm (not implemented yet)
+       numPeers := config.Cfg.Network.NumPeers
+       if numPeers != 0 {
+               dhtSrv.SetNetworkSize(numPeers)
+       }
+
        // handle command-line arguments for RPC
        if len(rpcEndp) > 0 {
                parts := strings.Split(rpcEndp, ":")
@@ -115,8 +122,8 @@ func main() {
        }
        // start JSON-RPC server on request
        if ep := config.Cfg.RPC.Endpoint; len(ep) > 0 {
-               var rpc *rpc.Server
-               if rpc, err = service.StartRPC(ctx, ep); err != nil {
+               var rpc *service.JRPCServer
+               if rpc, err = service.RunRPCServer(ctx, ep); err != nil {
                        logger.Printf(logger.ERROR, "[dht] RPC failed to start: 
%s", err.Error())
                        return
                }
@@ -125,7 +132,7 @@ func main() {
 
        // handle bootstrap: collect known addresses
        bsList := make([]*util.Address, 0)
-       for _, bs := range config.Cfg.Bootstrap.Nodes {
+       for _, bs := range config.Cfg.Network.Bootstrap {
                // check for HELLO URL
                if strings.HasPrefix(bs, "gnunet://hello/") {
                        var hb *blocks.HelloBlock
@@ -147,7 +154,9 @@ func main() {
        }
        // send HELLO to all bootstrap addresses
        for _, addr := range bsList {
-               c.SendHello(ctx, addr)
+               if err := dhtSrv.SendHello(ctx, addr); err != nil {
+                       logger.Printf(logger.ERROR, "[dht] send HELLO failed: 
%s", err.Error())
+               }
        }
 
        // handle OS signals
@@ -181,5 +190,7 @@ loop:
 
        // terminating service
        cancel()
-       srv.Stop()
+       if err := srv.Stop(); err != nil {
+               logger.Printf(logger.ERROR, "[dht] Failed to stop service: %s", 
err.Error())
+       }
 }
diff --git a/src/gnunet/cmd/gnunet-service-gns-go/main.go 
b/src/gnunet/cmd/gnunet-service-gns-go/main.go
index 72f7de6..ebf2151 100644
--- a/src/gnunet/cmd/gnunet-service-gns-go/main.go
+++ b/src/gnunet/cmd/gnunet-service-gns-go/main.go
@@ -21,7 +21,6 @@ package main
 import (
        "context"
        "flag"
-       "net/rpc"
        "os"
        "os/signal"
        "strings"
@@ -109,8 +108,8 @@ func main() {
        }
        // start JSON-RPC server on request
        if ep := config.Cfg.RPC.Endpoint; len(ep) > 0 {
-               var rpc *rpc.Server
-               if rpc, err = service.StartRPC(ctx, ep); err != nil {
+               var rpc *service.JRPCServer
+               if rpc, err = service.RunRPCServer(ctx, ep); err != nil {
                        logger.Printf(logger.ERROR, "[gns] RPC failed to start: 
%s", err.Error())
                        return
                }
@@ -148,5 +147,7 @@ loop:
 
        // terminating service
        cancel()
-       srv.Stop()
+       if err = srv.Stop(); err != nil {
+               logger.Printf(logger.ERROR, "[gns] Failed to stop service: %s", 
err.Error())
+       }
 }
diff --git a/src/gnunet/cmd/gnunet-service-revocation-go/main.go 
b/src/gnunet/cmd/gnunet-service-revocation-go/main.go
index 564f430..93c5432 100644
--- a/src/gnunet/cmd/gnunet-service-revocation-go/main.go
+++ b/src/gnunet/cmd/gnunet-service-revocation-go/main.go
@@ -21,7 +21,6 @@ package main
 import (
        "context"
        "flag"
-       "net/rpc"
        "os"
        "os/signal"
        "strings"
@@ -109,8 +108,8 @@ func main() {
        }
        // start JSON-RPC server on request
        if ep := config.Cfg.RPC.Endpoint; len(ep) > 0 {
-               var rpc *rpc.Server
-               if rpc, err = service.StartRPC(ctx, ep); err != nil {
+               var rpc *service.JRPCServer
+               if rpc, err = service.RunRPCServer(ctx, ep); err != nil {
                        logger.Printf(logger.ERROR, "[revocation] RPC failed to 
start: %s", err.Error())
                        return
                }
@@ -148,5 +147,7 @@ loop:
 
        // terminating service
        cancel()
-       srv.Stop()
+       if err := srv.Stop(); err != nil {
+               logger.Printf(logger.ERROR, "[revocation] Failed to stop 
service: %s", err.Error())
+       }
 }
diff --git a/src/gnunet/cmd/peer_mockup/main.go 
b/src/gnunet/cmd/peer_mockup/main.go
index 96f62da..4d29927 100644
--- a/src/gnunet/cmd/peer_mockup/main.go
+++ b/src/gnunet/cmd/peer_mockup/main.go
@@ -15,6 +15,7 @@ import (
        "gnunet/message"
        "gnunet/service"
 
+       "github.com/bfix/gospel/crypto/ed25519"
        "github.com/bfix/gospel/logger"
 )
 
@@ -33,10 +34,9 @@ var (
                },
        }
        // configuration for remote node
-       remoteCfg  = "3GXXMNb5YpIUO7ejIR2Yy0Cf5texuLfDjHkXcqbPxkc="
-       remoteAddr = "udp://172.17.0.5:2086"
+       remoteCfg = "3GXXMNb5YpIUO7ejIR2Yy0Cf5texuLfDjHkXcqbPxkc="
 
-       // top-level variables used accross functions
+       // top-level variables used across functions
        local  *core.Peer // local peer (with private key)
        remote *core.Peer // remote peer
        c      *core.Core
@@ -79,7 +79,9 @@ func main() {
 
        if !asServer {
                // we start the message exchange
-               c.Send(ctx, remote.GetID(), 
message.NewTransportTCPWelcomeMsg(c.PeerID()))
+               if err := c.Send(ctx, remote.GetID(), 
message.NewTransportTCPWelcomeMsg(c.PeerID())); err != nil {
+                       fmt.Printf("send message failed: %s", err.Error())
+               }
        }
 
        // handle OS signals
@@ -117,23 +119,27 @@ loop:
 // process incoming messages and send responses; it is used for protocol 
exploration only.
 // it tries to mimick the message flow between "real" GNUnet peers.
 func process(ctx context.Context, ev *core.Event) {
-
        logger.Printf(logger.DBG, "<<< %s", ev.Msg.String())
 
        switch msg := ev.Msg.(type) {
-
        case *message.TransportTCPWelcomeMsg:
-               c.Send(ctx, ev.Peer, message.NewTransportPingMsg(ev.Peer, nil))
+               if err := c.Send(ctx, ev.Peer, 
message.NewTransportPingMsg(ev.Peer, nil)); err != nil {
+                       logger.Printf(logger.ERROR, "TransportTCPWelcomeMsg 
send failed: %s", err.Error())
+                       return
+               }
 
        case *message.HelloMsg:
 
        case *message.TransportPingMsg:
                mOut := message.NewTransportPongMsg(msg.Challenge, nil)
                if err := mOut.Sign(local.PrvKey()); err != nil {
-                       logger.Println(logger.ERROR, "PONG: signing failed")
+                       logger.Printf(logger.ERROR, "PONG signing failed: %s", 
err.Error())
+                       return
+               }
+               if err := c.Send(ctx, ev.Peer, mOut); err != nil {
+                       logger.Printf(logger.ERROR, "TransportPongMsg send 
failed: %s", err.Error())
                        return
                }
-               c.Send(ctx, ev.Peer, mOut)
                logger.Printf(logger.DBG, ">>> %s", mOut)
 
        case *message.TransportPongMsg:
@@ -148,7 +154,9 @@ func process(ctx context.Context, ev *core.Event) {
        case *message.SessionSynMsg:
                mOut := message.NewSessionSynAckMsg()
                mOut.Timestamp = msg.Timestamp
-               c.Send(ctx, ev.Peer, mOut)
+               if err := c.Send(ctx, ev.Peer, mOut); err != nil {
+                       logger.Printf(logger.ERROR, "SessionSynAckMsg send 
failed: %s", err.Error())
+               }
                logger.Printf(logger.DBG, ">>> %s", mOut)
 
        case *message.SessionQuotaMsg:
@@ -157,7 +165,9 @@ func process(ctx context.Context, ev *core.Event) {
 
        case *message.SessionKeepAliveMsg:
                mOut := message.NewSessionKeepAliveRespMsg(msg.Nonce)
-               c.Send(ctx, ev.Peer, mOut)
+               if err := c.Send(ctx, ev.Peer, mOut); err != nil {
+                       logger.Printf(logger.ERROR, "SessionKeepAliveRespMsg 
send failed: %s", err.Error())
+               }
                logger.Printf(logger.DBG, ">>> %s", mOut)
 
        case *message.EphemeralKeyMsg:
@@ -171,9 +181,13 @@ func process(ctx context.Context, ev *core.Event) {
                }
                remote.SetEphKeyMsg(msg)
                mOut := local.EphKeyMsg()
-               c.Send(ctx, ev.Peer, mOut)
+               if err := c.Send(ctx, ev.Peer, mOut); err != nil {
+                       logger.Printf(logger.ERROR, "EphKeyMsg send failed: 
%s", err.Error())
+               }
                logger.Printf(logger.DBG, ">>> %s", mOut)
-               secret = crypto.SharedSecret(local.EphPrvKey(), 
remote.EphKeyMsg().Public())
+               pk := 
ed25519.NewPublicKeyFromBytes(remote.EphKeyMsg().Public().Data)
+               secret = crypto.SharedSecret(local.EphPrvKey(), pk)
+               fmt.Printf("Shared secret: %s\n", secret.String())
 
        default:
                fmt.Printf("!!! %v\n", msg)
diff --git a/src/gnunet/cmd/revoke-zonekey/main.go 
b/src/gnunet/cmd/revoke-zonekey/main.go
index 0713582..fe56946 100644
--- a/src/gnunet/cmd/revoke-zonekey/main.go
+++ b/src/gnunet/cmd/revoke-zonekey/main.go
@@ -43,10 +43,10 @@ import (
 
 // State of RevData calculation
 const (
-       S_NEW    = iota // start new PoW calculation
-       S_CONT          // continue PoW calculation
-       S_DONE          // PoW calculation done
-       S_SIGNED        // revocation data signed
+       StateNew    = iota // start new PoW calculation
+       StateCont          // continue PoW calculation
+       StateDone          // PoW calculation done
+       StateSigned        // revocation data signed
 )
 
 // RevData is the storage layout for persistent data used by this program.
@@ -67,7 +67,7 @@ func ReadRevData(filename string, bits int, zk 
*crypto.ZoneKey) (rd *RevData, er
                Rd:      revocation.NewRevDataCalc(zk),
                Numbits: uint8(bits),
                T:       util.NewRelativeTime(0),
-               State:   S_NEW,
+               State:   StateNew,
        }
 
        // read revocation object from file. If the file does not exist, a new
@@ -80,24 +80,23 @@ func ReadRevData(filename string, bits int, zk 
*crypto.ZoneKey) (rd *RevData, er
        dataBuf := make([]byte, rd.size())
        var n int
        if n, err = file.Read(dataBuf); err != nil {
-               err = fmt.Errorf("Error reading file: " + err.Error())
+               err = fmt.Errorf("error reading file: " + err.Error())
                return
        }
        if n != len(dataBuf) {
-               err = fmt.Errorf("File size mismatch")
+               err = fmt.Errorf("file size mismatch")
                return
        }
        if err = data.Unmarshal(&rd, dataBuf); err != nil {
-               err = fmt.Errorf("File corrupted: " + err.Error())
+               err = fmt.Errorf("file corrupted: " + err.Error())
                return
        }
        if !zk.Equal(&rd.Rd.RevData.ZoneKeySig.ZoneKey) {
-               err = fmt.Errorf("Zone key mismatch")
+               err = fmt.Errorf("zone key mismatch")
                return
        }
-       bits = int(rd.Numbits)
        if err = file.Close(); err != nil {
-               err = fmt.Errorf("Error closing file: " + err.Error())
+               err = fmt.Errorf("error closing file: " + err.Error())
        }
        return
 }
@@ -106,24 +105,24 @@ func ReadRevData(filename string, bits int, zk 
*crypto.ZoneKey) (rd *RevData, er
 func (r *RevData) Write(filename string) (err error) {
        var file *os.File
        if file, err = os.Create(filename); err != nil {
-               return fmt.Errorf("Can't write to output file: " + err.Error())
+               return fmt.Errorf("can't write to output file: " + err.Error())
        }
        var buf []byte
        if buf, err = data.Marshal(r); err != nil {
-               return fmt.Errorf("Internal error: " + err.Error())
+               return fmt.Errorf("internal error: " + err.Error())
        }
        if len(buf) != r.size() {
-               return fmt.Errorf("Internal error: Buffer mismatch %d != %d", 
len(buf), r.size())
+               return fmt.Errorf("internal error: Buffer mismatch %d != %d", 
len(buf), r.size())
        }
        var n int
        if n, err = file.Write(buf); err != nil {
-               return fmt.Errorf("Can't write to output file: " + err.Error())
+               return fmt.Errorf("can't write to output file: " + err.Error())
        }
        if n != len(buf) {
-               return fmt.Errorf("Can't write data to output file!")
+               return fmt.Errorf("can't write data to output file")
        }
        if err = file.Close(); err != nil {
-               return fmt.Errorf("Error closing file: " + err.Error())
+               return fmt.Errorf("error closing file: " + err.Error())
        }
        return
 }
@@ -138,7 +137,7 @@ func (r *RevData) size() int {
 //
 // (1) Generate the desired PoWs for the public zone key:
 //     This process can be started, stopped and resumed, so the long
-//     calculation time (usually days or even weeks) can be interruped if
+//     calculation time (usually days or even weeks) can be interrupted if
 //     desired. For security reasons you should only pass the "-z" argument to
 //     this step but not the "-k" argument (private key) as it is not required
 //     to calculate the PoWs.
@@ -164,7 +163,7 @@ func main() {
                zonekey  string // zonekey to be revoked
                prvkey   string // private zonekey (base64-encoded key data)
                testing  bool   // test mode (no minimum difficulty)
-               filename string // name of file for persistance
+               filename string // name of file for persistence
        )
        minDiff := revocation.MinDifficulty
        flag.IntVar(&bits, "b", minDiff+1, "Number of leading zero bits")
@@ -227,27 +226,27 @@ func main() {
 
        // handle revocation data state
        switch rd.State {
-       case S_NEW:
+       case StateNew:
                log.Println("Starting new revocation calculation...")
-               rd.State = S_CONT
+               rd.State = StateCont
 
-       case S_CONT:
+       case StateCont:
                log.Printf("Revocation calculation started at %s\n", 
rd.Rd.Timestamp.String())
                log.Printf("Time spent on calculation: %s\n", rd.T.String())
                log.Printf("Last tested PoW value: %d\n", rd.Last)
                log.Println("Continuing...")
 
-       case S_DONE:
+       case StateDone:
                // calculation complete: sign with private key
                if sk == nil {
                        log.Fatal("Need to sign revocation: private key is 
missing.")
                }
                log.Println("Signing revocation with private key")
-               if err := rd.Rd.Sign(sk); err != nil {
+               if err = rd.Rd.Sign(sk); err != nil {
                        log.Fatal("Failed to sign revocation: " + err.Error())
                }
                // write final revocation
-               rd.State = S_SIGNED
+               rd.State = StateSigned
                if err = rd.Write(filename); err != nil {
                        log.Fatal("Failed to write revocation: " + err.Error())
                }
@@ -278,10 +277,10 @@ func main() {
                        // The calculation was interrupted; we still need to 
compute
                        // more and better PoWs...
                        log.Printf("Incomplete revocation: Only %f zero bits on 
average!\n", average)
-                       rd.State = S_CONT
+                       rd.State = StateCont
                } else {
                        // we have reached the required PoW difficulty
-                       rd.State = S_DONE
+                       rd.State = StateDone
                        // check if we have a valid revocation.
                        log.Println("Revocation calculation complete:")
                        diff, rc := rd.Rd.Verify(false)
@@ -313,22 +312,19 @@ func main() {
                sigCh := make(chan os.Signal, 5)
                signal.Notify(sigCh)
        loop:
-               for {
-                       select {
+               for sig := range sigCh {
                        // handle OS signals
-                       case sig := <-sigCh:
-                               switch sig {
-                               case syscall.SIGKILL, syscall.SIGINT, 
syscall.SIGTERM:
-                                       log.Printf("Terminating (on signal 
'%s')\n", sig)
-                                       cancelFcn()
-                                       break loop
-                               case syscall.SIGHUP:
-                                       log.Println("SIGHUP")
-                               case syscall.SIGURG:
-                                       // TODO: 
https://github.com/golang/go/issues/37942
-                               default:
-                                       log.Println("Unhandled signal: " + 
sig.String())
-                               }
+                       switch sig {
+                       case syscall.SIGKILL, syscall.SIGINT, syscall.SIGTERM:
+                               log.Printf("Terminating (on signal '%s')\n", 
sig)
+                               cancelFcn()
+                               break loop
+                       case syscall.SIGHUP:
+                               log.Println("SIGHUP")
+                       case syscall.SIGURG:
+                               // TODO: 
https://github.com/golang/go/issues/37942
+                       default:
+                               log.Println("Unhandled signal: " + sig.String())
                        }
                }
        }()
diff --git a/src/gnunet/cmd/vanityid/main.go b/src/gnunet/cmd/vanityid/main.go
index 938df61..1c0bf24 100644
--- a/src/gnunet/cmd/vanityid/main.go
+++ b/src/gnunet/cmd/vanityid/main.go
@@ -8,8 +8,9 @@ import (
        "regexp"
        "time"
 
-       "github.com/bfix/gospel/crypto/ed25519"
        "gnunet/util"
+
+       "github.com/bfix/gospel/crypto/ed25519"
 )
 
 func main() {
@@ -32,16 +33,13 @@ func main() {
        seed := make([]byte, 32)
        start := time.Now()
        for i := 0; ; i++ {
-               n, err := rand.Read(seed)
-               if err != nil || n != 32 {
-                       panic(err)
-               }
+               _, _ = rand.Read(seed)
                prv := ed25519.NewPrivateKeyFromSeed(seed)
                pub := prv.Public().Bytes()
                id := util.EncodeBinaryToString(pub)
                for _, r := range reg {
                        if r.MatchString(id) {
-                               elapsed := time.Now().Sub(start)
+                               elapsed := time.Since(start)
                                s1 := hex.EncodeToString(seed)
                                s2 := hex.EncodeToString(prv.D.Bytes())
                                fmt.Printf("%s [%s][%s] (%d tries, %s 
elapsed)\n", id, s1, s2, i, elapsed)
diff --git a/src/gnunet/config/config.go b/src/gnunet/config/config.go
index b4d2840..c4d3722 100644
--- a/src/gnunet/config/config.go
+++ b/src/gnunet/config/config.go
@@ -21,6 +21,7 @@ package config
 import (
        "encoding/json"
        "fmt"
+       "gnunet/util"
        "io/ioutil"
        "reflect"
        "regexp"
@@ -56,12 +57,13 @@ type NodeConfig struct {
 }
 
 //----------------------------------------------------------------------
-// Bootstrap configuration
+// Network configuration
 //----------------------------------------------------------------------
 
-// BootstrapConfig holds parameters for the initial connection to the network.
-type BootstrapConfig struct {
-       Nodes []string `json:"nodes"` // bootstrap nodes
+// NetworkConfig holds parameters for the initial connection to the network.
+type NetworkConfig struct {
+       Bootstrap []string `json:"bootstrap"` // bootstrap nodes
+       NumPeers  int      `json:"numPeers"`  // estimated number of peers (0 = 
use NSE)
 }
 
 //----------------------------------------------------------------------
@@ -93,34 +95,21 @@ type GNSConfig struct {
        MaxDepth     int            `json:"maxDepth"`     // maximum recursion 
depth in resolution
 }
 
-//----------------------------------------------------------------------
-// Generic parameter configuration (handle any key/value settings)
-//----------------------------------------------------------------------
-
-// ParameterConfig handle arbitrary values for a key strings. This necessary
-// e.g. in the 'Storage' configuration, as custom storage implementations
-// require different sets of parameters.
-type ParameterConfig map[string]any
-
-// Get a parameter value with given type 'V'
-func GetParam[V any](params ParameterConfig, key string) (i V, ok bool) {
-       var v any
-       if v, ok = params[key]; ok {
-               if i, ok = v.(V); ok {
-                       return
-               }
-       }
-       return
-}
-
 //----------------------------------------------------------------------
 // DHT configuration
 //----------------------------------------------------------------------
 
 // DHTConfig contains parameters for the distributed hash table (DHT)
 type DHTConfig struct {
-       Service *ServiceConfig  `json:"service"` // socket for DHT service
-       Storage ParameterConfig `json:"storage"` // filesystem storage location
+       Service   *ServiceConfig    `json:"service"`   // socket for DHT service
+       Storage   util.ParameterSet `json:"storage"`   // filesystem storage 
location
+       Routing   *RoutingConfig    `json:"routing"`   // routing table 
configuration
+       Heartbeat int               `json:"heartbeat"` // heartbeat intervall
+}
+
+// RoutingConfig holds parameters for routing tables
+type RoutingConfig struct {
+       PeerTTL int `json:"peerTTL"` // time-out for peers in table
 }
 
 //----------------------------------------------------------------------
@@ -129,8 +118,8 @@ type DHTConfig struct {
 
 // NamecacheConfig contains parameters for the local name cache
 type NamecacheConfig struct {
-       Service *ServiceConfig  `json:"service"` // socket for Namecache service
-       Storage ParameterConfig `json:"storage"` // key/value cache
+       Service *ServiceConfig    `json:"service"` // socket for Namecache 
service
+       Storage util.ParameterSet `json:"storage"` // key/value cache
 }
 
 //----------------------------------------------------------------------
@@ -139,8 +128,8 @@ type NamecacheConfig struct {
 
 // RevocationConfig contains parameters for the key revocation service
 type RevocationConfig struct {
-       Service *ServiceConfig  `json:"service"` // socket for Revocation 
service
-       Storage ParameterConfig `json:"storage"` // persistance mechanism for 
revocation data
+       Service *ServiceConfig    `json:"service"` // socket for Revocation 
service
+       Storage util.ParameterSet `json:"storage"` // persistence mechanism for 
revocation data
 }
 
 //----------------------------------------------------------------------
@@ -153,7 +142,7 @@ type Environment map[string]string
 // Config is the aggregated configuration for GNUnet.
 type Config struct {
        Local      *NodeConfig       `json:"local"`
-       Bootstrap  *BootstrapConfig  `json:"bootstrap"`
+       Network    *NetworkConfig    `json:"network"`
        Env        Environment       `json:"environ"`
        RPC        *RPCConfig        `json:"rpc"`
        DHT        *DHTConfig        `json:"dht"`
@@ -193,7 +182,7 @@ func ParseConfigBytes(data []byte, subst bool) (err error) {
 }
 
 var (
-       rx = regexp.MustCompile("\\$\\{([^\\}]*)\\}")
+       rx = regexp.MustCompile(`\$\{([^\}]*)\}`)
 )
 
 // substString is a helper function to substitute environment variables
@@ -215,7 +204,6 @@ func substString(s string, env map[string]string) string {
 // applySubstitutions traverses the configuration data structure
 // and applies string substitutions to all string values.
 func applySubstitutions(x interface{}, env map[string]string) {
-
        var process func(v reflect.Value)
        process = func(v reflect.Value) {
                for i := 0; i < v.NumField(); i++ {
@@ -224,7 +212,7 @@ func applySubstitutions(x interface{}, env 
map[string]string) {
                                switch fld.Kind() {
                                case reflect.String:
                                        // check for substitution
-                                       s := fld.Interface().(string)
+                                       s, _ := fld.Interface().(string)
                                        for {
                                                s1 := substString(s, env)
                                                if s1 == s {
diff --git a/src/gnunet/config/config_test.go b/src/gnunet/config/config_test.go
index d82019e..b61cb67 100644
--- a/src/gnunet/config/config_test.go
+++ b/src/gnunet/config/config_test.go
@@ -35,7 +35,7 @@ func TestConfigRead(t *testing.T) {
                t.Fatal(err)
        }
        // parse configuration
-       if err := ParseConfigBytes(data, false); err != nil {
+       if err = ParseConfigBytes(data, false); err != nil {
                t.Fatal(err)
        }
        // write configuration
diff --git a/src/gnunet/config/gnunet-config.json 
b/src/gnunet/config/gnunet-config.json
index 167bfa0..27678ab 100644
--- a/src/gnunet/config/gnunet-config.json
+++ b/src/gnunet/config/gnunet-config.json
@@ -1,8 +1,10 @@
 {
-    "bootstrap": {
-        "nodes": [
+    "network": {
+        "bootstrap": [
+            "ip+udp://172.17.0.5:10000",
             
"gnunet://hello/7KTBJ90340HF1Q2GB0A57E2XJER4FDHX8HP5GHEB9125VPWPD27G/BNMDFN6HJCPWSPNBSEC06MC1K8QN1Z2DHRQSRXDTFR7FTBD4JHNBJ2RJAAEZ31FWG1Q3PMN3PXGZQ3Q7NTNEKQZFA7TE2Y46FM8E20R/1653499308?r5n+ip+udp=127.0.0.1%3A7654"
-        ]
+        ],
+        "numPeers": 10
     },
     "local": {
         "name": "ygng",
@@ -33,7 +35,11 @@
             "cache": false,
             "path": "/var/lib/gnunet/dht/store",
             "maxGB": 10
-        }
+        },
+        "routing": {
+            "peerTTL": 10800
+        },
+        "heartbeat": 900
     },
     "gns": {
         "service": {
diff --git a/src/gnunet/core/core.go b/src/gnunet/core/core.go
index 83da8b1..128d154 100644
--- a/src/gnunet/core/core.go
+++ b/src/gnunet/core/core.go
@@ -20,10 +20,10 @@ package core
 
 import (
        "context"
+       "encoding/hex"
        "errors"
        "gnunet/config"
        "gnunet/message"
-       "gnunet/service/dht/blocks"
        "gnunet/transport"
        "gnunet/util"
        "net"
@@ -47,7 +47,7 @@ type EndpointRef struct {
        id     string             // endpoint identifier in configuration
        ep     transport.Endpoint // reference to endpoint
        addr   *util.Address      // public endpoint address
-       upnpId string             // UPNP identifier (empty if unused)
+       upnpID string             // UPNP identifier (empty if unused)
 }
 
 //----------------------------------------------------------------------
@@ -57,7 +57,7 @@ type Core struct {
        local *Peer
 
        // incoming messages from transport
-       incoming chan *transport.TransportMessage
+       incoming chan *transport.Message
 
        // reference to transport implementation
        trans *transport.Transport
@@ -68,37 +68,39 @@ type Core struct {
        // list of known peers with addresses
        peers *util.PeerAddrList
 
+       // list of connected peers
+       connected *util.Map[string, bool]
+
        // List of registered endpoints
        endpoints map[string]*EndpointRef
-
-       // last HELLO message used; re-create if expired
-       lastHello *message.HelloDHTMsg
 }
 
 //----------------------------------------------------------------------
 
 // NewCore creates and runs a new core instance.
 func NewCore(ctx context.Context, node *config.NodeConfig) (c *Core, err 
error) {
-
        // instantiate peer
        var peer *Peer
        if peer, err = NewLocalPeer(node); err != nil {
                return
        }
+       logger.Printf(logger.DBG, "[core] Local node is %s", 
peer.GetID().String())
+
        // create new core instance
-       incoming := make(chan *transport.TransportMessage)
+       incoming := make(chan *transport.Message)
        c = &Core{
                local:     peer,
                incoming:  incoming,
                listeners: make(map[string]*Listener),
                trans:     transport.NewTransport(ctx, node.Name, incoming),
                peers:     util.NewPeerAddrList(),
+               connected: util.NewMap[string, bool](),
                endpoints: make(map[string]*EndpointRef),
        }
        // add all local peer endpoints to transport.
        for _, epCfg := range node.Endpoints {
                var (
-                       upnpId string             // upnp identifier
+                       upnpID string             // upnp identifier
                        local  *util.Address      // local address
                        remote *util.Address      // remote address
                        ep     transport.Endpoint // endpoint reference
@@ -113,7 +115,7 @@ func NewCore(ctx context.Context, node *config.NodeConfig) 
(c *Core, err error)
                        // handle UPNP port forwarding
                        protocol := transport.EpProtocol(epCfg.Network)
                        var localA, remoteA string
-                       if upnpId, remoteA, localA, err = 
c.trans.ForwardOpen(protocol, epCfg.Address[5:], epCfg.Port); err != nil {
+                       if upnpID, remoteA, localA, err = 
c.trans.ForwardOpen(protocol, epCfg.Address[5:], epCfg.Port); err != nil {
                                return
                        }
                        // parse local and remote addresses
@@ -129,7 +131,7 @@ func NewCore(ctx context.Context, node *config.NodeConfig) 
(c *Core, err error)
                                return
                        }
                        remote = local
-                       upnpId = ""
+                       upnpID = ""
                }
                // add endpoint for address
                if ep, err = c.trans.AddEndpoint(ctx, local); err != nil {
@@ -148,7 +150,7 @@ func NewCore(ctx context.Context, node *config.NodeConfig) 
(c *Core, err error)
                        id:     epCfg.ID,
                        ep:     ep,
                        addr:   remote,
-                       upnpId: upnpId,
+                       upnpID: upnpID,
                }
        }
        // run message pump
@@ -165,35 +167,20 @@ func (c *Core) pump(ctx context.Context) {
                case tm := <-c.incoming:
                        logger.Printf(logger.DBG, "[core] Message received from 
%s: %s", tm.Peer, transport.Dump(tm.Msg, "json"))
 
-                       // inspect message for peer state events
-                       var ev *Event
-                       switch msg := tm.Msg.(type) {
-                       case *message.HelloDHTMsg:
-
-                               // verify integrity of message
-                               if ok, err := msg.Verify(tm.Peer); !ok || err 
!= nil {
-                                       logger.Println(logger.WARN, "[core] 
Received invalid DHT_P2P_HELLO message")
-                                       break
-                               }
-                               // keep peer addresses
-                               aList, err := msg.Addresses()
-                               if err != nil {
-                                       logger.Println(logger.WARN, "[core] 
Failed to parse addresses from DHT_P2P_HELLO message")
-                                       break
-                               }
-                               if err := c.Learn(ctx, tm.Peer, aList); err != 
nil {
-                                       logger.Println(logger.WARN, "[core] 
Failed to learn addresses from DHT_P2P_HELLO message: "+err.Error())
-                                       break
-                               }
-
+                       // check if peer is already connected (has an entry in 
PeerAddrist)
+                       _, connected := c.connected.Get(tm.Peer.String())
+                       if !connected {
+                               // no: mark connected
+                               c.connected.Put(tm.Peer.String(), true)
                                // generate EV_CONNECT event
-                               ev = &Event{
+                               c.dispatch(&Event{
                                        ID:   EV_CONNECT,
                                        Peer: tm.Peer,
-                                       Msg:  msg,
-                               }
-                               c.dispatch(ev)
+                               })
+                               // grace period for connection signal
+                               time.Sleep(time.Second)
                        }
+
                        // set default responder (core) if no custom responder
                        // is defined by the receiving endpoint.
                        resp := tm.Resp
@@ -204,13 +191,12 @@ func (c *Core) pump(ctx context.Context) {
                                }
                        }
                        // generate EV_MESSAGE event
-                       ev = &Event{
+                       c.dispatch(&Event{
                                ID:   EV_MESSAGE,
                                Peer: tm.Peer,
                                Msg:  tm.Msg,
-                               Resp: tm.Resp,
-                       }
-                       c.dispatch(ev)
+                               Resp: resp,
+                       })
 
                // wait for termination
                case <-ctx.Done():
@@ -234,12 +220,12 @@ func (c *Core) Send(ctx context.Context, peer 
*util.PeerID, msg message.Message)
        netw := "ip+udp"
 
        // try all addresses for peer
-       aList := c.peers.Get(peer.String(), netw)
+       aList := c.peers.Get(peer, netw)
        maybe := false // message may be sent...
        for _, addr := range aList {
-               logger.Printf(logger.WARN, "[core] Trying to send to %s", 
addr.URI())
+               logger.Printf(logger.INFO, "[core] Trying to send to %s", 
addr.URI())
                // send message to address
-               if err = c.send(ctx, addr, msg); err != nil {
+               if err = c.SendToAddr(ctx, addr, msg); err != nil {
                        // if it is possible that the message was not sent, try 
next address
                        if err != transport.ErrEndpMaybeSent {
                                logger.Printf(logger.WARN, "[core] Failed to 
send to %s: %s", addr.URI(), err.Error())
@@ -252,15 +238,16 @@ func (c *Core) Send(ctx context.Context, peer 
*util.PeerID, msg message.Message)
                return
        }
        if maybe {
-               err = transport.ErrEndpMaybeSent
+               logger.Printf(logger.WARN, "[core] %s", 
transport.ErrEndpMaybeSent.Error())
+               err = nil
        } else {
                err = ErrCoreNotSent
        }
        return
 }
 
-// send message directly to address
-func (c *Core) send(ctx context.Context, addr *util.Address, msg 
message.Message) error {
+// SendToAddr message directly to address
+func (c *Core) SendToAddr(ctx context.Context, addr *util.Address, msg 
message.Message) error {
        // assemble transport message
        tm := transport.NewTransportMessage(c.PeerID(), msg)
        // send on transport
@@ -268,81 +255,16 @@ func (c *Core) send(ctx context.Context, addr 
*util.Address, msg message.Message
 }
 
 // Learn (new) addresses for peer
-func (c *Core) Learn(ctx context.Context, peer *util.PeerID, addrs 
[]*util.Address) (err error) {
+func (c *Core) Learn(ctx context.Context, peer *util.PeerID, addrs 
[]*util.Address) (newPeer bool) {
        // learn all addresses for peer
-       newPeer := false
+       newPeer = false
        for _, addr := range addrs {
                logger.Printf(logger.INFO, "[core] Learning %s for %s (expires 
%s)", addr.URI(), peer, addr.Expires)
-               newPeer = (c.peers.Add(peer.String(), addr) == 1) || newPeer
-       }
-       // new peer detected?
-       if newPeer {
-               // we added a previously unknown peer: send a HELLO
-               var msg *message.HelloDHTMsg
-               if msg, err = c.getHello(); err != nil {
-                       return
-               }
-               logger.Printf(logger.INFO, "[core] Sending HELLO to %s: %s", 
peer, msg)
-               err = c.Send(ctx, peer, msg)
-               // no error if the message might have been sent
-               if err == transport.ErrEndpMaybeSent {
-                       err = nil
-               }
+               newPeer = (c.peers.Add(peer, addr) == 1) || newPeer
        }
        return
 }
 
-// Send the currently active HELLO to given network address
-func (c *Core) SendHello(ctx context.Context, addr *util.Address) (err error) {
-       // get (buffered) HELLO
-       var msg *message.HelloDHTMsg
-       if msg, err = c.getHello(); err != nil {
-               return
-       }
-       logger.Printf(logger.INFO, "[core] Sending HELLO to %s: %s", 
addr.URI(), msg)
-       return c.send(ctx, addr, msg)
-}
-
-// get the recent HELLO if it is defined and not expired;
-// create a new HELLO otherwise.
-func (c *Core) getHello() (msg *message.HelloDHTMsg, err error) {
-       if c.lastHello == nil || c.lastHello.Expires.Expired() {
-               // assemble new HELLO message
-               addrList := make([]*util.Address, 0)
-               for _, epRef := range c.endpoints {
-                       addrList = append(addrList, epRef.addr)
-               }
-               node := c.local
-               var hello *blocks.HelloBlock
-               hello, err = node.HelloData(time.Hour, addrList)
-               if err != nil {
-                       return
-               }
-               msg = message.NewHelloDHTMsg()
-               msg.NumAddr = uint16(len(hello.Addresses()))
-               msg.SetAddresses(hello.Addresses())
-               if err = msg.Sign(c.local.prv); err != nil {
-                       return
-               }
-               // save for later use
-               c.lastHello = msg
-
-               // DEBUG
-               var ok bool
-               if ok, err = msg.Verify(c.PeerID()); !ok || err != nil {
-                       if !ok {
-                               err = errors.New("[core] failed to verify own 
HELLO")
-                       }
-                       logger.Println(logger.ERROR, err.Error())
-                       return
-               }
-               logger.Println(logger.DBG, "[core] New HELLO: 
"+transport.Dump(msg, "json"))
-               return
-       }
-       // we have a valid HELLO for re-use.
-       return c.lastHello, nil
-}
-
 // Addresses returns the list of listening endpoint addresses
 func (c *Core) Addresses() (list []*util.Address, err error) {
        for _, epRef := range c.endpoints {
@@ -365,6 +287,29 @@ func (c *Core) PeerID() *util.PeerID {
 
 //----------------------------------------------------------------------
 
+// Signable interface for objects that can get signed by peer
+type Signable interface {
+       // SignedData returns the byte array to be signed
+       SignedData() []byte
+
+       // SetSignature returns the signature to the signable object
+       SetSignature(*util.PeerSignature) error
+}
+
+// Sign a signable onject with private peer key
+func (c *Core) Sign(obj Signable) error {
+       sd := obj.SignedData()
+       logger.Printf(logger.DBG, "[core] Signing data '%s'", 
hex.EncodeToString(sd))
+       sig, err := c.local.prv.EdSign(sd)
+       if err != nil {
+               return err
+       }
+       logger.Printf(logger.DBG, "[core] --> signature '%s'", 
hex.EncodeToString(sig.Bytes()))
+       return obj.SetSignature(util.NewPeerSignature(sig.Bytes()))
+}
+
+//----------------------------------------------------------------------
+
 // TryConnect is a function which allows the local peer to attempt the
 // establishment of a connection to another peer using an address.
 // When the connection attempt is successful, information on the new
@@ -390,14 +335,6 @@ func (c *Core) Hold(peer *util.PeerID) {}
 // established by HOLD().
 func (c *Core) Drop(peer *util.PeerID) {}
 
-// L2NSE is ESTIMATE_NETWORK_SIZE(), a procedure that provides estimates
-// on the base-2 logarithm of the network size L2NSE, that is the base-2
-// logarithm number of peers in the network, for use by the routing
-// algorithm.
-func (c *Core) L2NSE() float64 {
-       return 0.
-}
-
 //----------------------------------------------------------------------
 // Event listener and event dispatch.
 //----------------------------------------------------------------------
@@ -418,11 +355,12 @@ func (c *Core) Unregister(name string) *Listener {
 
 // internal: dispatch event to listeners
 func (c *Core) dispatch(ev *Event) {
+       logger.Printf(logger.DBG, "[core] Dispatching %v...", ev)
        // dispatch event to listeners
        for _, l := range c.listeners {
                if l.filter.CheckEvent(ev.ID) {
-                       mt := ev.Msg.Header().MsgType
                        if ev.ID == EV_MESSAGE {
+                               mt := ev.Msg.Header().MsgType
                                if mt != 0 && !l.filter.CheckMsgType(mt) {
                                        // skip event
                                        return
diff --git a/src/gnunet/core/core_test.go b/src/gnunet/core/core_test.go
index 29f740b..f87dfef 100644
--- a/src/gnunet/core/core_test.go
+++ b/src/gnunet/core/core_test.go
@@ -36,7 +36,6 @@ import (
 
 // TestCoreSimple test a two node network
 func TestCoreSimple(t *testing.T) {
-
        var (
                peer1Cfg = &config.NodeConfig{
                        Name:        "p1",
@@ -75,11 +74,11 @@ func TestCoreSimple(t *testing.T) {
        }()
 
        // create and run nodes
-       node1, err := NewTestNode(t, ctx, peer1Cfg)
+       node1, err := NewTestNode(ctx, t, peer1Cfg)
        if err != nil {
                t.Fatal(err)
        }
-       node2, err := NewTestNode(t, ctx, peer2Cfg)
+       node2, err := NewTestNode(ctx, t, peer2Cfg)
        if err != nil {
                t.Fatal(err)
        }
@@ -104,7 +103,6 @@ func TestCoreSimple(t *testing.T) {
 
 // TestCoreSimple test a two node network
 func TestCoreUPNP(t *testing.T) {
-
        // configuration data
        var (
                peer1Cfg = &config.NodeConfig{
@@ -143,12 +141,16 @@ func TestCoreUPNP(t *testing.T) {
        }()
 
        // create and run nodes
-       node1, err := NewTestNode(t, ctx, peer1Cfg)
+       node1, err := NewTestNode(ctx, t, peer1Cfg)
        if err != nil {
+               if err == transport.ErrTransNoUPNP {
+                       t.Log("No UPnP available -- skipping test")
+                       return
+               }
                t.Fatal(err)
        }
        defer node1.Shutdown()
-       node2, err := NewTestNode(t, ctx, peer2Cfg)
+       node2, err := NewTestNode(ctx, t, peer2Cfg)
        if err != nil {
                t.Fatal(err)
        }
@@ -180,7 +182,7 @@ func TestDHTU(t *testing.T) {
        }
        // convert arguments
        var (
-               rId   *util.PeerID
+               rID   *util.PeerID
                rAddr *util.Address
                err   error
        )
@@ -211,14 +213,14 @@ func TestDHTU(t *testing.T) {
        }()
 
        // create and run node
-       node, err := NewTestNode(t, ctx, peerCfg)
+       node, err := NewTestNode(ctx, t, peerCfg)
        if err != nil {
                log.Fatal(err)
        }
        defer node.Shutdown()
 
        // learn bootstrap address (triggers HELLO)
-       node.Learn(ctx, rId, rAddr)
+       node.Learn(ctx, rID, rAddr)
 
        // run forever
        var ch chan struct{}
@@ -246,14 +248,12 @@ func (n *TestNode) Learn(ctx context.Context, peer 
*util.PeerID, addr *util.Addr
        if peer != nil {
                label = peer.String()
        }
-       n.t.Logf("[%d] Learning %s for %s", n.id, addr.StringAll(), label)
-       if err := n.core.Learn(ctx, peer, []*util.Address{addr}); err != nil {
-               n.t.Log("Learn: " + err.Error())
-       }
+       n.t.Logf("[%d] Learning %s for %s", n.id, addr.URI(), label)
+       n.core.Learn(ctx, peer, []*util.Address{addr})
 }
 
-func NewTestNode(t *testing.T, ctx context.Context, cfg *config.NodeConfig) 
(node *TestNode, err error) {
-
+func NewTestNode(ctx context.Context, t *testing.T, cfg *config.NodeConfig) 
(node *TestNode, err error) {
+       t.Helper()
        // create test node
        node = new(TestNode)
        node.t = t
@@ -265,7 +265,7 @@ func NewTestNode(t *testing.T, ctx context.Context, cfg 
*config.NodeConfig) (nod
        }
        node.peer = node.core.Peer()
        t.Logf("[%d] Node %s starting", node.id, node.peer.GetID())
-       t.Logf("[%d]   --> %s", node.id, 
hex.EncodeToString(node.peer.GetID().Key))
+       t.Logf("[%d]   --> %s", node.id, 
hex.EncodeToString(node.peer.GetID().Data))
 
        list, err := node.core.Addresses()
        if err != nil {
diff --git a/src/gnunet/core/event.go b/src/gnunet/core/event.go
index 8d05b0d..7d7c7e1 100644
--- a/src/gnunet/core/event.go
+++ b/src/gnunet/core/event.go
@@ -19,6 +19,7 @@
 package core
 
 import (
+       "fmt"
        "gnunet/message"
        "gnunet/transport"
        "gnunet/util"
@@ -29,6 +30,7 @@ import (
 //----------------------------------------------------------------------
 
 // Event types
+//nolint:stylecheck // allow non-camel-case in constants
 const (
        EV_CONNECT    = iota // peer connected
        EV_DISCONNECT        // peer disconnected
@@ -82,6 +84,8 @@ func (f *EventFilter) CheckMsgType(mt uint16) bool {
        return ok
 }
 
+//----------------------------------------------------------------------
+
 // Event sent to listeners
 type Event struct {
        ID    int                 // event type
@@ -91,6 +95,19 @@ type Event struct {
        Label string              // event label (can be empty)
 }
 
+// String returns a human-readable representation of an event.
+func (e *Event) String() string {
+       s := "Event{"
+       if len(e.Label) > 0 {
+               s += "label=" + e.Label + ","
+       }
+       s += fmt.Sprintf("id=%d,peer=%s", e.ID, e.Peer)
+       if e.Msg != nil {
+               s += fmt.Sprintf(",msg=%d", e.Msg.Header().MsgType)
+       }
+       return s + "}"
+}
+
 //----------------------------------------------------------------------
 
 // Listener for network events
diff --git a/src/gnunet/core/hello_test.go b/src/gnunet/core/hello_test.go
index 4abad04..0590c90 100644
--- a/src/gnunet/core/hello_test.go
+++ b/src/gnunet/core/hello_test.go
@@ -71,7 +71,6 @@ func TestHelloURLDirect(t *testing.T) {
 }
 
 func TestHelloURL(t *testing.T) {
-
        // prepare peer and HELLO data
        peer, err := NewLocalPeer(peerCfg)
        if err != nil {
diff --git a/src/gnunet/core/peer.go b/src/gnunet/core/peer.go
index 86ed43a..5b0c3d0 100644
--- a/src/gnunet/core/peer.go
+++ b/src/gnunet/core/peer.go
@@ -103,11 +103,16 @@ func (p *Peer) HelloData(ttl time.Duration, a 
[]*util.Address) (h *blocks.HelloB
        // assemble HELLO data
        h = new(blocks.HelloBlock)
        h.PeerID = p.GetID()
-       h.Expire = util.NewAbsoluteTime(time.Now().Add(ttl))
+       h.Expires = util.NewAbsoluteTime(time.Now().Add(ttl))
        h.SetAddresses(a)
 
        // sign data
-       err = h.Sign(p.prv)
+       sd := h.SignedData()
+       var sig *ed25519.EdSignature
+       if sig, err = p.prv.EdSign(sd); err != nil {
+               return
+       }
+       err = h.SetSignature(util.NewPeerSignature(sig.Bytes()))
        return
 }
 
@@ -139,7 +144,7 @@ func (p *Peer) PubKey() *ed25519.PublicKey {
 // GetID returns the node ID (public key) in binary format
 func (p *Peer) GetID() *util.PeerID {
        return &util.PeerID{
-               Key: util.Clone(p.pub.Bytes()),
+               Data: util.Clone(p.pub.Bytes()),
        }
 }
 
@@ -151,7 +156,7 @@ func (p *Peer) GetIDString() string {
 // Sign a message with the (long-term) private key.
 func (p *Peer) Sign(msg []byte) (*ed25519.EdSignature, error) {
        if p.prv == nil {
-               return nil, fmt.Errorf("No private key")
+               return nil, fmt.Errorf("no private key")
        }
        return p.prv.EdSign(msg)
 }
diff --git a/src/gnunet/core/peer_test.go b/src/gnunet/core/peer_test.go
index f546a52..1be4d42 100644
--- a/src/gnunet/core/peer_test.go
+++ b/src/gnunet/core/peer_test.go
@@ -43,7 +43,6 @@ var (
 )
 
 func TestPeerHello(t *testing.T) {
-
        // generate new local node
        node, err := NewLocalPeer(cfg)
        if err != nil {
@@ -54,13 +53,16 @@ func TestPeerHello(t *testing.T) {
        // This hack will only work for direct listening addresses
        addrList := make([]*util.Address, 0)
        for _, epRef := range cfg.Endpoints {
-               addr, err := util.ParseAddress(epRef.Addr())
-               if err != nil {
+               var addr *util.Address
+               if addr, err = util.ParseAddress(epRef.Addr()); err != nil {
                        t.Fatal(err)
                }
                addrList = append(addrList, addr)
        }
        h, err := node.HelloData(TTL, addrList)
+       if err != nil {
+               t.Fatal(err)
+       }
 
        // convert to URL and back
        u := h.URL()
diff --git a/src/gnunet/crypto/gns.go b/src/gnunet/crypto/gns.go
index d62a047..27a52d3 100644
--- a/src/gnunet/crypto/gns.go
+++ b/src/gnunet/crypto/gns.go
@@ -28,6 +28,7 @@ import (
        "gnunet/util"
 
        "github.com/bfix/gospel/data"
+       "github.com/bfix/gospel/logger"
        "github.com/bfix/gospel/math"
        "golang.org/x/crypto/hkdf"
 )
@@ -105,7 +106,7 @@ type ZoneKeyImpl interface {
 
        // Derive a zone key from this zone key based on a big integer
        // (key blinding). Returns the derived key and the blinding value.
-       Derive(h *math.Int) (ZoneKeyImpl, *math.Int)
+       Derive(h *math.Int) (ZoneKeyImpl, *math.Int, error)
 
        // BlockKey returns the key for block en-/decryption
        BlockKey(label string, expires util.AbsoluteTime) (skey []byte)
@@ -126,7 +127,7 @@ type ZonePrivateImpl interface {
 
        // Derive a private key from this zone key based on a big integer
        // (key blinding). Returns the derived key and the blinding value.
-       Derive(h *math.Int) (ZonePrivateImpl, *math.Int)
+       Derive(h *math.Int) (ZonePrivateImpl, *math.Int, error)
 
        // Sign binary data and return the signature
        Sign(data []byte) (*ZoneSignature, error)
@@ -143,10 +144,14 @@ type ZoneSigImpl interface {
 //----------------------------------------------------------------------
 // Zone types
 //----------------------------------------------------------------------
+
+//nolint:stylecheck // allow non-camel-case in constants
 var (
        ZONE_PKEY  = uint32(enums.GNS_TYPE_PKEY)
        ZONE_EDKEY = uint32(enums.GNS_TYPE_EDKEY)
+)
 
+var (
        // register available zone types for BlockHandler
        ZoneTypes = []enums.GNSType{
                enums.GNS_TYPE_PKEY,
@@ -177,6 +182,7 @@ var (
 // Error codes
 var (
        ErrNoImplementation = errors.New("unknown zone implementation")
+       ErrUnknownZoneType  = errors.New("unknown zone type")
 )
 
 // GetImplementation return the factory for a given zone type.
@@ -204,14 +210,14 @@ type ZonePrivate struct {
 }
 
 // NewZonePrivate returns a new initialized ZonePrivate instance
-func NewZonePrivate(ztype uint32, d []byte) (*ZonePrivate, error) {
+func NewZonePrivate(ztype uint32, d []byte) (zp *ZonePrivate, err error) {
        // get factory for given zone type
        impl, ok := zoneImpl[ztype]
        if !ok {
                return nil, ErrNoImplementation
        }
        // assemble private zone key
-       zp := &ZonePrivate{
+       zp = &ZonePrivate{
                ZoneKey{
                        ztype,
                        nil,
@@ -220,11 +226,13 @@ func NewZonePrivate(ztype uint32, d []byte) 
(*ZonePrivate, error) {
                nil,
        }
        zp.impl = impl.NewPrivate()
-       zp.impl.Init(d)
+       if err = zp.impl.Init(d); err != nil {
+               return
+       }
        zp.ZoneKey.KeyData = zp.impl.Public().Bytes()
        zp.ZoneKey.impl = impl.NewPublic()
-       zp.ZoneKey.impl.Init(zp.ZoneKey.KeyData)
-       return zp, nil
+       err = zp.ZoneKey.impl.Init(zp.ZoneKey.KeyData)
+       return
 }
 
 // KeySize returns the number of bytes of a key representation.
@@ -237,17 +245,18 @@ func (zp *ZonePrivate) KeySize() uint {
 }
 
 // Derive key (key blinding)
-func (zp *ZonePrivate) Derive(label, context string) (*ZonePrivate, *math.Int) 
{
+func (zp *ZonePrivate) Derive(label, context string) (dzp *ZonePrivate, h 
*math.Int, err error) {
        // get factory for given zone type
        impl := zoneImpl[zp.Type]
 
-       // caclulate derived key
-       h := deriveH(zp.impl.Bytes(), label, context)
+       // calculate derived key
+       h = deriveH(zp.impl.Bytes(), label, context)
        var derived ZonePrivateImpl
-       derived, h = zp.impl.Derive(h)
-
+       if derived, h, err = zp.impl.Derive(h); err != nil {
+               return
+       }
        // assemble derived pivate key
-       dzp := &ZonePrivate{
+       dzp = &ZonePrivate{
                ZoneKey{
                        zp.Type,
                        nil,
@@ -257,8 +266,8 @@ func (zp *ZonePrivate) Derive(label, context string) 
(*ZonePrivate, *math.Int) {
        }
        zp.ZoneKey.KeyData = derived.Public().Bytes()
        zp.ZoneKey.impl = impl.NewPublic()
-       zp.ZoneKey.impl.Init(zp.ZoneKey.KeyData)
-       return dzp, h
+       err = zp.ZoneKey.impl.Init(zp.ZoneKey.KeyData)
+       return
 }
 
 // ZoneSign data with a private key
@@ -284,20 +293,21 @@ type ZoneKey struct {
 }
 
 // NewZoneKey returns a new initialized ZoneKey instance
-func NewZoneKey(d []byte) (*ZoneKey, error) {
+func NewZoneKey(d []byte) (zk *ZoneKey, err error) {
        // read zone key from data
-       zk := new(ZoneKey)
-       if err := data.Unmarshal(zk, d); err != nil {
-               return nil, err
+       zk = new(ZoneKey)
+       if err = data.Unmarshal(zk, d); err != nil {
+               return
        }
        // initialize implementation
        impl, ok := zoneImpl[zk.Type]
        if !ok {
-               return nil, errors.New("unknown zone type")
+               err = ErrUnknownZoneType
+               return
        }
        zk.impl = impl.NewPublic()
-       zk.impl.Init(zk.KeyData)
-       return zk, nil
+       err = zk.impl.Init(zk.KeyData)
+       return
 }
 
 // KeySize returns the number of bytes of a key representation.
@@ -310,15 +320,18 @@ func (zk *ZoneKey) KeySize() uint {
 }
 
 // Derive key (key blinding)
-func (zk *ZoneKey) Derive(label, context string) (*ZoneKey, *math.Int) {
-       h := deriveH(zk.KeyData, label, context)
+func (zk *ZoneKey) Derive(label, context string) (dzk *ZoneKey, h *math.Int, 
err error) {
+       h = deriveH(zk.KeyData, label, context)
        var derived ZoneKeyImpl
-       derived, h = zk.impl.Derive(h)
-       return &ZoneKey{
+       if derived, h, err = zk.impl.Derive(h); err != nil {
+               return
+       }
+       dzk = &ZoneKey{
                Type:    zk.Type,
                KeyData: derived.Bytes(),
                impl:    derived,
-       }, h
+       }
+       return
 }
 
 // BlockKey returns the key for block en-/decryption
@@ -338,15 +351,22 @@ func (zk *ZoneKey) Decrypt(data []byte, label string, 
expire util.AbsoluteTime)
 
 // Verify a zone signature
 func (zk *ZoneKey) Verify(data []byte, zs *ZoneSignature) (ok bool, err error) 
{
-       zk.withImpl()
+       if err = zk.withImpl(); err != nil {
+               return
+       }
        return zk.impl.Verify(data, zs)
 }
 
 // ID returns the human-readable zone identifier.
 func (zk *ZoneKey) ID() string {
        buf := new(bytes.Buffer)
-       binary.Write(buf, binary.BigEndian, zk.Type)
-       buf.Write(zk.KeyData)
+       err := binary.Write(buf, binary.BigEndian, zk.Type)
+       if err == nil {
+               _, err = buf.Write(zk.KeyData)
+       }
+       if err != nil {
+               logger.Printf(logger.ERROR, "[ZoneKey.ID] failed: %s", 
err.Error())
+       }
        return util.EncodeBinaryToString(buf.Bytes())
 }
 
@@ -362,12 +382,13 @@ func (zk *ZoneKey) Equal(k *ZoneKey) bool {
 }
 
 // withImpl ensure that an implementation reference is available
-func (zk *ZoneKey) withImpl() {
+func (zk *ZoneKey) withImpl() (err error) {
        if zk.impl == nil {
                factory := zoneImpl[zk.Type]
                zk.impl = factory.NewPublic()
-               zk.impl.Init(zk.KeyData)
+               err = zk.impl.Init(zk.KeyData)
        }
+       return
 }
 
 //----------------------------------------------------------------------
@@ -382,27 +403,29 @@ type ZoneSignature struct {
 }
 
 // NewZoneSignature returns a new initialized ZoneSignature instance
-func NewZoneSignature(d []byte) (*ZoneSignature, error) {
+func NewZoneSignature(d []byte) (sig *ZoneSignature, err error) {
        // read signature
-       sig := new(ZoneSignature)
-       if err := data.Unmarshal(sig, d); err != nil {
-               return nil, err
+       sig = new(ZoneSignature)
+       if err = data.Unmarshal(sig, d); err != nil {
+               return
        }
        // initialize implementations
        impl, ok := zoneImpl[sig.Type]
        if !ok {
-               return nil, errors.New("unknown zone type")
+               err = ErrUnknownZoneType
+               return
        }
        // set signature implementation
        zs := impl.NewSignature()
-       zs.Init(sig.Signature)
+       err = zs.Init(sig.Signature)
        sig.impl = zs
        // set public key implementation
        zk := impl.NewPublic()
-       zk.Init(sig.KeyData)
+       if err = zk.Init(sig.KeyData); err != nil {
+               return
+       }
        sig.ZoneKey.impl = zk
-
-       return sig, nil
+       return
 }
 
 // SigSize returns the number of bytes of a signature that can be
@@ -435,6 +458,8 @@ func deriveH(key []byte, label, context string) *math.Int {
        data := append([]byte(label), []byte(context)...)
        rdr := hkdf.Expand(sha256.New, prk, data)
        b := make([]byte, 64)
-       rdr.Read(b)
+       if _, err := rdr.Read(b); err != nil {
+               logger.Printf(logger.ERROR, "[deriveH] failed: %s", err.Error())
+       }
        return math.NewIntFromBytes(b)
 }
diff --git a/src/gnunet/crypto/gns_edkey.go b/src/gnunet/crypto/gns_edkey.go
index 3153628..68a6444 100644
--- a/src/gnunet/crypto/gns_edkey.go
+++ b/src/gnunet/crypto/gns_edkey.go
@@ -26,6 +26,7 @@ import (
 
        "github.com/bfix/gospel/crypto/ed25519"
        "github.com/bfix/gospel/data"
+       "github.com/bfix/gospel/logger"
        "github.com/bfix/gospel/math"
        "golang.org/x/crypto/hkdf"
        "golang.org/x/crypto/nacl/secretbox"
@@ -76,15 +77,15 @@ func (pk *EDKEYPublicImpl) Bytes() []byte {
 
 // Derive a public key from this key based on a big integer
 // (key blinding). Returns the derived key and the blinding value.
-func (pk *EDKEYPublicImpl) Derive(h *math.Int) (ZoneKeyImpl, *math.Int) {
+func (pk *EDKEYPublicImpl) Derive(h *math.Int) (dPk ZoneKeyImpl, hOut 
*math.Int, err error) {
        // limit to allowed value range
-       h = h.Mod(ed25519.GetCurve().N)
-       derived := pk.pub.Mult(h)
-       dPk := &EDKEYPublicImpl{
+       hOut = h.Mod(ed25519.GetCurve().N)
+       derived := pk.pub.Mult(hOut)
+       dPk = &EDKEYPublicImpl{
                pk.ztype,
                derived,
        }
-       return dPk, h
+       return
 }
 
 // Encrypt binary data (of any size). Output can be larger than input
@@ -137,8 +138,9 @@ func (pk *EDKEYPublicImpl) BlockKey(label string, expires 
util.AbsoluteTime) (sk
        kd := pk.Bytes()
        prk := hkdf.Extract(sha512.New, kd, []byte("gns-xsalsa-ctx-key"))
        rdr := hkdf.Expand(sha256.New, prk, []byte(label))
-       rdr.Read(skey[:32])
-
+       if _, err := rdr.Read(skey[:32]); err != nil {
+               logger.Printf(logger.ERROR, "[EDKEYPublicImpl.BlockKey] failed: 
%s", err.Error())
+       }
        // assemble initialization vector
        iv := &struct {
                Nonce      []byte            `size:"16"` // Nonce
@@ -149,7 +151,9 @@ func (pk *EDKEYPublicImpl) BlockKey(label string, expires 
util.AbsoluteTime) (sk
        }
        prk = hkdf.Extract(sha512.New, kd, []byte("gns-xsalsa-ctx-iv"))
        rdr = hkdf.Expand(sha256.New, prk, []byte(label))
-       rdr.Read(iv.Nonce)
+       if _, err := rdr.Read(iv.Nonce); err != nil {
+               logger.Printf(logger.ERROR, "[EDKEYPublicImpl.BlockKey] failed: 
%s", err.Error())
+       }
        buf, _ := data.Marshal(iv)
        copy(skey[32:], buf)
        return
@@ -188,30 +192,32 @@ func (pk *EDKEYPrivateImpl) Public() ZoneKeyImpl {
 
 // Derive a public key from this key based on a big integer
 // (key blinding). Returns the derived key and the blinding value.
-func (pk *EDKEYPrivateImpl) Derive(h *math.Int) (ZonePrivateImpl, *math.Int) {
+func (pk *EDKEYPrivateImpl) Derive(h *math.Int) (dPk ZonePrivateImpl, hOut 
*math.Int, err error) {
        // limit to allowed value range
-       h = h.Mod(ed25519.GetCurve().N)
-       derived := pk.prv.Mult(h)
-       dPk := &EDKEYPrivateImpl{
+       hOut = h.Mod(ed25519.GetCurve().N)
+       derived := pk.prv.Mult(hOut)
+       dPk = &EDKEYPrivateImpl{
                EDKEYPublicImpl{
                        pk.ztype,
                        derived.Public(),
                },
                derived,
        }
-       return dPk, h
+       return
 }
 
 // Sign binary data
-func (pk *EDKEYPrivateImpl) Sign(data []byte) (*ZoneSignature, error) {
-       s, err := pk.prv.EdSign(data)
-       if err != nil {
-               return nil, err
+func (pk *EDKEYPrivateImpl) Sign(data []byte) (sig *ZoneSignature, err error) {
+       var s *ed25519.EdSignature
+       if s, err = pk.prv.EdSign(data); err != nil {
+               return
        }
        sd := s.Bytes()
        sigImpl := new(EDKEYSigImpl)
-       sigImpl.Init(sd)
-       sig := &ZoneSignature{
+       if err = sigImpl.Init(sd); err != nil {
+               return
+       }
+       sig = &ZoneSignature{
                ZoneKey{
                        Type:    pk.ztype,
                        KeyData: pk.pub.Bytes(),
@@ -219,7 +225,7 @@ func (pk *EDKEYPrivateImpl) Sign(data []byte) 
(*ZoneSignature, error) {
                sd,
                sigImpl,
        }
-       return sig, nil
+       return
 }
 
 //----------------------------------------------------------------------
diff --git a/src/gnunet/crypto/gns_pkey.go b/src/gnunet/crypto/gns_pkey.go
index fa319a4..e9fdfb5 100644
--- a/src/gnunet/crypto/gns_pkey.go
+++ b/src/gnunet/crypto/gns_pkey.go
@@ -27,6 +27,7 @@ import (
 
        "github.com/bfix/gospel/crypto/ed25519"
        "github.com/bfix/gospel/data"
+       "github.com/bfix/gospel/logger"
        "github.com/bfix/gospel/math"
        "golang.org/x/crypto/hkdf"
 )
@@ -76,15 +77,15 @@ func (pk *PKEYPublicImpl) Bytes() []byte {
 
 // Derive a public key from this key based on a big integer
 // (key blinding). Returns the derived key and the blinding value.
-func (pk *PKEYPublicImpl) Derive(h *math.Int) (ZoneKeyImpl, *math.Int) {
+func (pk *PKEYPublicImpl) Derive(h *math.Int) (dPk ZoneKeyImpl, hOut 
*math.Int, err error) {
        // limit to allowed value range
-       h = h.Mod(ed25519.GetCurve().N)
-       derived := pk.pub.Mult(h)
-       dPk := &PKEYPublicImpl{
+       hOut = h.Mod(ed25519.GetCurve().N)
+       derived := pk.pub.Mult(hOut)
+       dPk = &PKEYPublicImpl{
                pk.ztype,
                derived,
        }
-       return dPk, h
+       return
 }
 
 // Encrypt binary data (of any size). Output can be larger than input
@@ -114,7 +115,9 @@ func (pk *PKEYPublicImpl) BlockKey(label string, expires 
util.AbsoluteTime) (ske
        kd := pk.pub.Bytes()
        prk := hkdf.Extract(sha512.New, kd, []byte("gns-aes-ctx-key"))
        rdr := hkdf.Expand(sha256.New, prk, []byte(label))
-       rdr.Read(skey[:32])
+       if _, err := rdr.Read(skey[:32]); err != nil {
+               logger.Printf(logger.ERROR, "[PKEYPublicImpl.BlockKey] failed: 
%s", err.Error())
+       }
 
        // assemble initialization vector
        iv := &struct {
@@ -128,7 +131,9 @@ func (pk *PKEYPublicImpl) BlockKey(label string, expires 
util.AbsoluteTime) (ske
        }
        prk = hkdf.Extract(sha512.New, kd, []byte("gns-aes-ctx-iv"))
        rdr = hkdf.Expand(sha256.New, prk, []byte(label))
-       rdr.Read(iv.Nonce)
+       if _, err := rdr.Read(iv.Nonce); err != nil {
+               logger.Printf(logger.ERROR, "[PKEYPublicImpl.BlockKey] failed: 
%s", err.Error())
+       }
        buf, _ := data.Marshal(iv)
        copy(skey[32:], buf)
        return
@@ -184,30 +189,32 @@ func (pk *PKEYPrivateImpl) Public() ZoneKeyImpl {
 
 // Derive a public key from this key based on a big integer
 // (key blinding). Returns the derived key and the blinding value.
-func (pk *PKEYPrivateImpl) Derive(h *math.Int) (ZonePrivateImpl, *math.Int) {
+func (pk *PKEYPrivateImpl) Derive(h *math.Int) (dPk ZonePrivateImpl, hOut 
*math.Int, err error) {
        // limit to allowed value range
-       h = h.Mod(ed25519.GetCurve().N)
-       derived := pk.prv.Mult(h)
-       dPk := &PKEYPrivateImpl{
+       hOut = h.Mod(ed25519.GetCurve().N)
+       derived := pk.prv.Mult(hOut)
+       dPk = &PKEYPrivateImpl{
                PKEYPublicImpl{
                        pk.ztype,
                        derived.Public(),
                },
                derived,
        }
-       return dPk, h
+       return
 }
 
 // Verify a signature for binary data
-func (pk *PKEYPrivateImpl) Sign(data []byte) (*ZoneSignature, error) {
-       s, err := pk.prv.EcSign(data)
-       if err != nil {
-               return nil, err
+func (pk *PKEYPrivateImpl) Sign(data []byte) (sig *ZoneSignature, err error) {
+       var s *ed25519.EcSignature
+       if s, err = pk.prv.EcSign(data); err != nil {
+               return
        }
        sd := s.Bytes()
        sigImpl := new(PKEYSigImpl)
-       sigImpl.Init(sd)
-       sig := &ZoneSignature{
+       if err = sigImpl.Init(sd); err != nil {
+               return
+       }
+       sig = &ZoneSignature{
                ZoneKey{
                        Type:    pk.ztype,
                        KeyData: pk.pub.Bytes(),
@@ -215,7 +222,7 @@ func (pk *PKEYPrivateImpl) Sign(data []byte) 
(*ZoneSignature, error) {
                sd,
                sigImpl,
        }
-       return sig, nil
+       return
 }
 
 //----------------------------------------------------------------------
diff --git a/src/gnunet/crypto/gns_test.go b/src/gnunet/crypto/gns_test.go
index 4ef64d4..12ab603 100644
--- a/src/gnunet/crypto/gns_test.go
+++ b/src/gnunet/crypto/gns_test.go
@@ -55,7 +55,9 @@ func TestDeriveBlockKey(t *testing.T) {
 
        // create and initialize new public zone key (PKEY)
        zkey := new(PKEYPublicImpl)
-       zkey.Init(PUB)
+       if err := zkey.Init(PUB); err != nil {
+               t.Fatal(err)
+       }
 
        // derive and check a key for symmetric cipher
        skey := zkey.BlockKey(LABEL, EXPIRE)
@@ -267,7 +269,10 @@ func TestDeriveH(t *testing.T) {
        }
 
        // test key derivation
-       dpub, h := pub.Derive(LABEL, CONTEXT)
+       dpub, h, err := pub.Derive(LABEL, CONTEXT)
+       if err != nil {
+               t.Fatal(err)
+       }
        if !bytes.Equal(h.Bytes(), H) {
                if testing.Verbose() {
                        t.Logf("H(computed) = %s\n", 
hex.EncodeToString(h.Bytes()))
@@ -297,7 +302,6 @@ func TestDeriveH(t *testing.T) {
 }
 
 func TestHKDF_gnunet(t *testing.T) {
-
        var (
                // SALT as defined in GNUnet
                salt = []byte("key-derivation")
@@ -332,7 +336,9 @@ func TestHKDF_gnunet(t *testing.T) {
 
        rdr := hkdf.Expand(sha256.New, prk, info)
        okm := make([]byte, len(OKM))
-       rdr.Read(okm)
+       if _, err := rdr.Read(okm); err != nil {
+               t.Fatal(err)
+       }
        if testing.Verbose() {
                t.Log("OKM(computed) = " + hex.EncodeToString(okm))
        }
@@ -387,7 +393,9 @@ func TestHDKF(t *testing.T) {
 
        rdr := hkdf.Expand(sha512.New, prk, info)
        okm := make([]byte, len(OKM))
-       rdr.Read(okm)
+       if _, err := rdr.Read(okm); err != nil {
+               t.Fatal(err)
+       }
        if testing.Verbose() {
                t.Log("OKM(computed) = " + hex.EncodeToString(okm))
        }
diff --git a/src/gnunet/crypto/hash.go b/src/gnunet/crypto/hash.go
index ed6edc7..437dcb2 100644
--- a/src/gnunet/crypto/hash.go
+++ b/src/gnunet/crypto/hash.go
@@ -21,6 +21,7 @@ package crypto
 import (
        "bytes"
        "crypto/sha512"
+       "encoding/hex"
 
        "gnunet/util"
 )
@@ -35,7 +36,17 @@ func (hc *HashCode) Equals(n *HashCode) bool {
        return bytes.Equal(hc.Bits, n.Bits)
 }
 
-// NewHashCode creates a new (initalized) hash value
+// Clone the hash code
+func (hc *HashCode) Clone() *HashCode {
+       return NewHashCode(hc.Bits)
+}
+
+// String returns a hex-representation of the hash code
+func (hc *HashCode) String() string {
+       return hex.EncodeToString(hc.Bits)
+}
+
+// NewHashCode creates a new (initialized) hash value
 func NewHashCode(buf []byte) *HashCode {
        hc := &HashCode{
                Bits: make([]byte, 64),
diff --git a/src/gnunet/crypto/key_exchange_test.go 
b/src/gnunet/crypto/key_exchange_test.go
index 1e4f0dc..dbd1792 100644
--- a/src/gnunet/crypto/key_exchange_test.go
+++ b/src/gnunet/crypto/key_exchange_test.go
@@ -29,14 +29,14 @@ import (
 )
 
 var (
-       d_1 = []byte{
+       d1 = []byte{
                0x7F, 0xDE, 0x7A, 0xAA, 0xEA, 0x0D, 0xA1, 0x7A,
                0x7B, 0xCB, 0x4F, 0x57, 0x49, 0xCC, 0xA9, 0xBE,
                0xA7, 0xFB, 0x2B, 0x85, 0x77, 0xAD, 0xC9, 0x55,
                0xDA, 0xB2, 0x68, 0xB2, 0xB4, 0xCC, 0x24, 0x78,
        }
 
-       d_2 = []byte{
+       d2 = []byte{
                0x20, 0x3f, 0x2f, 0x8c, 0x54, 0xf4, 0x1a, 0xd3,
                0x01, 0x9a, 0x56, 0x92, 0x19, 0xda, 0xee, 0x4f,
                0xd2, 0x53, 0x55, 0xa6, 0x3c, 0xfc, 0x57, 0x40,
@@ -54,11 +54,11 @@ var (
                0x05, 0xbd, 0x1b, 0x85, 0xd5, 0xfd, 0x63, 0x60,
        }
 
-       prv_1, prv_2 *ed25519.PrivateKey
-       pub_1, pub_2 *ed25519.PublicKey
-       ss_1, ss_2   []byte
+       prv1, prv2 *ed25519.PrivateKey
+       pub1, pub2 *ed25519.PublicKey
+       ss1, ss2   []byte
 
-       ED25519_N = ed25519.GetCurve().N
+       ed25519N = ed25519.GetCurve().N
 )
 
 func calcSharedSecret() bool {
@@ -67,29 +67,29 @@ func calcSharedSecret() bool {
                return x[:]
        }
        // compute shared secret
-       ss_1 = calc(prv_1, pub_2)
-       ss_2 = calc(prv_2, pub_1)
-       return bytes.Equal(ss_1, ss_2)
+       ss1 = calc(prv1, pub2)
+       ss2 = calc(prv2, pub1)
+       return bytes.Equal(ss1, ss2)
 }
 
 func TestDHE(t *testing.T) {
        // generate two key pairs
-       prv_1 = ed25519.NewPrivateKeyFromD(math.NewIntFromBytes(d_1))
-       pub_1 = prv_1.Public()
-       prv_2 = ed25519.NewPrivateKeyFromD(math.NewIntFromBytes(d_2))
-       pub_2 = prv_2.Public()
+       prv1 = ed25519.NewPrivateKeyFromD(math.NewIntFromBytes(d1))
+       pub1 = prv1.Public()
+       prv2 = ed25519.NewPrivateKeyFromD(math.NewIntFromBytes(d2))
+       pub2 = prv2.Public()
 
        if !calcSharedSecret() {
                t.Fatal("Shared secret mismatch")
        }
        if testing.Verbose() {
-               t.Logf("SS_1 = %s\n", hex.EncodeToString(ss_1))
-               t.Logf("SS_2 = %s\n", hex.EncodeToString(ss_2))
+               t.Logf("SS_1 = %s\n", hex.EncodeToString(ss1))
+               t.Logf("SS_2 = %s\n", hex.EncodeToString(ss2))
        }
 
-       if !bytes.Equal(ss_1[:], ss) {
+       if !bytes.Equal(ss1[:], ss) {
                t.Logf("SS(expected) = %s\n", hex.EncodeToString(ss))
-               t.Logf("SS(computed) = %s\n", hex.EncodeToString(ss_1[:]))
+               t.Logf("SS(computed) = %s\n", hex.EncodeToString(ss1[:]))
                t.Fatal("Wrong shared secret:")
        }
 }
@@ -98,19 +98,19 @@ func TestDHERandom(t *testing.T) {
        failed := 0
        once := false
        for i := 0; i < 100; i++ {
-               prv_1 = ed25519.NewPrivateKeyFromD(math.NewIntRnd(ED25519_N))
-               pub_1 = prv_1.Public()
-               prv_2 = ed25519.NewPrivateKeyFromD(math.NewIntRnd(ED25519_N))
-               pub_2 = prv_2.Public()
+               prv1 = ed25519.NewPrivateKeyFromD(math.NewIntRnd(ed25519N))
+               pub1 = prv1.Public()
+               prv2 = ed25519.NewPrivateKeyFromD(math.NewIntRnd(ed25519N))
+               pub2 = prv2.Public()
 
                if !calcSharedSecret() {
                        if !once {
                                once = true
-                               t.Logf("d1=%s\n", 
hex.EncodeToString(prv_1.D.Bytes()))
-                               t.Logf("d2=%s\n", 
hex.EncodeToString(prv_2.D.Bytes()))
-                               t.Logf("ss1=%s\n", hex.EncodeToString(ss_1))
-                               t.Logf("ss2=%s\n", hex.EncodeToString(ss_2))
-                               dd := prv_1.D.Mul(prv_2.D).Mod(ED25519_N)
+                               t.Logf("d1=%s\n", 
hex.EncodeToString(prv1.D.Bytes()))
+                               t.Logf("d2=%s\n", 
hex.EncodeToString(prv2.D.Bytes()))
+                               t.Logf("ss1=%s\n", hex.EncodeToString(ss1))
+                               t.Logf("ss2=%s\n", hex.EncodeToString(ss2))
+                               dd := prv1.D.Mul(prv2.D).Mod(ed25519N)
                                pk := 
sha512.Sum512(ed25519.NewPrivateKeyFromD(dd).Public().Q.X().Bytes())
                                t.Logf("ss0=%s\n", hex.EncodeToString(pk[:]))
                        }
diff --git a/src/gnunet/crypto/keys_test.go b/src/gnunet/crypto/keys_test.go
index d8ffe96..ca5c7b7 100644
--- a/src/gnunet/crypto/keys_test.go
+++ b/src/gnunet/crypto/keys_test.go
@@ -58,7 +58,7 @@ var (
 func TestPrvKey(t *testing.T) {
        if testing.Verbose() {
                t.Logf("PRIVATE (seed=%s)\n", hex.EncodeToString(seed))
-               t.Logf("     d = %s\n", hex.EncodeToString(prv_1.D.Bytes()))
+               t.Logf("     d = %s\n", hex.EncodeToString(prv1.D.Bytes()))
                t.Logf("    ID = '%s'\n", util.EncodeBinaryToString(seed))
        }
 
diff --git a/src/gnunet/enums/blocktype_string.go 
b/src/gnunet/enums/blocktype_string.go
index 639501f..cc14d79 100644
--- a/src/gnunet/enums/blocktype_string.go
+++ b/src/gnunet/enums/blocktype_string.go
@@ -40,7 +40,7 @@ var (
 
 func (i BlockType) String() string {
        switch {
-       case 0 <= i && i <= 2:
+       case i <= 2:
                return 
_BlockType_name_0[_BlockType_index_0[i]:_BlockType_index_0[i+1]]
        case 6 <= i && i <= 13:
                i -= 6
diff --git a/src/gnunet/enums/dht.go b/src/gnunet/enums/dht.go
index 36c3d8a..040e72f 100644
--- a/src/gnunet/enums/dht.go
+++ b/src/gnunet/enums/dht.go
@@ -16,18 +16,16 @@
 //
 // SPDX-License-Identifier: AGPL3.0-or-later
 
+//nolint:stylecheck // allow non-camel-case for constants
 package enums
 
 // DHT flags and settings
 const (
-       DHT_RO_NONE                   = 0  // Default.  Do nothing special.
-       DHT_RO_DEMULTIPLEX_EVERYWHERE = 1  // Each peer along the way should 
look at 'enc'
-       DHT_RO_RECORD_ROUTE           = 2  // keep track of the route that the 
message took in the P2P network.
-       DHT_RO_FIND_PEER              = 3  // This is a 'FIND-PEER' request, so 
approximate results are fine.
-       DHT_RO_BART                   = 4  // Possible message option for query 
key randomization.
-       DHT_RO_LAST_HOP               = 16 // Flag given to monitors if this 
was the last hop for a GET/PUT.
-
-       DHT_GNS_REPLICATION_LEVEL = 10
+       DHT_RO_NONE                   = 0 // Default.  Do nothing special.
+       DHT_RO_DEMULTIPLEX_EVERYWHERE = 1 // Each peer along the way should 
look at 'enc'
+       DHT_RO_RECORD_ROUTE           = 2 // keep track of the route that the 
message took in the P2P network.
+       DHT_RO_FIND_APPROXIMATE       = 4 // Approximate results are fine.
+       DHT_RO_TRUNCATED              = 8 // Flag if path is truncated
 )
 
 //go:generate go run generate.go gnunet-dht.rec gnunet-dht.tpl 
dht_block_type.go
diff --git a/src/gnunet/enums/dht_block_type.go 
b/src/gnunet/enums/dht_block_type.go
index e435419..beb52dc 100644
--- a/src/gnunet/enums/dht_block_type.go
+++ b/src/gnunet/enums/dht_block_type.go
@@ -1,8 +1,9 @@
 // Code generated by enum generator; DO NOT EDIT.
 
+//nolint:stylecheck // allow non-camel-case for constants
 package enums
 
-type BlockType int
+type BlockType uint16
 
 // DHT block types
 const (
diff --git a/src/gnunet/enums/gns.go b/src/gnunet/enums/gns.go
index 5ab28ad..f6e58a2 100644
--- a/src/gnunet/enums/gns.go
+++ b/src/gnunet/enums/gns.go
@@ -16,6 +16,7 @@
 //
 // SPDX-License-Identifier: AGPL3.0-or-later
 
+//nolint:stylecheck // allow non-camel-case for constants
 package enums
 
 const (
@@ -31,6 +32,8 @@ const (
        GNS_LO_LOCAL_MASTER = 2 // For the rightmost label, only look in the 
cache.
 
        GNS_MAX_BLOCK_SIZE = (63 * 1024) // Maximum size of a value that can be 
stored in a GNS block.
+
+       GNS_REPLICATION_LEVEL = 10
 )
 
 //go:generate go run generate.go gnunet-gns.rec gnunet-gns.tpl gns_type.go
diff --git a/src/gnunet/enums/gns_type.go b/src/gnunet/enums/gns_type.go
index 2b55817..5a13d67 100644
--- a/src/gnunet/enums/gns_type.go
+++ b/src/gnunet/enums/gns_type.go
@@ -1,5 +1,6 @@
 // Code generated by enum generator; DO NOT EDIT.
 
+//nolint:stylecheck // allow non-camel-case for constants
 package enums
 
 type GNSType int
diff --git a/src/gnunet/enums/gnunet-dht.tpl b/src/gnunet/enums/gnunet-dht.tpl
index 0010897..ed00e57 100644
--- a/src/gnunet/enums/gnunet-dht.tpl
+++ b/src/gnunet/enums/gnunet-dht.tpl
@@ -1,8 +1,9 @@
 // Code generated by enum generator; DO NOT EDIT.
 
+//nolint:stylecheck // allow non-camel-case for constants
 package enums
 
-type BlockType int
+type BlockType uint16
 
 // DHT block types
 const (
diff --git a/src/gnunet/enums/gnunet-gns.tpl b/src/gnunet/enums/gnunet-gns.tpl
index 075fe73..3249569 100644
--- a/src/gnunet/enums/gnunet-gns.tpl
+++ b/src/gnunet/enums/gnunet-gns.tpl
@@ -1,5 +1,6 @@
 // Code generated by enum generator; DO NOT EDIT.
 
+//nolint:stylecheck // allow non-camel-case for constants
 package enums
 
 type GNSType int
diff --git a/src/gnunet/enums/gnunet-signature.tpl 
b/src/gnunet/enums/gnunet-signature.tpl
index 009c086..5af6c28 100644
--- a/src/gnunet/enums/gnunet-signature.tpl
+++ b/src/gnunet/enums/gnunet-signature.tpl
@@ -1,5 +1,6 @@
 // Code generated by enum generator; DO NOT EDIT.
 
+//nolint:stylecheck // allow non-camel-case for constants
 package enums
 
 type SigPurpose int
diff --git a/src/gnunet/enums/signature_purpose.go 
b/src/gnunet/enums/signature_purpose.go
index ad4bf0a..0c1901e 100644
--- a/src/gnunet/enums/signature_purpose.go
+++ b/src/gnunet/enums/signature_purpose.go
@@ -1,5 +1,6 @@
 // Code generated by enum generator; DO NOT EDIT.
 
+//nolint:stylecheck // allow non-camel-case for constants
 package enums
 
 type SigPurpose int
diff --git a/src/gnunet/go.mod b/src/gnunet/go.mod
index bb2e58f..5109da4 100644
--- a/src/gnunet/go.mod
+++ b/src/gnunet/go.mod
@@ -3,10 +3,11 @@ module gnunet
 go 1.18
 
 require (
-       github.com/bfix/gospel v1.2.14
+       github.com/bfix/gospel v1.2.15
        github.com/go-redis/redis/v8 v8.11.5
        github.com/go-sql-driver/mysql v1.6.0
        github.com/gorilla/mux v1.8.0
+       github.com/gorilla/rpc v1.2.0
        github.com/mattn/go-sqlite3 v1.14.13
        github.com/miekg/dns v1.1.49
        golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898
diff --git a/src/gnunet/go.sum b/src/gnunet/go.sum
index 2451a41..5a08cee 100644
--- a/src/gnunet/go.sum
+++ b/src/gnunet/go.sum
@@ -1,5 +1,5 @@
-github.com/bfix/gospel v1.2.14 h1:lIdagJvkebG+uYbVdfK6XbT1udnq/ezd/Gi54EaMtV0=
-github.com/bfix/gospel v1.2.14/go.mod 
h1:cdu63bA9ZdfeDoqZ+vnWOcbY9Puwdzmf5DMxMGMznRI=
+github.com/bfix/gospel v1.2.15 h1:f0t8dvihSXWvhnXDI2q7FCtG7LHg5qImjEWdzIN/luY=
+github.com/bfix/gospel v1.2.15/go.mod 
h1:cdu63bA9ZdfeDoqZ+vnWOcbY9Puwdzmf5DMxMGMznRI=
 github.com/cespare/xxhash/v2 v2.1.2 
h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
 github.com/cespare/xxhash/v2 v2.1.2/go.mod 
h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f 
h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
@@ -11,6 +11,8 @@ github.com/go-sql-driver/mysql v1.6.0 
h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC
 github.com/go-sql-driver/mysql v1.6.0/go.mod 
h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 github.com/gorilla/mux v1.8.0/go.mod 
h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk=
+github.com/gorilla/rpc v1.2.0/go.mod 
h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
 github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo=
 github.com/huin/goupnp v1.0.0/go.mod 
h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc=
 github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod 
h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
diff --git a/src/gnunet/message/factory.go b/src/gnunet/message/factory.go
index 25506dc..23da078 100644
--- a/src/gnunet/message/factory.go
+++ b/src/gnunet/message/factory.go
@@ -66,8 +66,18 @@ func NewEmptyMessage(msgType uint16) (Message, error) {
                return NewDHTClientResultMsg(nil), nil
        case DHT_CLIENT_GET_RESULTS_KNOWN:
                return NewDHTClientGetResultsKnownMsg(nil), nil
+
+       //------------------------------------------------------------------
+       // DHT-P2P
+       //------------------------------------------------------------------
        case DHT_P2P_HELLO:
-               return NewHelloDHTMsg(), nil
+               return NewDHTP2PHelloMsg(), nil
+       case DHT_P2P_GET:
+               return NewDHTP2PGetMsg(), nil
+       case DHT_P2P_PUT:
+               return NewDHTP2PPutMsg(), nil
+       case DHT_P2P_RESULT:
+               return NewDHTP2PResultMsg(), nil
 
        //------------------------------------------------------------------
        // GNS
diff --git a/src/gnunet/message/msg_core.go b/src/gnunet/message/msg_core.go
index 245c61d..208b438 100644
--- a/src/gnunet/message/msg_core.go
+++ b/src/gnunet/message/msg_core.go
@@ -36,17 +36,17 @@ type EphKeyBlock struct {
        Purpose      *crypto.SignaturePurpose // signature purpose: SIG_ECC_KEY
        CreateTime   util.AbsoluteTime        // Time of key creation
        ExpireTime   util.RelativeTime        // Time to live for key
-       EphemeralKey []byte                   `size:"32"` // Ephemeral EdDSA 
public key
+       EphemeralKey *util.PeerPublicKey      // Ephemeral EdDSA public key
        PeerID       *util.PeerID             // Peer identity (EdDSA public 
key)
 }
 
 // EphemeralKeyMsg announces a new transient key for a peer. The key is signed
 // by the issuing peer.
 type EphemeralKeyMsg struct {
-       MsgSize      uint16 `order:"big"` // total size of message
-       MsgType      uint16 `order:"big"` // CORE_EPHEMERAL_KEY (88)
-       SenderStatus uint32 `order:"big"` // enum PeerStateMachine
-       Signature    []byte `size:"64"`   // EdDSA signature
+       MsgSize      uint16              `order:"big"` // total size of message
+       MsgType      uint16              `order:"big"` // CORE_EPHEMERAL_KEY 
(88)
+       SenderStatus uint32              `order:"big"` // enum PeerStateMachine
+       Signature    *util.PeerSignature ``            // EdDSA signature
        SignedBlock  *EphKeyBlock
 }
 
@@ -56,7 +56,7 @@ func NewEphemeralKeyMsg() *EphemeralKeyMsg {
                MsgSize:      160,
                MsgType:      CORE_EPHEMERAL_KEY,
                SenderStatus: 1,
-               Signature:    make([]byte, 64),
+               Signature:    util.NewPeerSignature(nil),
                SignedBlock: &EphKeyBlock{
                        Purpose: &crypto.SignaturePurpose{
                                Size:    88,
@@ -64,7 +64,7 @@ func NewEphemeralKeyMsg() *EphemeralKeyMsg {
                        },
                        CreateTime:   util.AbsoluteTimeNow(),
                        ExpireTime:   util.NewRelativeTime(12 * time.Hour),
-                       EphemeralKey: make([]byte, 32),
+                       EphemeralKey: util.NewPeerPublicKey(nil),
                        PeerID:       util.NewPeerID(nil),
                },
        }
@@ -73,8 +73,8 @@ func NewEphemeralKeyMsg() *EphemeralKeyMsg {
 // String returns a human-readable representation of the message.
 func (m *EphemeralKeyMsg) String() string {
        return 
fmt.Sprintf("EphKeyMsg{peer=%s,ephkey=%s,create=%s,expire=%s,status=%d}",
-               util.EncodeBinaryToString(m.SignedBlock.PeerID.Key),
-               util.EncodeBinaryToString(m.SignedBlock.EphemeralKey),
+               util.EncodeBinaryToString(m.SignedBlock.PeerID.Data),
+               util.EncodeBinaryToString(m.SignedBlock.EphemeralKey.Data),
                m.SignedBlock.CreateTime, m.SignedBlock.ExpireTime,
                m.SenderStatus)
 }
@@ -85,8 +85,8 @@ func (m *EphemeralKeyMsg) Header() *Header {
 }
 
 // Public extracts the public key of an announcing peer.
-func (m *EphemeralKeyMsg) Public() *ed25519.PublicKey {
-       return m.SignedBlock.PeerID.PublicKey()
+func (m *EphemeralKeyMsg) Public() *util.PeerPublicKey {
+       return util.NewPeerPublicKey(m.SignedBlock.PeerID.Data)
 }
 
 // Verify the integrity of the message data using the public key of the
@@ -96,7 +96,7 @@ func (m *EphemeralKeyMsg) Verify(pub *ed25519.PublicKey) 
(bool, error) {
        if err != nil {
                return false, err
        }
-       sig, err := ed25519.NewEdSignatureFromBytes(m.Signature)
+       sig, err := ed25519.NewEdSignatureFromBytes(m.Signature.Data)
        if err != nil {
                return false, err
        }
@@ -107,10 +107,10 @@ func (m *EphemeralKeyMsg) Verify(pub *ed25519.PublicKey) 
(bool, error) {
 // key and the corresponding GNUnet message to announce the new key.
 func NewEphemeralKey(peerID []byte, ltPrv *ed25519.PrivateKey) 
(*ed25519.PrivateKey, *EphemeralKeyMsg, error) {
        msg := NewEphemeralKeyMsg()
-       copy(msg.SignedBlock.PeerID.Key, peerID)
+       copy(msg.SignedBlock.PeerID.Data, peerID)
        seed := util.NewRndArray(32)
        prv := ed25519.NewPrivateKeyFromSeed(seed)
-       copy(msg.SignedBlock.EphemeralKey, prv.Public().Bytes())
+       copy(msg.SignedBlock.EphemeralKey.Data, prv.Public().Bytes())
 
        data, err := data.Marshal(msg.SignedBlock)
        if err != nil {
@@ -120,7 +120,7 @@ func NewEphemeralKey(peerID []byte, ltPrv 
*ed25519.PrivateKey) (*ed25519.Private
        if err != nil {
                return nil, nil, err
        }
-       copy(msg.Signature, sig.Bytes())
+       copy(msg.Signature.Data, sig.Bytes())
 
        return prv, msg, nil
 }
diff --git a/src/gnunet/message/msg_dht_p2p.go 
b/src/gnunet/message/msg_dht_p2p.go
new file mode 100644
index 0000000..55bb71d
--- /dev/null
+++ b/src/gnunet/message/msg_dht_p2p.go
@@ -0,0 +1,473 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix  >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package message
+
+import (
+       "bytes"
+       "crypto/sha512"
+       "encoding/binary"
+       "errors"
+       "fmt"
+       "gnunet/crypto"
+       "gnunet/enums"
+       "gnunet/service/dht/blocks"
+       "gnunet/util"
+       "time"
+
+       "github.com/bfix/gospel/crypto/ed25519"
+       "github.com/bfix/gospel/data"
+       "github.com/bfix/gospel/logger"
+)
+
+//======================================================================
+// DHT-P2P is a next-generation implementation of the R5N DHT.
+//======================================================================
+
+// shared path element data across types
+type pathElementData struct {
+       Expiration      util.AbsoluteTime // expiration date
+       BlockHash       *crypto.HashCode  // block hash
+       PeerPredecessor *util.PeerID      // predecessor peer
+       PeerSuccessor   *util.PeerID      // successor peer
+}
+
+// helper type for signature creation/verification
+type pathElementSignedData struct {
+       Size    uint16           `order:"big"` // size of signed data
+       Purpose uint16           `order:"big"` // signature purpose 
(SIG_DHT_HOP)
+       Elem    *pathElementData ``            // path element data
+}
+
+// PathElement is the full-fledged data assembly for a path element in
+// PUT/GET pathes. It is assembled programatically (on generation[1] and
+// verification[2]) and not transferred in messages directly.
+//
+// [1] spe = &PathElement{...}
+//     core.Sign(spe)
+//     msg.putpath[i] = spe.Wire()
+//
+// [2] pe = &PathElement{...,Signature: wire.sig}
+//     if !pe.Verify(peerId) { ... }
+//
+type PathElement struct {
+       pathElementData
+       Signature *util.PeerSignature // signature
+}
+
+// NewPathElement creates a new path element from data
+func NewPathElement(key *crypto.HashCode, pred, succ *util.PeerID) 
*PathElement {
+       return &PathElement{
+               pathElementData: pathElementData{
+                       Expiration:      util.AbsoluteTimeNow().Add(12 * 
time.Hour),
+                       BlockHash:       key,
+                       PeerPredecessor: pred,
+                       PeerSuccessor:   succ,
+               },
+               Signature: nil,
+       }
+}
+
+// PathElementWire is the data stored and retrieved from messages
+type PathElementWire struct {
+       Predecessor *util.PeerID        // peer id of predecessor
+       Signature   *util.PeerSignature // path signature
+}
+
+// Size returns the size of a path element in wire format
+func (pew *PathElementWire) Size() uint16 {
+       return 96
+}
+
+// SignedData gets the data to be signed by peer ('Signable' interface)
+func (pe *PathElement) SignedData() []byte {
+       sd := &pathElementSignedData{
+               Size:    80,
+               Purpose: uint16(enums.SIG_DHT_HOP),
+               Elem:    &(pe.pathElementData),
+       }
+       buf, err := data.Marshal(sd)
+       if err != nil {
+               logger.Println(logger.ERROR, "can't serialize path element for 
signature")
+               return nil
+       }
+       return buf
+}
+
+// SetSignature stores the generated signature.
+func (pe *PathElement) SetSignature(sig *util.PeerSignature) error {
+       pe.Signature = sig
+       return nil
+}
+
+// Wire returns the path element suitable for inclusion into messages
+func (pe *PathElement) Wire() *PathElementWire {
+       return &PathElementWire{
+               Predecessor: pe.PeerPredecessor,
+               Signature:   pe.Signature,
+       }
+}
+
+//----------------------------------------------------------------------
+// DHT-P2P-GET messages are used to request information from other
+// peers in the DHT.
+//----------------------------------------------------------------------
+
+// DHTP2PGetMsg wire layout
+type DHTP2PGetMsg struct {
+       MsgSize    uint16             `order:"big"`   // total size of message
+       MsgType    uint16             `order:"big"`   // DHT_P2P_GET (147)
+       BType      uint32             `order:"big"`   // content type of the 
payload
+       Flags      uint16             `order:"big"`   // processing flags
+       HopCount   uint16             `order:"big"`   // number of hops so far
+       ReplLevel  uint16             `order:"big"`   // Replication level
+       RfSize     uint16             `order:"big"`   // size of result filter
+       PeerFilter *blocks.PeerFilter ``              // peer filter to prevent 
loops
+       Query      *crypto.HashCode   ``              // query hash
+       ResFilter  []byte             `size:"RfSize"` // result filter
+       XQuery     []byte             `size:"*"`      // extended query
+}
+
+// NewDHTP2PGetMsg creates an empty DHT-P2P-Get message
+func NewDHTP2PGetMsg() *DHTP2PGetMsg {
+       return &DHTP2PGetMsg{
+               MsgSize:    208,                     // message size without 
ResFiter and XQuery
+               MsgType:    DHT_P2P_GET,             // DHT_P2P_GET (147)
+               BType:      0,                       // no block type defined
+               Flags:      0,                       // no flags defined
+               HopCount:   0,                       // no hops
+               ReplLevel:  0,                       // no replication level 
defined
+               RfSize:     0,                       // no result filter
+               PeerFilter: blocks.NewPeerFilter(),  // allocate bloom filter
+               Query:      crypto.NewHashCode(nil), // empty Query hash
+               ResFilter:  nil,                     // empty result filter
+               XQuery:     nil,                     // empty XQuery
+       }
+}
+
+// String returns a human-readable representation of the message.
+func (m *DHTP2PGetMsg) String() string {
+       return fmt.Sprintf("DHTP2PGetMsg{btype=%s,hops=%d,flags=%d}",
+               enums.BlockType(m.BType).String(), m.HopCount, m.Flags)
+}
+
+// Header returns the message header in a separate instance.
+func (m *DHTP2PGetMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
+}
+
+// Clone message
+func (m *DHTP2PGetMsg) Update(pf *blocks.PeerFilter, rf blocks.ResultFilter, 
hop uint16) *DHTP2PGetMsg {
+       buf := rf.Bytes()
+       ns := uint16(len(buf))
+       return &DHTP2PGetMsg{
+               MsgSize:    m.MsgSize - m.RfSize + ns,
+               MsgType:    DHT_P2P_GET,
+               BType:      m.BType,
+               Flags:      m.Flags,
+               HopCount:   hop,
+               ReplLevel:  m.ReplLevel,
+               RfSize:     ns,
+               PeerFilter: pf.Clone(),
+               Query:      m.Query,
+               ResFilter:  buf,
+               XQuery:     util.Clone(m.XQuery),
+       }
+}
+
+//----------------------------------------------------------------------
+// DHT-P2P-PUT messages are used by other peers in the DHT to
+// request block storage.
+//----------------------------------------------------------------------
+
+// DHTP2PPutMsg wire layout
+type DHTP2PPutMsg struct {
+       MsgSize    uint16             `order:"big"`     // total size of message
+       MsgType    uint16             `order:"big"`     // DHT_P2P_PUT (146)
+       BType      uint32             `order:"big"`     // block type
+       Flags      uint16             `order:"big"`     // processing flags
+       HopCount   uint16             `order:"big"`     // message hops
+       ReplLvl    uint16             `order:"big"`     // replication level
+       PathL      uint16             `order:"big"`     // path length
+       Expiration util.AbsoluteTime  ``                // expiration date
+       PeerFilter *blocks.PeerFilter ``                // peer bloomfilter
+       Key        *crypto.HashCode   ``                // query key to block
+       Origin     []byte             `size:"(PESize)"` // truncated origin (if 
TRUNCATED flag set)
+       PutPath    []*PathElementWire `size:"PathL"`    // PUT path
+       LastSig    []byte             `size:"(PESize)"` // signature of last 
hop (if RECORD_ROUTE flag is set)
+       Block      []byte             `size:"*"`        // block data
+}
+
+// NewDHTP2PPutMsg creates an empty new DHTP2PPutMsg
+func NewDHTP2PPutMsg() *DHTP2PPutMsg {
+       return &DHTP2PPutMsg{
+               MsgSize:    218,                         // total size without 
path and block data
+               MsgType:    DHT_P2P_PUT,                 // DHT_P2P_PUT (146)
+               BType:      0,                           // block type
+               Flags:      0,                           // processing flags
+               HopCount:   0,                           // message hops
+               ReplLvl:    0,                           // replication level
+               PathL:      0,                           // no PUT path
+               Expiration: util.AbsoluteTimeNever(),    // expiration date
+               PeerFilter: blocks.NewPeerFilter(),      // peer bloom filter
+               Key:        crypto.NewHashCode(nil),     // query key
+               Origin:     nil,                         // no truncated path
+               PutPath:    make([]*PathElementWire, 0), // empty PUT path
+               LastSig:    nil,                         // no signature from 
last hop
+               Block:      nil,                         // no block data
+       }
+}
+
+// PESize calculates field sizes based on flags and attributes
+func (m *DHTP2PPutMsg) PESize(field string) uint {
+       switch field {
+       case "Origin":
+               if m.Flags&enums.DHT_RO_TRUNCATED != 0 {
+                       return 32
+               }
+       case "LastSig":
+               if m.Flags&enums.DHT_RO_RECORD_ROUTE != 0 {
+                       return 64
+               }
+       }
+       return 0
+}
+
+// AddPutPath adds an element to the PUT path
+func (m *DHTP2PPutMsg) AppendPutPath(pe *PathElement) {
+       pew := pe.Wire()
+       m.PutPath = append(m.PutPath, pew)
+       m.PathL++
+       m.MsgSize += pew.Size()
+}
+
+// String returns a human-readable representation of the message.
+func (m *DHTP2PPutMsg) String() string {
+       return fmt.Sprintf("DHTP2PPutMsg{btype=%s,hops=%d,flags=%d}",
+               enums.BlockType(m.BType).String(), m.HopCount, m.Flags)
+}
+
+// Header returns the message header in a separate instance.
+func (m *DHTP2PPutMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
+}
+
+//----------------------------------------------------------------------
+// DHT-P2P-RESULT messages are used to answer peer requests for
+// bock retrieval.
+//----------------------------------------------------------------------
+
+// DHTP2PResultMsg wire layout
+type DHTP2PResultMsg struct {
+       MsgSize  uint16             `order:"big"`     // total size of message
+       MsgType  uint16             `order:"big"`     // DHT_P2P_RESULT (148)
+       BType    uint32             `order:"big"`     // Block type of result
+       Reserved uint32             `order:"big"`     // Reserved for further 
use
+       PutPathL uint16             `order:"big"`     // size of PUTPATH field
+       GetPathL uint16             `order:"big"`     // size of GETPATH field
+       Expires  util.AbsoluteTime  ``                // expiration date
+       Query    *crypto.HashCode   ``                // Query key for block
+       Origin   []byte             `size:"(PESize)"` // truncated origin (if 
TRUNCATED flag set)
+       PutPath  []*PathElementWire `size:"PutPathL"` // PUTPATH
+       GetPath  []*PathElementWire `size:"GetPathL"` // GETPATH
+       LastSig  []byte             `size:"(PESize)"` // signature of last hop 
(if RECORD_ROUTE flag is set)
+       Block    []byte             `size:"*"`        // block data
+}
+
+// NewDHTP2PResultMsg creates a new empty DHTP2PResultMsg
+func NewDHTP2PResultMsg() *DHTP2PResultMsg {
+       return &DHTP2PResultMsg{
+               MsgSize:  88,                           // size of empty message
+               MsgType:  DHT_P2P_RESULT,               // DHT_P2P_RESULT (148)
+               BType:    uint32(enums.BLOCK_TYPE_ANY), // type of returned 
block
+               Origin:   nil,                          // no truncated origin
+               PutPathL: 0,                            // empty putpath
+               PutPath:  nil,                          // -"-
+               GetPathL: 0,                            // empty getpath
+               GetPath:  nil,                          // -"-
+               LastSig:  nil,                          // no recorded route
+               Block:    nil,                          // empty block
+       }
+}
+
+// PESize calculates field sizes based on flags and attributes
+func (m *DHTP2PResultMsg) PESize(field string) uint {
+       switch field {
+       case "Origin":
+               //if m.Flags&enums.DHT_RO_TRUNCATED != 0 {
+               return 32
+               //}
+       case "LastSig":
+               //if m.Flags&enums.DHT_RO_RECORD_ROUTE != 0 {
+               return 64
+               //}
+       }
+       return 0
+}
+
+// String returns a human-readable representation of the message.
+func (m *DHTP2PResultMsg) String() string {
+       return fmt.Sprintf("DHTP2PResultMsg{btype=%s,putl=%d,getl=%d}",
+               enums.BlockType(m.BType).String(), m.PutPathL, m.GetPathL)
+}
+
+// Header returns the message header in a separate instance.
+func (m *DHTP2PResultMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
+}
+
+//----------------------------------------------------------------------
+// DHT-P2P-HELLO
+//
+// A DHT-P2P-HELLO message is used to exchange information about transports
+// with other DHT nodes. This struct is always followed by the actual
+// network addresses of type "HelloAddress"
+//----------------------------------------------------------------------
+
+// DHTP2PHelloMsg is a message send by peers to announce their presence
+type DHTP2PHelloMsg struct {
+       MsgSize   uint16              `order:"big"` // total size of message
+       MsgType   uint16              `order:"big"` // DHT_P2P_HELLO (157)
+       Reserved  uint16              `order:"big"` // Reserved for further use
+       NumAddr   uint16              `order:"big"` // Number of addresses in 
list
+       Signature *util.PeerSignature ``            // Signature
+       Expires   util.AbsoluteTime   ``            // expiration time
+       AddrList  []byte              `size:"*"`    // List of end-point 
addresses (HelloAddress)
+}
+
+// NewHelloMsgDHT creates an empty DHT_P2P_HELLO message.
+func NewDHTP2PHelloMsg() *DHTP2PHelloMsg {
+       // return empty HelloMessage
+       exp := time.Now().Add(HelloAddressExpiration)
+       return &DHTP2PHelloMsg{
+               MsgSize:   80,                         // size without 
'AddrList'
+               MsgType:   DHT_P2P_HELLO,              // DHT_P2P_HELLO (157)
+               Reserved:  0,                          // not used here
+               NumAddr:   0,                          // start with empty 
address list
+               Signature: util.NewPeerSignature(nil), // signature
+               Expires:   util.NewAbsoluteTime(exp),  // default expiration
+               AddrList:  make([]byte, 0),            // list of addresses
+       }
+}
+
+// Addresses returns the list of HelloAddress
+func (m *DHTP2PHelloMsg) Addresses() (list []*util.Address, err error) {
+       var addr *util.Address
+       var as string
+       num, pos := 0, 0
+       for {
+               // parse address string from stream
+               if as, pos = util.ReadCString(m.AddrList, pos); pos == -1 {
+                       break
+               }
+               if addr, err = util.ParseAddress(as); err != nil {
+                       return
+               }
+               addr.Expires = m.Expires
+               list = append(list, addr)
+               num++
+       }
+       // check numbers
+       if num != int(m.NumAddr) {
+               logger.Printf(logger.WARN, "[DHTP2PHelloMsg] Number of 
addresses does not match (got %d, expected %d)", num, m.NumAddr)
+       }
+       return
+}
+
+// SetAddresses adds addresses to the HELLO message.
+func (m *DHTP2PHelloMsg) SetAddresses(list []*util.Address) {
+       // write addresses as blob and track earliest expiration
+       exp := util.NewAbsoluteTime(time.Now().Add(HelloAddressExpiration))
+       wrt := new(bytes.Buffer)
+       for _, addr := range list {
+               // check if address expires before current expire
+               if exp.Compare(addr.Expires) > 0 {
+                       exp = addr.Expires
+               }
+               n, _ := wrt.Write([]byte(addr.URI()))
+               wrt.WriteByte(0)
+               m.MsgSize += uint16(n + 1)
+       }
+       m.AddrList = wrt.Bytes()
+       m.Expires = exp
+       m.NumAddr = uint16(len(list))
+}
+
+// String returns a human-readable representation of the message.
+func (m *DHTP2PHelloMsg) String() string {
+       addrs, _ := m.Addresses()
+       aList := ""
+       for i, a := range addrs {
+               if i > 0 {
+                       aList += ","
+               }
+               aList += a.URI()
+       }
+       return fmt.Sprintf("DHTP2PHelloMsg{expire:%s,addrs=%d:[%s]}", 
m.Expires, m.NumAddr, aList)
+}
+
+// Header returns the message header in a separate instance.
+func (m *DHTP2PHelloMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
+}
+
+// Verify the message signature
+func (m *DHTP2PHelloMsg) Verify(peer *util.PeerID) (bool, error) {
+       // assemble signed data and public key
+       sd := m.SignedData()
+       pub := ed25519.NewPublicKeyFromBytes(peer.Data)
+       sig, err := ed25519.NewEdSignatureFromBytes(m.Signature.Data)
+       if err != nil {
+               return false, err
+       }
+       return pub.EdVerify(sd, sig)
+}
+
+// SetSignature stores a signature in the the HELLO block
+func (m *DHTP2PHelloMsg) SetSignature(sig *util.PeerSignature) error {
+       m.Signature = sig
+       return nil
+}
+
+// SignedData assembles a data block for sign and verify operations.
+func (m *DHTP2PHelloMsg) SignedData() []byte {
+       // hash address block
+       hAddr := sha512.Sum512(m.AddrList)
+       var size uint32 = 80
+       purpose := uint32(enums.SIG_HELLO)
+
+       // assemble signed data
+       buf := new(bytes.Buffer)
+       var n int
+       err := binary.Write(buf, binary.BigEndian, size)
+       if err == nil {
+               if err = binary.Write(buf, binary.BigEndian, purpose); err == 
nil {
+                       if err = binary.Write(buf, binary.BigEndian, 
m.Expires.Epoch()*1000000); err == nil {
+                               if n, err = buf.Write(hAddr[:]); err == nil {
+                                       if n != len(hAddr[:]) {
+                                               err = errors.New("write failed")
+                                       }
+                               }
+                       }
+               }
+       }
+       if err != nil {
+               logger.Printf(logger.ERROR, "[DHTP2PHelloMsg.SignedData] 
failed: %s", err.Error())
+       }
+       return buf.Bytes()
+}
diff --git a/src/gnunet/message/msg_hello.go b/src/gnunet/message/msg_hello.go
index ae4c0e2..fdd9696 100644
--- a/src/gnunet/message/msg_hello.go
+++ b/src/gnunet/message/msg_hello.go
@@ -25,6 +25,8 @@ import (
        "gnunet/util"
        "io"
        "time"
+
+       "github.com/bfix/gospel/logger"
 )
 
 //----------------------------------------------------------------------
@@ -112,11 +114,19 @@ func (a *HelloAddress) String() string {
 // Bytes returns the binary representation of a HelloAddress
 func (a *HelloAddress) Bytes() []byte {
        buf := new(bytes.Buffer)
-       buf.Write([]byte(a.transport))
-       buf.WriteByte(0)
-       binary.Write(buf, binary.BigEndian, a.addrSize)
-       binary.Write(buf, binary.BigEndian, a.expires.Val)
-       buf.Write(a.address)
+       _, err := buf.Write([]byte(a.transport))
+       if err == nil {
+               if err = buf.WriteByte(0); err == nil {
+                       if err = binary.Write(buf, binary.BigEndian, 
a.addrSize); err == nil {
+                               if err = binary.Write(buf, binary.BigEndian, 
a.expires.Val); err != nil {
+                                       _, err = buf.Write(a.address)
+                               }
+                       }
+               }
+       }
+       if err != nil {
+               logger.Printf(logger.ERROR, "[HelloAddress] failed: %s", 
err.Error())
+       }
        return buf.Bytes()
 }
 
diff --git a/src/gnunet/message/msg_hello_dht.go 
b/src/gnunet/message/msg_hello_dht.go
deleted file mode 100644
index f51757c..0000000
--- a/src/gnunet/message/msg_hello_dht.go
+++ /dev/null
@@ -1,167 +0,0 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019-2022 Bernd Fix  >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package message
-
-import (
-       "bytes"
-       "crypto/sha512"
-       "encoding/binary"
-       "fmt"
-       "gnunet/enums"
-       "gnunet/util"
-       "time"
-
-       "github.com/bfix/gospel/crypto/ed25519"
-       "github.com/bfix/gospel/logger"
-)
-
-//----------------------------------------------------------------------
-// HELLO-DHT
-//
-// A HELLO message is used to exchange information about transports with
-// other DHT nodes. This struct is always followed by the actual network
-// addresses of type "HelloAddress"
-//----------------------------------------------------------------------
-
-// HelloDHTMsg is a message send by peers to announce their presence
-type HelloDHTMsg struct {
-       MsgSize   uint16            `order:"big"` // total size of message
-       MsgType   uint16            `order:"big"` // DHT_P2P_HELLO (157)
-       Reserved  uint16            `order:"big"` // Reserved for further use
-       NumAddr   uint16            `order:"big"` // Number of addresses in list
-       Signature []byte            `size:"64"`   // Signature
-       Expires   util.AbsoluteTime ``            // expiration time
-       AddrList  []byte            `size:"*"`    // List of end-point 
addresses (HelloAddress)
-}
-
-// NewHelloMsgDHT creates an empty DHT_P2P_HELLO message.
-func NewHelloDHTMsg() *HelloDHTMsg {
-       // return empty HelloMessage
-       exp := time.Now().Add(HelloAddressExpiration)
-       return &HelloDHTMsg{
-               MsgSize:   80,                        // size without 'AddrList'
-               MsgType:   DHT_P2P_HELLO,             // DHT_P2P_HELLO (157)
-               Reserved:  0,                         // not used here
-               NumAddr:   0,                         // start with empty 
address list
-               Signature: make([]byte, 64),          // signature
-               Expires:   util.NewAbsoluteTime(exp), // default expiration
-               AddrList:  make([]byte, 0),           // list of addresses
-       }
-}
-
-// Addresses returns the list of HelloAddress
-func (m *HelloDHTMsg) Addresses() (list []*util.Address, err error) {
-       var addr *util.Address
-       var as string
-       num, pos := 0, 0
-       for {
-               // parse address string from stream
-               if as, pos = util.ReadCString(m.AddrList, pos); pos == -1 {
-                       break
-               }
-               if addr, err = util.ParseAddress(as); err != nil {
-                       return
-               }
-               addr.Expires = m.Expires
-               list = append(list, addr)
-               num++
-       }
-       // check numbers
-       if num != int(m.NumAddr) {
-               logger.Printf(logger.WARN, "[HelloDHTMsg] Number of addresses 
does not match (got %d, expected %d)", num, m.NumAddr)
-       }
-       return
-}
-
-// SetAddresses adds addresses to the HELLO message.
-func (m *HelloDHTMsg) SetAddresses(list []*util.Address) {
-       // write addresses as blob and track earliest expiration
-       exp := util.NewAbsoluteTime(time.Now().Add(HelloAddressExpiration))
-       wrt := new(bytes.Buffer)
-       for _, addr := range list {
-               // check if address expires before current expire
-               if exp.Compare(addr.Expires) > 0 {
-                       exp = addr.Expires
-               }
-               n, _ := wrt.Write([]byte(addr.URI()))
-               wrt.WriteByte(0)
-               m.MsgSize += uint16(n + 1)
-       }
-       m.AddrList = wrt.Bytes()
-       m.Expires = exp
-       m.NumAddr = uint16(len(list))
-}
-
-// String returns a human-readable representation of the message.
-func (m *HelloDHTMsg) String() string {
-       addrs, _ := m.Addresses()
-       aList := ""
-       for i, a := range addrs {
-               if i > 0 {
-                       aList += ","
-               }
-               aList += a.URI()
-       }
-       return fmt.Sprintf("HelloDHTMsg{expire:%s,addrs=%d:[%s]}", m.Expires, 
m.NumAddr, aList)
-}
-
-// Header returns the message header in a separate instance.
-func (m *HelloDHTMsg) Header() *Header {
-       return &Header{m.MsgSize, m.MsgType}
-}
-
-// Verify the message signature
-func (m *HelloDHTMsg) Verify(peer *util.PeerID) (bool, error) {
-       // assemble signed data and public key
-       sd := m.signedData()
-       pub := peer.PublicKey()
-       sig, err := ed25519.NewEdSignatureFromBytes(m.Signature)
-       if err != nil {
-               return false, err
-       }
-       return pub.EdVerify(sd, sig)
-}
-
-// Sign the HELLO data with private key
-func (m *HelloDHTMsg) Sign(prv *ed25519.PrivateKey) error {
-       // assemble signed data
-       sd := m.signedData()
-       sig, err := prv.EdSign(sd)
-       if err != nil {
-               return err
-       }
-       m.Signature = sig.Bytes()
-       return nil
-}
-
-// signedData assembles a data block for sign and verify operations.
-func (m *HelloDHTMsg) signedData() []byte {
-       // hash address block
-       hAddr := sha512.Sum512(m.AddrList)
-       var size uint32 = 80
-       purpose := uint32(enums.SIG_HELLO)
-
-       // assemble signed data
-       buf := new(bytes.Buffer)
-       binary.Write(buf, binary.BigEndian, size)
-       binary.Write(buf, binary.BigEndian, purpose)
-       binary.Write(buf, binary.BigEndian, m.Expires.Epoch()*1000000)
-       buf.Write(hAddr[:])
-       return buf.Bytes()
-}
diff --git a/src/gnunet/message/msg_namecache.go 
b/src/gnunet/message/msg_namecache.go
index 517f11b..c36ec7c 100644
--- a/src/gnunet/message/msg_namecache.go
+++ b/src/gnunet/message/msg_namecache.go
@@ -149,7 +149,7 @@ func (m *NamecacheCacheMsg) Header() *Header {
 // NAMECACHE_BLOCK_CACHE_RESPONSE
 //----------------------------------------------------------------------
 
-// NamecacheCacheResponseMsg is the reponse message for a put request
+// NamecacheCacheResponseMsg is the response message for a put request
 type NamecacheCacheResponseMsg struct {
        MsgSize uint16 `order:"big"` // total size of message
        MsgType uint16 `order:"big"` // NAMECACHE_LOOKUP_BLOCK_RESPONSE (432)
diff --git a/src/gnunet/message/msg_transport.go 
b/src/gnunet/message/msg_transport.go
index 18d8ceb..fca651f 100644
--- a/src/gnunet/message/msg_transport.go
+++ b/src/gnunet/message/msg_transport.go
@@ -28,6 +28,7 @@ import (
 
        "github.com/bfix/gospel/crypto/ed25519"
        "github.com/bfix/gospel/data"
+       "github.com/bfix/gospel/logger"
 )
 
 //----------------------------------------------------------------------
@@ -106,7 +107,9 @@ func NewTransportPingMsg(target *util.PeerID, a 
*util.Address) *TransportPingMsg
 // String returns a human-readable representation of the message.
 func (m *TransportPingMsg) String() string {
        a := new(util.Address)
-       data.Unmarshal(a, m.Address)
+       if err := data.Unmarshal(a, m.Address); err != nil {
+               logger.Printf(logger.ERROR, "[TransportPingMsg.String] failed: 
%s", err.Error())
+       }
        return fmt.Sprintf("TransportPingMsg{target=%s,addr=%s,challenge=%d}",
                m.Target, a, m.Challenge)
 }
@@ -155,7 +158,7 @@ func NewSignedAddress(a *util.Address) *SignedAddress {
        return addr
 }
 
-// TransportPongMsg is a reponse message for a PING request
+// TransportPongMsg is a response message for a PING request
 type TransportPongMsg struct {
        MsgSize     uint16         `order:"big"` // total size of message
        MsgType     uint16         `order:"big"` // TRANSPORT_PING (372)
@@ -164,7 +167,7 @@ type TransportPongMsg struct {
        SignedBlock *SignedAddress // signed block of data
 }
 
-// NewTransportPongMsg creates a reponse message with an address the replying
+// NewTransportPongMsg creates a response message with an address the replying
 // peer wants to be reached.
 func NewTransportPongMsg(challenge uint32, a *util.Address) *TransportPongMsg {
        m := &TransportPongMsg{
@@ -189,7 +192,7 @@ func (m *TransportPongMsg) String() string {
                return fmt.Sprintf("TransportPongMsg{addr=%s,challenge=%d}",
                        a, m.Challenge)
        }
-       return fmt.Sprintf("TransportPongMsg{addr=<unkown>,%d}", m.Challenge)
+       return fmt.Sprintf("TransportPongMsg{addr=<unknown>,%d}", m.Challenge)
 }
 
 // Header returns the message header in a separate instance.
@@ -228,7 +231,7 @@ func (m *TransportPongMsg) Verify(pub *ed25519.PublicKey) 
(bool, error) {
 // TRANSPORT_SESSION_ACK
 //----------------------------------------------------------------------
 
-// SessionAckMsg is a message to acknowlege a session request
+// SessionAckMsg is a message to acknowledge a session request
 type SessionAckMsg struct {
        MsgSize uint16 `order:"big"` // total size of message
        MsgType uint16 `order:"big"` // TRANSPORT_SESSION_ACK (377)
diff --git a/src/gnunet/message/types.go b/src/gnunet/message/types.go
index 3b1cf7a..6fa4d3a 100644
--- a/src/gnunet/message/types.go
+++ b/src/gnunet/message/types.go
@@ -16,6 +16,7 @@
 //
 // SPDX-License-Identifier: AGPL3.0-or-later
 
+//nolint:stylecheck // allow non-camel-case in constants
 package message
 
 // GNUnet message types
@@ -370,7 +371,7 @@ const (
        NAMESTORE_MONITOR_START          = 441 // Client to service: start 
monitoring (yields sequence of "ZONE_ITERATION_RESPONSES" --- forever).
        NAMESTORE_MONITOR_SYNC           = 442 // Service to client: you're now 
in sync.
        NAMESTORE_RECORD_RESULT          = 443 // Service to client: here is a 
(plaintext) record you requested.
-       NAMESTORE_MONITOR_NEXT           = 444 // Client to service: I am now 
ready for the next (set of) monitor events. Monitoring equivlaent of 
#NAMESTORE_ZONE_ITERATION_NEXT.
+       NAMESTORE_MONITOR_NEXT           = 444 // Client to service: I am now 
ready for the next (set of) monitor events. Monitoring equivalent of 
#NAMESTORE_ZONE_ITERATION_NEXT.
        NAMESTORE_ZONE_ITERATION_START   = 445 // Client to service: please 
start iteration; receives "NAMESTORE_LOOKUP_NAME_RESPONSE" messages in return.
        NAMESTORE_ZONE_ITERATION_NEXT    = 447 // Client to service: next 
record(s) in iteration please.
        NAMESTORE_ZONE_ITERATION_STOP    = 448 // Client to service: stop 
iterating.
@@ -445,7 +446,7 @@ const (
        CONSENSUS_P2P_ELEMENTS_REQUEST    = 544 // Elements, and requests for 
further elements
        CONSENSUS_P2P_ELEMENTS_REPORT     = 545 // Elements that a peer reports 
to be missing at the remote peer.
        CONSENSUS_P2P_HELLO               = 546 // Initialization message for 
consensus p2p communication.
-       CONSENSUS_P2P_SYNCED              = 547 // Report that the peer is 
synced with the partner after successfuly decoding the invertible bloom filter.
+       CONSENSUS_P2P_SYNCED              = 547 // Report that the peer is 
synced with the partner after successfully decoding the invertible bloom filter.
        CONSENSUS_P2P_FIN                 = 548 // Interaction os over, got 
synched and reported all elements
        CONSENSUS_P2P_ABORT               = 548 // Abort a round, don't send 
requested elements anymore
        CONSENSUS_P2P_ROUND_CONTEXT       = 547 // Abort a round, don't send 
requested elements anymore
diff --git a/src/gnunet/service/client.go b/src/gnunet/service/client.go
index 81a9f01..5f7f8f0 100644
--- a/src/gnunet/service/client.go
+++ b/src/gnunet/service/client.go
@@ -67,7 +67,6 @@ func RequestResponse(
        callee string,
        path string,
        req message.Message) (message.Message, error) {
-
        // client-connect to the service
        logger.Printf(logger.DBG, "[%s] Connecting to %s service...\n", caller, 
callee)
        cl, err := NewClient(ctx, path)
diff --git a/src/gnunet/service/connection.go b/src/gnunet/service/connection.go
index 1c690c5..d443160 100644
--- a/src/gnunet/service/connection.go
+++ b/src/gnunet/service/connection.go
@@ -23,6 +23,7 @@ import (
        "errors"
        "fmt"
        "gnunet/message"
+       "gnunet/util"
        "net"
        "os"
        "strconv"
@@ -43,6 +44,7 @@ var (
 // based on Unix domain sockets. It is used locally by services and
 // clients in the standard GNUnet environment.
 type Connection struct {
+       id   int      // connection identifier
        path string   // file name of Unix socket
        conn net.Conn // associated connection
        buf  []byte   // read/write buffer
@@ -53,6 +55,7 @@ type Connection struct {
 func NewConnection(ctx context.Context, path string) (s *Connection, err 
error) {
        var d net.Dialer
        s = new(Connection)
+       s.id = util.NextID()
        s.path = path
        s.buf = make([]byte, 65536)
        s.conn, err = d.DialContext(ctx, "unix", path)
@@ -118,11 +121,11 @@ func (s *Connection) Receive(ctx context.Context) 
(message.Message, error) {
                return nil, err
        }
        // get rest of message
-       if err := get(4, int(mh.MsgSize)-4); err != nil {
+       if err = get(4, int(mh.MsgSize)-4); err != nil {
                return nil, err
        }
-       msg, err := message.NewEmptyMessage(mh.MsgType)
-       if err != nil {
+       var msg message.Message
+       if msg, err = message.NewEmptyMessage(mh.MsgType); err != nil {
                return nil, err
        }
        if msg == nil {
@@ -134,6 +137,11 @@ func (s *Connection) Receive(ctx context.Context) 
(message.Message, error) {
        return msg, nil
 }
 
+// Receiver returns the receiving client (string representation)
+func (s *Connection) Receiver() string {
+       return fmt.Sprintf("uds:%d", s.id)
+}
+
 //----------------------------------------------------------------------
 // internal methods
 //----------------------------------------------------------------------
@@ -212,7 +220,6 @@ func NewConnectionManager(
        params map[string]string, // connection parameters
        hdlr chan *Connection, // handler for incoming connections
 ) (cs *ConnectionManager, err error) {
-
        // instantiate channel server
        cs = &ConnectionManager{
                listener: nil,
@@ -224,24 +231,21 @@ func NewConnectionManager(
                return
        }
        // handle additional parameters
-       if params != nil {
-               for key, value := range params {
-                       switch key {
-                       case "perm": // set permissions on 'unix'
-                               if perm, err := strconv.ParseInt(value, 8, 32); 
err == nil {
-                                       if err := os.Chmod(path, 
os.FileMode(perm)); err != nil {
-                                               logger.Printf(
-                                                       logger.ERROR,
-                                                       "MsgChannelServer: 
Failed to set permissions %s on %s: %s\n",
-                                                       path, value, 
err.Error())
-
-                                       }
-                               } else {
+       for key, value := range params {
+               switch key {
+               case "perm": // set permissions on 'unix'
+                       if perm, err := strconv.ParseInt(value, 8, 32); err == 
nil {
+                               if err := os.Chmod(path, os.FileMode(perm)); 
err != nil {
                                        logger.Printf(
                                                logger.ERROR,
-                                               "MsgChannelServer: Invalid 
permissions '%s'\n",
-                                               value)
+                                               "MsgChannelServer: Failed to 
set permissions %s on %s: %s\n",
+                                               path, value, err.Error())
                                }
+                       } else {
+                               logger.Printf(
+                                       logger.ERROR,
+                                       "MsgChannelServer: Invalid permissions 
'%s'\n",
+                                       value)
                        }
                }
        }
diff --git a/src/gnunet/service/dht/blocks/filters.go 
b/src/gnunet/service/dht/blocks/filters.go
new file mode 100644
index 0000000..0d194cc
--- /dev/null
+++ b/src/gnunet/service/dht/blocks/filters.go
@@ -0,0 +1,296 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix  >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package blocks
+
+import (
+       "bytes"
+       "crypto/sha512"
+       "encoding/binary"
+       "gnunet/util"
+
+       "github.com/bfix/gospel/logger"
+)
+
+//======================================================================
+// Peer filter
+//======================================================================
+
+// PeerFilter is a bloom filter without mutator
+type PeerFilter struct {
+       BF *BloomFilter
+}
+
+// NewPeerFilter creates an empty peer filter instance.
+func NewPeerFilter() *PeerFilter {
+       return &PeerFilter{
+               BF: NewBloomFilter(128),
+       }
+}
+
+// Add peer id to the filter
+func (pf *PeerFilter) Add(p *util.PeerID) {
+       pf.BF.Add(p.Data)
+}
+
+// Contains returns true if the peer id is filtered (in the filter)
+func (pf *PeerFilter) Contains(p *util.PeerID) bool {
+       return pf.BF.Contains(p.Data)
+}
+
+// Cloone peer filter instance
+func (pf *PeerFilter) Clone() *PeerFilter {
+       return &PeerFilter{
+               BF: pf.BF.Clone(),
+       }
+}
+
+//======================================================================
+// Result filter
+//======================================================================
+
+// ResultFilter return values
+//nolint:stylecheck // allow non-camel-case in constants
+const (
+       RF_MORE       = iota // Valid result, and there may be more.
+       RF_LAST              // Last possible valid result.
+       RF_DUPLICATE         // Valid result, but duplicate (was filtered by 
the result filter).
+       RF_IRRELEVANT        // Block does not satisfy the constraints imposed 
by the XQuery.
+)
+
+// Compare return values
+//nolint:stylecheck // allow non-camel-case in constants
+const (
+       CMP_SAME   = iota // the two result filter are the same
+       CMP_MERGE         // the two result filter can be merged
+       CMP_DIFFER        // the two result filter are different
+       CMP_1             // used as state by derived/complex compare functions
+       CMP_2
+       CMP_3
+)
+
+//----------------------------------------------------------------------
+
+// ResultFilter is used to indicate to other peers which results are not of
+// interest when processing a GetMessage. Any peer which is processing
+// GetMessages and has a result which matches the query key MUST check the
+// result filter and only send a reply message if the result does not test
+// positive under the result filter. Before forwarding the GetMessage, the
+// result filter MUST be updated to filter out all results already returned
+// by the local peer.
+type ResultFilter interface {
+
+       // Add entry to filter
+       Add(Block)
+
+       // Contains returns true if entry is filtered
+       Contains(Block) bool
+
+       // Bytes returns the binary representation of a result filter
+       Bytes() []byte
+
+       // Compare two result filters
+       Compare(ResultFilter) int
+
+       // Merge two result filters
+       Merge(ResultFilter) bool
+}
+
+//----------------------------------------------------------------------
+// Dummy result filter
+// [Additional filters (per block type) are defined in corresponding files]
+//----------------------------------------------------------------------
+
+// PassResultFilter is a dummy result filter with no state.
+type PassResultFilter struct{}
+
+// Add a block to the result filter.
+func (rf *PassResultFilter) Add(Block) {
+}
+
+// Contains returns true if entry (binary representation) is filtered
+func (rf *PassResultFilter) Contains(Block) bool {
+       return false
+}
+
+// Bytes returns the binary representation of a result filter
+func (rf *PassResultFilter) Bytes() (buf []byte) {
+       return
+}
+
+// Merge two result filters
+func (rf *PassResultFilter) Merge(ResultFilter) bool {
+       return true
+}
+
+// Compare two result filters
+func (rf *PassResultFilter) Compare(t ResultFilter) int {
+       if _, ok := t.(*PassResultFilter); ok {
+               return CMP_SAME
+       }
+       return CMP_DIFFER
+}
+
+//======================================================================
+// Generic bllom filter with mutator
+//======================================================================
+
+// BloomFilter is a space-efficient probabilistic datastructure to test if
+// an element is part of a set of elementsis defined as a string of bits
+// always initially empty. An optional mutator can be used to additionally
+// "randomize" the computation of the bloomfilter while remaining 
deterministic.
+type BloomFilter struct {
+       Bits []byte // filter bits
+
+       // transient attributes
+       mInput []byte // mutator input
+       mData  []byte // mutator data
+}
+
+// NewBloomFilter creates a new empty filter of given size (8*n bits).
+func NewBloomFilter(n int) *BloomFilter {
+       return &BloomFilter{
+               Bits:   make([]byte, n),
+               mInput: nil,
+               mData:  nil,
+       }
+}
+
+// SetMutator to define a mutator for randomization. If 'm' is nil,
+// the mutator is removed from the filter (use with care!)
+func (bf *BloomFilter) SetMutator(m any) {
+       // handle mutator input
+       switch v := m.(type) {
+       case uint32:
+               buf := new(bytes.Buffer)
+               if err := binary.Write(buf, binary.BigEndian, v); err != nil {
+                       logger.Printf(logger.ERROR, "[BloomFilter.SetMutator] 
failed: %s", err.Error())
+               }
+               bf.mInput = buf.Bytes()
+       case []byte:
+               bf.mInput = make([]byte, 4)
+               util.CopyAlignedBlock(bf.mInput, v)
+       case nil:
+               bf.mInput = nil
+               bf.mData = nil
+               return
+       }
+       // generate mutator bytes
+       h := sha512.New()
+       if _, err := h.Write(bf.mInput); err != nil {
+               logger.Printf(logger.ERROR, "[BloomFilter.SetMutator] failed: 
%s", err.Error())
+       }
+       bf.mData = h.Sum(nil)
+
+       //logger.Printf(logger.DBG, "[filter] Mutator %s -> %s", 
hex.EncodeToString(bf.mInput), hex.EncodeToString(bf.mData))
+}
+
+// Mutator returns the mutator input as a 4-byte array
+func (bf *BloomFilter) Mutator() []byte {
+       return bf.mInput
+}
+
+// Bytes returns the binary representation of a bloom filter
+func (bf *BloomFilter) Bytes() []byte {
+       var buf []byte
+       if bf.mInput != nil {
+               buf = append(buf, bf.mInput...)
+       }
+       buf = append(buf, bf.Bits...)
+       return buf
+}
+
+// Compare two bloom filters
+func (bf *BloomFilter) Compare(a *BloomFilter) int {
+       if len(bf.Bits) != len(a.Bits) || !bytes.Equal(bf.mInput, a.mInput) {
+               return CMP_DIFFER
+       }
+       if bytes.Equal(bf.Bits, a.Bits) {
+               return CMP_SAME
+       }
+       return CMP_MERGE
+}
+
+// Merge two bloom filters
+func (bf *BloomFilter) Merge(a *BloomFilter) bool {
+       if len(bf.Bits) != len(a.Bits) || !bytes.Equal(bf.mInput, a.mInput) {
+               return false
+       }
+       for i := range bf.Bits {
+               bf.Bits[i] |= a.Bits[i]
+       }
+       return true
+}
+
+// Clone a bloom filter instance
+func (bf *BloomFilter) Clone() *BloomFilter {
+       return &BloomFilter{
+               Bits:   util.Clone(bf.Bits),
+               mInput: util.Clone(bf.mInput),
+               mData:  util.Clone(bf.mData),
+       }
+}
+
+// Add entry (binary representation):
+// When adding an element to the Bloom filter bf using BF-SET(bf,e), each
+// integer n of the mapping M(e) is interpreted as a bit offset n mod L
+// within bf and set to 1.
+func (bf *BloomFilter) Add(e []byte) {
+       for _, idx := range bf.indices(e) {
+               bf.Bits[idx/8] |= (1 << (idx % 8))
+       }
+}
+
+// Contains returns true if the entry is most likely to be included:
+// When testing if an element may be in the Bloom filter bf using
+// BF-TEST(bf,e), each bit offset n mod L within bf MUST have been set to 1.
+// Otherwise, the element is not considered to be in the Bloom filter.
+func (bf *BloomFilter) Contains(e []byte) bool {
+       for _, idx := range bf.indices(e) {
+               if bf.Bits[idx/8]&(1<<(idx%8)) == 0 {
+                       return false
+               }
+       }
+       return true
+}
+
+// indices returns the list of bit indices for antry e:
+// The element e is hashed using SHA-512. If a mutator is present, the
+// hash values are XOR-ed. The resulting value is interpreted as a list
+// of 16 32-bit integers in network byte order.
+func (bf *BloomFilter) indices(e []byte) []uint32 {
+       // hash the entry
+       h := sha512.Sum512(e)
+       // apply mutator if available
+       if bf.mData != nil {
+               for i := range h {
+                       h[i] ^= bf.mData[i]
+               }
+       }
+       // compute the indices for the entry
+       size := uint32(8 * len(bf.Bits))
+       idx := make([]uint32, 16)
+       buf := bytes.NewReader(h[:])
+       for i := range idx {
+               if err := binary.Read(buf, binary.BigEndian, &idx[i]); err != 
nil {
+                       logger.Printf(logger.ERROR, "[BloomFilter.indices] 
failed: %s", err.Error())
+               }
+               idx[i] %= size
+       }
+       return idx
+}
diff --git a/src/gnunet/service/dht/blocks/filters_test.go 
b/src/gnunet/service/dht/blocks/filters_test.go
new file mode 100644
index 0000000..ef1331c
--- /dev/null
+++ b/src/gnunet/service/dht/blocks/filters_test.go
@@ -0,0 +1,110 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix  >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package blocks
+
+import (
+       "bytes"
+       "crypto/rand"
+       "sort"
+       "testing"
+)
+
+type Entry []byte
+
+type EntryList []Entry
+
+func (list EntryList) Len() int           { return len(list) }
+func (list EntryList) Swap(i, j int)      { list[i], list[j] = list[j], 
list[i] }
+func (list EntryList) Less(i, j int) bool { return bytes.Compare(list[i], 
list[j]) < 0 }
+
+func (list EntryList) Contains(e Entry) bool {
+       size := len(list)
+       i := sort.Search(size, func(i int) bool { return bytes.Compare(list[i], 
e) >= 0 })
+       return i != size
+}
+
+func TestBloomfilter(t *testing.T) {
+       F := 500 // number of expected entries
+
+       // The K-value for the HELLO_BF Bloom filter is always 16. The size S of
+       // the Bloom filter in bytes depends on the number of elements F known 
to
+       // be filtered at the initiator. If F is zero, the size S is just 8 
(bytes).
+       // Otherwise, S is set to the minimum of 2^15 and the lowest power of 2 
that
+       // is strictly larger than K*F/4 (in bytes). The wire format of 
HELLO_BF is
+       // the resulting byte array. In particular, K is never transmitted.
+       S := 1
+       for S < 4*F && S < 32768 {
+               S <<= 1
+       }
+       t.Logf("BloomFilter size in bytes: %d\n", S)
+
+       // generate positives (entries in the set)
+       positives := make(EntryList, F)
+       for i := 0; i < F; i++ {
+               data := make(Entry, 32)
+               if _, err := rand.Read(data); err != nil {
+                       t.Fatal(err)
+               }
+               positives[i] = data
+       }
+       sort.Sort(positives)
+
+       // generate negatives (entries outside the set)
+       negatives := make(EntryList, F)
+       for i := 0; i < F; {
+               data := make(Entry, 32)
+               if _, err := rand.Read(data); err != nil {
+                       t.Fatal(err)
+               }
+               if !positives.Contains(data) {
+                       negatives[i] = data
+                       i++
+               }
+       }
+
+       // create BloomFilter
+       bf := NewBloomFilter(S)
+
+       // add positives to bloomfilter
+       for _, e := range positives {
+               bf.Add(e)
+       }
+
+       // check lookup of positives
+       count := 0
+       for _, e := range positives {
+               if !bf.Contains(e) {
+                       count++
+               }
+       }
+       if count > 0 {
+               t.Logf("FAILED with %d false-negatives", count)
+       }
+
+       // check lookup of negatives
+       count = 0
+       for _, e := range negatives {
+               if bf.Contains(e) {
+                       count++
+               }
+       }
+       if count > 0 {
+               t.Logf("FAILED with %d false-positives", count)
+       }
+}
diff --git a/src/gnunet/service/dht/blocks/generic.go 
b/src/gnunet/service/dht/blocks/generic.go
index 6301e3b..2962025 100644
--- a/src/gnunet/service/dht/blocks/generic.go
+++ b/src/gnunet/service/dht/blocks/generic.go
@@ -19,11 +19,10 @@
 package blocks
 
 import (
-       "bytes"
-       "encoding/gob"
        "encoding/hex"
        "fmt"
        "gnunet/crypto"
+       "gnunet/enums"
        "gnunet/util"
 
        "github.com/bfix/gospel/data"
@@ -39,13 +38,11 @@ type Query interface {
        // Key returns the DHT key for a block
        Key() *crypto.HashCode
 
-       // Get retrieves the value of a named query parameter. The value is
-       // unchanged if the key is not in the map or if the value in the map
-       // has an incompatible type.
-       Get(key string, value any) bool
+       // Type returns the requested block type
+       Type() uint16
 
-       // Set stores the value of a named query parameter
-       Set(key string, value any)
+       // Flags returns the query flags
+       Flags() uint16
 
        // Verify the integrity of a retrieved block (optional). Override in
        // custom query types to implement block-specific integrity checks
@@ -77,7 +74,7 @@ type Block interface {
        // types to implement block-specific integrity checks (see GNSBlock for
        // example). This verification is usually weaker than the verification
        // method from a Query (see GNSBlock.Verify for explanation).
-       Verify() error
+       Verify() (bool, error)
 
        // String returns the human-readable representation of a block
        String() string
@@ -97,8 +94,14 @@ type GenericQuery struct {
        // Key for repository queries (local/remote)
        key *crypto.HashCode
 
-       // query parameters (binary value representation)
-       params map[string][]byte
+       // block type requested
+       btype uint16
+
+       // query flags
+       flags uint16
+
+       // Params holds additional query parameters
+       Params util.ParameterSet
 }
 
 // Key interface method implementation
@@ -106,23 +109,14 @@ func (q *GenericQuery) Key() *crypto.HashCode {
        return q.key
 }
 
-// Get retrieves the value of a named query parameter
-func (q *GenericQuery) Get(key string, value any) bool {
-       data, ok := q.params[key]
-       if !ok {
-               return false
-       }
-       dec := gob.NewDecoder(bytes.NewReader(data))
-       return dec.Decode(value) != nil
+// Type returns the requested block type
+func (q *GenericQuery) Type() uint16 {
+       return q.btype
 }
 
-// Set stores the value of a named query parameter
-func (q *GenericQuery) Set(key string, value any) {
-       wrt := new(bytes.Buffer)
-       enc := gob.NewEncoder(wrt)
-       if enc.Encode(value) == nil {
-               q.params[key] = wrt.Bytes()
-       }
+// Flags returns the query flags
+func (q *GenericQuery) Flags() uint16 {
+       return q.flags
 }
 
 // Verify interface method implementation
@@ -139,14 +133,16 @@ func (q *GenericQuery) Decrypt(b Block) error {
 
 // String returns the human-readable representation of a block
 func (q *GenericQuery) String() string {
-       return fmt.Sprintf("GenericQuery{key=%s}", 
hex.EncodeToString(q.Key().Bits))
+       return fmt.Sprintf("GenericQuery{btype=%d,key=%s}", q.btype, 
hex.EncodeToString(q.Key().Bits))
 }
 
 // NewGenericQuery creates a simple Query from hash code.
-func NewGenericQuery(buf []byte) *GenericQuery {
+func NewGenericQuery(key []byte, btype enums.BlockType, flags uint16) 
*GenericQuery {
        return &GenericQuery{
-               key:    crypto.NewHashCode(buf),
-               params: make(map[string][]byte),
+               key:    crypto.NewHashCode(key),
+               btype:  uint16(btype),
+               flags:  flags,
+               Params: make(util.ParameterSet),
        }
 }
 
@@ -181,16 +177,16 @@ func (b *GenericBlock) String() string {
 }
 
 // Verify interface method implementation
-func (b *GenericBlock) Verify() error {
+func (b *GenericBlock) Verify() (bool, error) {
        // no verification, no errors ;)
-       return nil
+       return true, nil
 }
 
 // NewGenericBlock creates a Block from binary data.
 func NewGenericBlock(buf []byte) *GenericBlock {
        return &GenericBlock{
                block:  util.Clone(buf),
-               btype:  DHT_BLOCK_ANY,            // unknown block type
-               expire: util.AbsoluteTimeNever(), // never expires
+               btype:  uint16(enums.BLOCK_TYPE_ANY), // unknown block type
+               expire: util.AbsoluteTimeNever(),     // never expires
        }
 }
diff --git a/src/gnunet/service/dht/blocks/generic_test.go 
b/src/gnunet/service/dht/blocks/generic_test.go
deleted file mode 100644
index 51ee5a1..0000000
--- a/src/gnunet/service/dht/blocks/generic_test.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019-2022 Bernd Fix  >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package blocks
-
-import (
-       "bytes"
-       "testing"
-)
-
-// Test parameter handling for queries
-func TestQueryParams(t *testing.T) {
-       q := NewGenericQuery(nil)
-
-       // set parameters
-       var (
-               btype uint16 = DHT_BLOCK_ANY
-               flags uint32 = 0
-               name  string = "Test"
-               data         = make([]byte, 8)
-       )
-       q.Set("btype", btype)
-       q.Set("flags", flags)
-       q.Set("name", name)
-       q.Set("data", data)
-
-       // get parameters
-       var (
-               t_btype uint16
-               t_flags uint32
-               t_name  string
-               t_data  []byte
-       )
-       q.Get("btype", &t_btype)
-       q.Get("flags", &t_flags)
-       q.Get("name", &t_name)
-       q.Get("data", &t_data)
-
-       // check for unchanged data
-       if btype != t_btype {
-               t.Fatal("btype mismatch")
-       }
-       if flags != t_flags {
-               t.Fatal("flags mismatch")
-       }
-       if name != t_name {
-               t.Fatal("name mismatch")
-       }
-       if !bytes.Equal(data, t_data) {
-               t.Fatal("data mismatch")
-       }
-}
diff --git a/src/gnunet/service/dht/blocks/gns.go 
b/src/gnunet/service/dht/blocks/gns.go
index 2085677..0225003 100644
--- a/src/gnunet/service/dht/blocks/gns.go
+++ b/src/gnunet/service/dht/blocks/gns.go
@@ -22,14 +22,19 @@ import (
        "errors"
        "fmt"
        "gnunet/crypto"
+       "gnunet/enums"
        "gnunet/util"
 
        "github.com/bfix/gospel/data"
+       "github.com/bfix/gospel/logger"
 )
 
 // Error messages
 var (
-       ErrBlockNotDecrypted = fmt.Errorf("GNS block not decrypted")
+       ErrBlockNotDecrypted    = errors.New("GNS block not decrypted")
+       ErrBlockInvalidSig      = errors.New("invalid signature key for GNS 
Block")
+       ErrBlockTypeNotVerified = errors.New("can't verify block type")
+       ErrBlockCantDecrypt     = errors.New("can't decrypt block type")
 )
 
 //----------------------------------------------------------------------
@@ -54,9 +59,13 @@ func (q *GNSQuery) Verify(b Block) (err error) {
 
                // verify derived key
                dkey := blk.DerivedKeySig.ZoneKey
-               dkey2, _ := q.Zone.Derive(q.Label, "gns")
+               var dkey2 *crypto.ZoneKey
+               if dkey2, _, err = q.Zone.Derive(q.Label, "gns"); err != nil {
+                       return
+               }
                if !dkey.Equal(dkey2) {
-                       return fmt.Errorf("invalid signature key for GNS Block")
+                       err = ErrBlockInvalidSig
+                       return
                }
                // verify signature
                var buf []byte
@@ -66,7 +75,7 @@ func (q *GNSQuery) Verify(b Block) (err error) {
                blk.verified, err = blk.DerivedKeySig.Verify(buf)
 
        default:
-               err = errors.New("can't verify block type")
+               err = ErrBlockTypeNotVerified
        }
        return
 }
@@ -81,7 +90,7 @@ func (q *GNSQuery) Decrypt(b Block) (err error) {
                return
 
        default:
-               err = errors.New("can't decrypt block type")
+               err = ErrBlockCantDecrypt
        }
        return
 }
@@ -91,10 +100,13 @@ func NewGNSQuery(zkey *crypto.ZoneKey, label string) 
*GNSQuery {
        // derive a public key from (pkey,label) and set the repository
        // key as the SHA512 hash of the binary key representation.
        // (key blinding)
-       pd, _ := zkey.Derive(label, "gns")
+       pd, _, err := zkey.Derive(label, "gns")
+       if err != nil {
+               logger.Printf(logger.ERROR, "[NewGNSQuery] failed: %s", 
err.Error())
+       }
        gq := crypto.Hash(pd.Bytes()).Bits
        return &GNSQuery{
-               GenericQuery: *NewGenericQuery(gq),
+               GenericQuery: *NewGenericQuery(gq, 
enums.BLOCK_TYPE_GNS_NAMERECORD, 0),
                Zone:         zkey,
                Label:        label,
                derived:      pd,
@@ -161,12 +173,11 @@ func NewBlock() *GNSBlock {
 // Only the cryptographic signature is verified; the formal correctness of
 // the association between the block and a GNS label in a GNS zone can't
 // be verified. This is only possible in Query.Verify().
-func (b *GNSBlock) Verify() (err error) {
+func (b *GNSBlock) Verify() (ok bool, err error) {
        // verify signature
        var buf []byte
        if buf, err = data.Marshal(b.Body); err != nil {
                return
        }
-       _, err = b.DerivedKeySig.Verify(buf)
-       return
+       return b.DerivedKeySig.Verify(buf)
 }
diff --git a/src/gnunet/service/dht/blocks/handlers.go 
b/src/gnunet/service/dht/blocks/handlers.go
new file mode 100644
index 0000000..9df3867
--- /dev/null
+++ b/src/gnunet/service/dht/blocks/handlers.go
@@ -0,0 +1,80 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix  >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package blocks
+
+import (
+       "gnunet/crypto"
+       "gnunet/enums"
+)
+
+// BlockHandler interface defines methods specific to block types.
+type BlockHandler interface {
+
+       // Parse a block instance from binary data
+       ParseBlock(buf []byte) (Block, error)
+
+       // ValidateBlockQuery is used to evaluate the request for a block as 
part of
+       // DHT-P2P-GET processing. Here, the block payload is unknown, but if 
possible
+       // the XQuery and Key SHOULD be verified.
+       ValidateBlockQuery(key *crypto.HashCode, xquery []byte) bool
+
+       // ValidateBlockKey returns true if the block key is the same as the
+       // query key used to access the block.
+       ValidateBlockKey(b Block, key *crypto.HashCode) bool
+
+       // ValidateBlockStoreRequest is used to evaluate a block payload as 
part of
+       // PutMessage and ResultMessage processing.
+       ValidateBlockStoreRequest(b Block) bool
+
+       // SetupResultFilter is used to setup an empty result filter. The 
arguments
+       // are the set of results that must be filtered at the initiator, and a
+       // MUTATOR value which MAY be used to deterministically re-randomize
+       // probabilistic data structures.
+       SetupResultFilter(filterSize int, mutator uint32) ResultFilter
+
+       // ParseResultFilter from binary data
+       ParseResultFilter(data []byte) ResultFilter
+
+       // FilterResult is used to filter results against specific queries. This
+       // function does not check the validity of the block itself or that it
+       // matches the given key, as this must have been checked earlier. Thus,
+       // locally stored blocks from previously observed ResultMessages and
+       // PutMessages use this function to perform filtering based on the 
request
+       // parameters of a particular GET operation. Possible values for the
+       // FilterEvaluationResult are defined above. If the main evaluation 
result
+       // is RF_MORE, the function also returns and updated result filter where
+       // the block is added to the set of filtered replies. An implementation 
is
+       // not expected to actually differentiate between the RF_DUPLICATE and
+       // RF_IRRELEVANT return values: in both cases the block is ignored for
+       // this query.
+       FilterResult(b Block, key *crypto.HashCode, rf ResultFilter, xQuery 
[]byte) int
+}
+
+// BlockHandlers is a map of block query validation implementations
+// for supported block types.
+var BlockHandlers map[enums.BlockType]BlockHandler
+
+// initializer function
+func init() {
+       // create map instance
+       BlockHandlers = make(map[enums.BlockType]BlockHandler)
+
+       // add validation functions
+       BlockHandlers[enums.BLOCK_TYPE_DHT_URL_HELLO] = new(HelloBlockHandler)
+}
diff --git a/src/gnunet/service/dht/blocks/hello.go 
b/src/gnunet/service/dht/blocks/hello.go
index a5ccf8c..c884fd2 100644
--- a/src/gnunet/service/dht/blocks/hello.go
+++ b/src/gnunet/service/dht/blocks/hello.go
@@ -24,6 +24,7 @@ import (
        "encoding/binary"
        "errors"
        "fmt"
+       "gnunet/crypto"
        "gnunet/enums"
        "gnunet/util"
        "net/url"
@@ -32,6 +33,7 @@ import (
 
        "github.com/bfix/gospel/crypto/ed25519"
        "github.com/bfix/gospel/data"
+       "github.com/bfix/gospel/logger"
 )
 
 // HELLO-related errors
@@ -50,12 +52,12 @@ const helloPrefix = "gnunet://hello/"
 // HelloBlock is the DHT-managed block type for HELLO information.
 // It is used to create and parse HELLO URLs.
 // All addresses expire at the same time /this different from HELLO
-// messages (see message.HeeloMsg).
+// messages (see message.HelloMsg).
 type HelloBlock struct {
-       PeerID    *util.PeerID      ``          // peer identifier
-       Signature []byte            `size:"64"` // signature
-       Expire    util.AbsoluteTime ``          // Expiration date
-       AddrBin   []byte            `size:"*"`  // raw address data
+       PeerID    *util.PeerID        ``         // peer identifier
+       Signature *util.PeerSignature ``         // signature
+       Expires   util.AbsoluteTime   ``         // Expiration date
+       AddrBin   []byte              `size:"*"` // raw address data
 
        // transient attributes
        addrs []*util.Address // cooked address data
@@ -64,7 +66,9 @@ type HelloBlock struct {
 // SetAddresses adds a bulk of addresses for this HELLO block.
 func (h *HelloBlock) SetAddresses(a []*util.Address) {
        h.addrs = util.Clone(a)
-       h.finalize()
+       if err := h.finalize(); err != nil {
+               logger.Printf(logger.ERROR, "[HelloBlock.SetAddresses] failed: 
%s", err.Error())
+       }
 }
 
 // Addresses returns the list of addresses
@@ -101,9 +105,10 @@ func ParseHelloURL(u string, checkExpiry bool) (h 
*HelloBlock, err error) {
        h.PeerID = util.NewPeerID(buf)
 
        // (2) parse signature
-       if h.Signature, err = util.DecodeStringToBinary(p[1], 64); err != nil {
+       if buf, err = util.DecodeStringToBinary(p[1], 64); err != nil {
                return
        }
+       h.Signature = util.NewPeerSignature(buf)
 
        // (3) split last element into parts
        q := strings.SplitN(p[2], "?", 2)
@@ -113,8 +118,8 @@ func ParseHelloURL(u string, checkExpiry bool) (h 
*HelloBlock, err error) {
        if exp, err = strconv.ParseUint(q[0], 10, 64); err != nil {
                return
        }
-       h.Expire = util.NewAbsoluteTimeEpoch(exp)
-       if checkExpiry && h.Expire.Expired() {
+       h.Expires = util.NewAbsoluteTimeEpoch(exp)
+       if checkExpiry && h.Expires.Expired() {
                err = ErrHelloExpired
                return
        }
@@ -138,7 +143,9 @@ func ParseHelloURL(u string, checkExpiry bool) (h 
*HelloBlock, err error) {
        }
 
        // (6) generate raw address data so block is complete
-       h.finalize()
+       if err = h.finalize(); err != nil {
+               return
+       }
 
        // check signature
        var ok bool
@@ -190,24 +197,39 @@ func (h *HelloBlock) finalize() (err error) {
        return
 }
 
-/*
-// Message returns the corresponding HELLO message to be sent to peers.
-func (h *HelloBlock) Message() *message.HelloMsg {
-       msg := message.NewHelloMsg(h.PeerID)
-       for _, a := range h.addrs {
-               msg.AddAddress(message.NewHelloAddress(a, h.Expire))
+// Return the block type
+func (h *HelloBlock) Type() uint16 {
+       return uint16(enums.BLOCK_TYPE_DHT_URL_HELLO)
+}
+
+// Data returns the raw block data
+func (h *HelloBlock) Data() []byte {
+       buf, err := data.Marshal(h)
+       if err != nil {
+               logger.Println(logger.ERROR, "[hello] Failed to serialize HELLO 
block: "+err.Error())
+               buf = nil
        }
-       return msg
+       return buf
+}
+
+// Expire returns the block expiration
+func (h *HelloBlock) Expire() util.AbsoluteTime {
+       return h.Expires
+}
+
+// String returns the human-readable representation of a block
+func (h *HelloBlock) String() string {
+       return fmt.Sprintf("HelloBlock{peer=%s,expires=%s,addrs=[%d]}",
+               h.PeerID, h.Expires, len(h.Addresses()))
 }
-*/
 
 // URL returns the HELLO URL for the data.
 func (h *HelloBlock) URL() string {
        u := fmt.Sprintf("%s%s/%s/%d?",
                helloPrefix,
                h.PeerID.String(),
-               util.EncodeBinaryToString(h.Signature),
-               h.Expire.Epoch(),
+               util.EncodeBinaryToString(h.Signature.Data),
+               h.Expires.Epoch(),
        )
        for i, a := range h.addrs {
                if i > 0 {
@@ -221,10 +243,10 @@ func (h *HelloBlock) URL() string {
 }
 
 // Equals returns true if two HELLOs are the same. The expiration
-// timestamp is ignored in the comparision.
+// timestamp is ignored in the comparison.
 func (h *HelloBlock) Equals(g *HelloBlock) bool {
        if !h.PeerID.Equals(g.PeerID) ||
-               !util.Equals(h.Signature, g.Signature) ||
+               !util.Equals(h.Signature.Data, g.Signature.Data) ||
                len(h.addrs) != len(g.addrs) {
                return false
        }
@@ -239,29 +261,23 @@ func (h *HelloBlock) Equals(g *HelloBlock) bool {
 // Verify the integrity of the HELLO data
 func (h *HelloBlock) Verify() (bool, error) {
        // assemble signed data and public key
-       sd := h.signedData()
-       pub := h.PeerID.PublicKey()
-       sig, err := ed25519.NewEdSignatureFromBytes(h.Signature)
+       sd := h.SignedData()
+       pub := ed25519.NewPublicKeyFromBytes(h.PeerID.Data)
+       sig, err := ed25519.NewEdSignatureFromBytes(h.Signature.Data)
        if err != nil {
                return false, err
        }
        return pub.EdVerify(sd, sig)
 }
 
-// Sign the HELLO data with private key
-func (h *HelloBlock) Sign(prv *ed25519.PrivateKey) error {
-       // assemble signed data
-       sd := h.signedData()
-       sig, err := prv.EdSign(sd)
-       if err != nil {
-               return err
-       }
-       h.Signature = sig.Bytes()
+// SetSignature stores a signature in the the HELLO block
+func (h *HelloBlock) SetSignature(sig *util.PeerSignature) error {
+       h.Signature = sig
        return nil
 }
 
-// signedData assembles a data block for sign and verify operations.
-func (h *HelloBlock) signedData() []byte {
+// SignedData assembles a data block for sign and verify operations.
+func (h *HelloBlock) SignedData() []byte {
        // hash address block
        hAddr := sha512.Sum512(h.AddrBin)
        var size uint32 = 80
@@ -269,9 +285,166 @@ func (h *HelloBlock) signedData() []byte {
 
        // assemble signed data
        buf := new(bytes.Buffer)
-       binary.Write(buf, binary.BigEndian, size)
-       binary.Write(buf, binary.BigEndian, purpose)
-       binary.Write(buf, binary.BigEndian, h.Expire.Epoch()*1000000)
-       buf.Write(hAddr[:])
+       var n int
+       err := binary.Write(buf, binary.BigEndian, size)
+       if err == nil {
+               if err = binary.Write(buf, binary.BigEndian, purpose); err == 
nil {
+                       if err = binary.Write(buf, binary.BigEndian, 
h.Expires.Epoch()*1000000); err == nil {
+                               if n, err = buf.Write(hAddr[:]); err == nil {
+                                       if n != len(hAddr[:]) {
+                                               err = errors.New("signed data 
size mismatch")
+                                       }
+                               }
+                       }
+               }
+       }
+       if err != nil {
+               logger.Printf(logger.ERROR, "[HelloBlock.SignedData] failed: 
%s", err.Error())
+       }
        return buf.Bytes()
 }
+
+//----------------------------------------------------------------------
+// HELLO block handler
+//----------------------------------------------------------------------
+
+// HelloBlockHandler methods related to HELLO blocks
+type HelloBlockHandler struct{}
+
+// Parse a block instance from binary data
+func (bh *HelloBlockHandler) ParseBlock(buf []byte) (Block, error) {
+       return ParseHelloFromBytes(buf)
+}
+
+// ValidateHelloBlockQuery validates query parameters for a
+// DHT-GET request for HELLO blocks.
+func (bh *HelloBlockHandler) ValidateBlockQuery(key *crypto.HashCode, xquery 
[]byte) bool {
+       // no xquery parameters allowed.
+       return len(xquery) == 0
+}
+
+// ValidateBlockKey returns true if the block key is the same as the
+// query key used to access the block.
+func (bh *HelloBlockHandler) ValidateBlockKey(b Block, key *crypto.HashCode) 
bool {
+       hb, ok := b.(*HelloBlock)
+       if !ok {
+               return false
+       }
+       // key must be the hash of the peer id
+       bkey := crypto.Hash(hb.PeerID.Bytes())
+       return key.Equals(bkey)
+}
+
+// ValidateBlockStoreRequest is used to evaluate a block payload as part of
+// PutMessage and ResultMessage processing.
+func (bh *HelloBlockHandler) ValidateBlockStoreRequest(b Block) bool {
+       // TODO: verify block payload
+       return true
+}
+
+// SetupResultFilter is used to setup an empty result filter. The arguments
+// are the set of results that must be filtered at the initiator, and a
+// MUTATOR value which MAY be used to deterministically re-randomize
+// probabilistic data structures.
+func (bh *HelloBlockHandler) SetupResultFilter(filterSize int, mutator uint32) 
ResultFilter {
+       return NewHelloResultFilter(filterSize, mutator)
+}
+
+// ParseResultFilter from binary data
+func (bh *HelloBlockHandler) ParseResultFilter(data []byte) ResultFilter {
+       return NewHelloResultFilterFromBytes(data)
+}
+
+// FilterResult is used to filter results against specific queries. This
+// function does not check the validity of the block itself or that it
+// matches the given key, as this must have been checked earlier. Thus,
+// locally stored blocks from previously observed ResultMessages and
+// PutMessages use this function to perform filtering based on the request
+// parameters of a particular GET operation. Possible values for the
+// FilterEvaluationResult are defined above. If the main evaluation result
+// is RF_MORE, the function also returns and updated result filter where
+// the block is added to the set of filtered replies. An implementation is
+// not expected to actually differentiate between the RF_DUPLICATE and
+// RF_IRRELEVANT return values: in both cases the block is ignored for
+// this query.
+func (bh *HelloBlockHandler) FilterResult(b Block, key *crypto.HashCode, rf 
ResultFilter, xQuery []byte) int {
+       if rf.Contains(b) {
+               return RF_DUPLICATE
+       }
+       rf.Add(b)
+       return RF_LAST
+}
+
+//----------------------------------------------------------------------
+
+// HelloResultFilter is a result  filter implementation for HELLO blocks
+type HelloResultFilter struct {
+       bf *BloomFilter
+}
+
+// NewHelloResultFilter initializes an empty resut filter
+func NewHelloResultFilter(filterSize int, mutator uint32) *HelloResultFilter {
+       // HELLO result filters are BloomFilters with a mutator
+       rf := new(HelloResultFilter)
+       rf.bf = NewBloomFilter(filterSize)
+       rf.bf.SetMutator(mutator)
+       return rf
+}
+
+// NewHelloResultFilterFromBytes creates a new result filter from a binary
+// representation: 'data' is the concatenaion 'mutator|bloomfilter'.
+// If 'withMutator' is false, no mutator is used.
+func NewHelloResultFilterFromBytes(data []byte) *HelloResultFilter {
+       //logger.Printf(logger.DBG, "[filter] FromBytes = %d:%s (mutator: 
%v)",len(data), hex.EncodeToString(data), withMutator)
+
+       // handle mutator input
+       mSize := 4
+       rf := new(HelloResultFilter)
+       rf.bf = &BloomFilter{
+               Bits: util.Clone(data[mSize:]),
+       }
+       if mSize > 0 {
+               rf.bf.SetMutator(data[:mSize])
+       }
+       return rf
+}
+
+// Add a HELLO block to th result filter
+func (rf *HelloResultFilter) Add(b Block) {
+       if hb, ok := b.(*HelloBlock); ok {
+               hAddr := sha512.Sum512(hb.AddrBin)
+               rf.bf.Add(hAddr[:])
+       }
+}
+
+// Contains checks if a block is contained in the result filter
+func (rf *HelloResultFilter) Contains(b Block) bool {
+       if hb, ok := b.(*HelloBlock); ok {
+               hAddr := sha512.Sum512(hb.AddrBin)
+               return rf.bf.Contains(hAddr[:])
+       }
+       return false
+}
+
+// Bytes returns a binary representation of a HELLO result filter
+func (rf *HelloResultFilter) Bytes() []byte {
+       return rf.bf.Bytes()
+}
+
+// Compare two HELLO result filters
+func (rf *HelloResultFilter) Compare(t ResultFilter) int {
+       trf, ok := t.(*HelloResultFilter)
+       if !ok {
+               return CMP_DIFFER
+       }
+       return rf.bf.Compare(trf.bf)
+}
+
+// Merge two HELLO result filters
+func (rf *HelloResultFilter) Merge(t ResultFilter) bool {
+       trf, ok := t.(*HelloResultFilter)
+       if !ok {
+               return false
+       }
+       return rf.bf.Merge(trf.bf)
+}
diff --git a/src/gnunet/service/dht/blocks/types.go 
b/src/gnunet/service/dht/blocks/types.go
deleted file mode 100644
index 04edb6e..0000000
--- a/src/gnunet/service/dht/blocks/types.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019-2022 Bernd Fix  >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package blocks
-
-// DHT Block types
-const (
-       DHT_BLOCK_ANY   = 0
-       DHT_BLOCK_HELLO = 7  // Type of a block that contains a HELLO for a peer
-       DHT_BLOCK_GNS   = 11 // Block for storing record data
-)
diff --git a/src/gnunet/service/dht/bloomfilter.go 
b/src/gnunet/service/dht/bloomfilter.go
deleted file mode 100644
index dcfd935..0000000
--- a/src/gnunet/service/dht/bloomfilter.go
+++ /dev/null
@@ -1,123 +0,0 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019-2022 Bernd Fix  >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package dht
-
-import (
-       "bytes"
-       "crypto/sha512"
-       "encoding/binary"
-)
-
-//======================================================================
-// Generic BloomFilter
-//======================================================================
-
-// BloomFilter parameter
-var (
-       bfNumBits = 128
-       bfHash    = sha512.New
-)
-
-// BloomFilter is a space-efficient probabilistic datastructure to test if
-// an element is part of a set of elementsis defined as a string of bits
-// always initially empty.
-type BloomFilter struct {
-       data []byte // filter bits
-       salt []byte // salt for hashing
-}
-
-// NewBloomFilter cretes a new filter using the specified salt. An unused
-// salt is set to nil.
-func NewBloomFilter(salt []byte) *BloomFilter {
-       return &BloomFilter{
-               data: make([]byte, (bfNumBits+7)/8),
-               salt: salt,
-       }
-}
-
-// Add entry (binary representation):
-// When adding an element to the Bloom filter bf using BF-SET(bf,e), each
-// integer n of the mapping M(e) is interpreted as a bit offset n mod L
-// within bf and set to 1.
-func (bf *BloomFilter) Add(e []byte) {
-       for _, idx := range bf.indices(e) {
-               bf.data[idx/8] |= (1 << (idx % 7))
-       }
-}
-
-// Contains returns true if the entry is most likely to be included:
-// When testing if an element may be in the Bloom filter bf using
-// BF-TEST(bf,e), each bit offset n mod L within bf MUST have been set to 1.
-// Otherwise, the element is not considered to be in the Bloom filter.
-func (bf *BloomFilter) Contains(e []byte) bool {
-       for _, idx := range bf.indices(e) {
-               if bf.data[idx/8]&(1<<(idx%7)) == 0 {
-                       return false
-               }
-       }
-       return true
-}
-
-// indices returns the list of bit indices for antry e:
-// The element e is prepended with a salt (pütional) and hashed using SHA-512.
-// The resulting byte string is interpreted as a list of 16 32-bit integers
-// in network byte order.
-func (bf *BloomFilter) indices(e []byte) []int {
-       // hash the entry (with optional salt prepended)
-       hsh := bfHash()
-       if bf.salt != nil {
-               hsh.Write(bf.salt)
-       }
-       hsh.Write(e)
-       h := hsh.Sum(nil)
-
-       // compute the indices for the entry
-       idx := make([]int, len(h)/2)
-       buf := bytes.NewReader(h)
-       for i := range idx {
-               binary.Read(buf, binary.BigEndian, &idx[i])
-       }
-       return idx
-}
-
-//======================================================================
-// BloomFilter for peer addresses
-//======================================================================
-
-// PeerBloomFilter implements specific Add/Contains functions.
-type PeerBloomFilter struct {
-       BloomFilter
-}
-
-// NewPeerBloomFilter creates a new filter for peer addresses.
-func NewPeerBloomFilter() *PeerBloomFilter {
-       return &PeerBloomFilter{
-               BloomFilter: *NewBloomFilter(nil),
-       }
-}
-
-// Add peer address to the filter.
-func (bf *PeerBloomFilter) Add(p *PeerAddress) {
-       bf.BloomFilter.Add(p.addr[:])
-}
-
-// Contains returns true if the peer address is most likely to be included.
-func (bf *PeerBloomFilter) Contains(p *PeerAddress) bool {
-       return bf.BloomFilter.Contains(p.addr[:])
-}
diff --git a/src/gnunet/service/dht/messages.go 
b/src/gnunet/service/dht/messages.go
new file mode 100644
index 0000000..38f0753
--- /dev/null
+++ b/src/gnunet/service/dht/messages.go
@@ -0,0 +1,380 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix  >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package dht
+
+import (
+       "context"
+       "gnunet/enums"
+       "gnunet/message"
+       "gnunet/service/dht/blocks"
+       "gnunet/transport"
+       "gnunet/util"
+
+       "github.com/bfix/gospel/logger"
+       "github.com/bfix/gospel/math"
+)
+
+//----------------------------------------------------------------------
+// Handle DHT messages from the network
+//----------------------------------------------------------------------
+
+// HandleMessage handles a DHT request/response message. Responses are sent
+// to the specified responder.
+func (m *Module) HandleMessage(ctx context.Context, sender *util.PeerID, msgIn 
message.Message, back transport.Responder) bool {
+       // assemble log label
+       label := "dht"
+       if v := ctx.Value("label"); v != nil {
+               if s, _ := v.(string); len(s) > 0 {
+                       label = "dht-" + s
+               }
+       }
+       logger.Printf(logger.INFO, "[%s] message received from %s", label, 
sender)
+
+       // process message
+       switch msg := msgIn.(type) {
+
+       case *message.DHTP2PGetMsg:
+               //--------------------------------------------------------------
+               // DHT-P2P GET
+               //--------------------------------------------------------------
+               logger.Printf(logger.INFO, "[%s] Handling DHT-P2P-GET message", 
label)
+               query := blocks.NewGenericQuery(msg.Query.Bits, 
enums.BlockType(msg.BType), msg.Flags)
+
+               var block blocks.Block
+               var dist *math.Int
+               var err error
+
+               //--------------------------------------------------------------
+               // validate query (based on block type requested)  (9.4.3.1)
+               btype := enums.BlockType(msg.BType)
+               blockHdlr, ok := blocks.BlockHandlers[btype]
+               if ok {
+                       // validate block query
+                       if !blockHdlr.ValidateBlockQuery(msg.Query, msg.XQuery) 
{
+                               logger.Printf(logger.WARN, "[%s] DHT-P2P-GET 
invalid query -- discarded", label)
+                               return false
+                       }
+               } else {
+                       logger.Printf(logger.INFO, "[%s] no handler defined for 
block type %s", label, btype.String())
+                       blockHdlr = nil
+               }
+               //----------------------------------------------------------
+               // check if sender is in peer filter (9.4.3.2)
+               if !msg.PeerFilter.Contains(sender) {
+                       logger.Printf(logger.WARN, "[%s] sender not in peer 
filter", label)
+               }
+               // parse result filter
+               var rf blocks.ResultFilter = new(blocks.PassResultFilter)
+               if msg.ResFilter != nil && len(msg.ResFilter) > 0 {
+                       if blockHdlr != nil {
+                               rf = blockHdlr.ParseResultFilter(msg.ResFilter)
+                       } else {
+                               logger.Printf(logger.WARN, "[%s] unknown result 
filter implementation -- skipped", label)
+                       }
+               }
+               // clone peer filter
+               pf := msg.PeerFilter.Clone()
+
+               //----------------------------------------------------------
+               // check if we need to respond (and how) (9.4.3.3)
+               addr := NewQueryAddress(msg.Query)
+               closest := m.rtable.IsClosestPeer(nil, addr, msg.PeerFilter)
+               demux := int(msg.Flags)&enums.DHT_RO_DEMULTIPLEX_EVERYWHERE != 0
+               approx := int(msg.Flags)&enums.DHT_RO_FIND_APPROXIMATE != 0
+               // actions
+               doResult := closest || (demux && approx)
+               doForward := !closest || (demux && !approx)
+               logger.Printf(logger.DBG, "[dht] GET message: closest=%v, 
demux=%v, approx=%v --> result=%v, forward=%v",
+                       closest, demux, approx, doResult, doForward)
+
+               //------------------------------------------------------
+               // query for a HELLO? (9.4.3.3a)
+               if msg.BType == uint32(enums.BLOCK_TYPE_DHT_URL_HELLO) {
+                       logger.Println(logger.DBG, "[dht] GET message for 
HELLO: check cache")
+                       // find best cached HELLO
+                       block, dist = m.rtable.BestHello(addr, rf)
+               }
+               //--------------------------------------------------------------
+               // find the closest block that has that is not filtered/ by the 
result
+               // filter (in case we did not find an appropriate block in 
cache).
+               if doResult {
+                       // save best-match values from cache
+                       blockCache := block
+                       distCache := dist
+
+                       // query DHT store for exact match  (9.4.3.3c)
+                       if block, err = m.Get(ctx, query); err != nil {
+                               logger.Printf(logger.ERROR, "[%s] Failed to get 
DHT block from storage: %s", label, err.Error())
+                               return true
+                       }
+                       // if block is filtered, skip it
+                       if rf.Contains(block) {
+                               logger.Println(logger.DBG, "[dht] GET message 
for HELLO: matching DHT block is filtered")
+                               block = nil
+                       }
+                       // if we have no exact match, find approximate block if 
requested
+                       if block == nil || approx {
+                               // no exact match: find approximate (9.4.3.3b)
+                               match := func(b blocks.Block) bool {
+                                       return rf.Contains(b)
+                               }
+                               block, dist, err = m.GetApprox(ctx, query, 
match)
+                               if err != nil {
+                                       logger.Printf(logger.ERROR, "[%s] 
Failed to get (approx.) DHT block from storage: %s", label, err.Error())
+                                       return true
+                               }
+                       }
+                       // if we have a block from cache, check if it is better 
than the
+                       // block found in the DHT
+                       if blockCache != nil && distCache.Cmp(dist) < 0 {
+                               block = blockCache
+                       }
+                       // if we have a block, send it as response
+                       if block != nil {
+                               logger.Printf(logger.INFO, "[%s] sending DHT 
result message to caller", label)
+                               if err := m.sendResult(ctx, query, block, 
back); err != nil {
+                                       logger.Printf(logger.ERROR, "[%s] 
Failed to send DHT result message: %s", label, err.Error())
+                               }
+                       }
+               }
+               // check if we need to forward message based on filter result
+               if block != nil && blockHdlr != nil {
+                       switch blockHdlr.FilterResult(block, query.Key(), rf, 
msg.XQuery) {
+                       case blocks.RF_LAST:
+                               // no need for further results
+                       case blocks.RF_MORE:
+                               // possibly more results
+                               doForward = true
+                       case blocks.RF_DUPLICATE, blocks.RF_IRRELEVANT:
+                               // do not forward
+                       }
+               }
+               if doForward {
+                       // build updated GET message
+                       pf.Add(m.core.PeerID())
+                       msgOut := msg.Update(pf, rf, msg.HopCount+1)
+
+                       // forward to number of peers
+                       numForward := m.rtable.ComputeOutDegree(msg.ReplLevel, 
msg.HopCount)
+                       key := NewQueryAddress(query.Key())
+                       for n := 0; n < numForward; n++ {
+                               if p := m.rtable.SelectClosestPeer(key, pf); p 
!= nil {
+                                       // forward message to peer
+                                       logger.Printf(logger.INFO, "[%s] 
forward DHT get message to %s", label, p.String())
+                                       if err := back.Send(ctx, msgOut); err 
!= nil {
+                                               logger.Printf(logger.ERROR, 
"[%s] Failed to forward DHT get message: %s", label, err.Error())
+                                       }
+                                       pf.Add(p.Peer)
+                                       // create open get-forward result 
handler
+                                       rh := NewForwardResultHandler(msg, rf, 
back)
+                                       logger.Printf(logger.INFO, "[%s] 
DHT-P2P-GET task #%d started", label, rh.ID())
+                                       m.reshdlrs.Add(rh)
+                               } else {
+                                       break
+                               }
+                       }
+               }
+               logger.Printf(logger.INFO, "[%s] Handling DHT-P2P-GET message 
done", label)
+
+       case *message.DHTP2PPutMsg:
+               //----------------------------------------------------------
+               // DHT-P2P PUT
+               //----------------------------------------------------------
+               logger.Printf(logger.INFO, "[%s] Handling DHT-P2P-PUT message", 
label)
+
+               //--------------------------------------------------------------
+               // check if request is expired (9.3.2.1)
+               if msg.Expiration.Expired() {
+                       logger.Printf(logger.WARN, "[%s] DHT-P2P-PUT message 
expired (%s)", label, msg.Expiration.String())
+                       return false
+               }
+               btype := enums.BlockType(msg.BType)
+               blockHdlr, ok := blocks.BlockHandlers[btype]
+               if ok { // (9.3.2.2)
+                       // reconstruct block instance
+                       if block, err := blockHdlr.ParseBlock(msg.Block); err 
== nil {
+
+                               // validate block key (9.3.2.3)
+                               if !blockHdlr.ValidateBlockKey(block, msg.Key) {
+                                       logger.Printf(logger.WARN, "[%s] 
DHT-P2P-PUT invalid key -- discarded", label)
+                                       return false
+                               }
+
+                               // validate block payload (9.3.2.4)
+                               if !blockHdlr.ValidateBlockStoreRequest(block) {
+                                       logger.Printf(logger.WARN, "[%s] 
DHT-P2P-PUT invalid payload -- discarded", label)
+                                       return false
+                               }
+                       }
+               } else {
+                       logger.Printf(logger.INFO, "[%s] No validator defined 
for block type %s", label, btype.String())
+                       blockHdlr = nil
+               }
+               //--------------------------------------------------------------
+               // check if sender is in peer filter (9.3.2.5)
+               if !msg.PeerFilter.Contains(sender) {
+                       logger.Printf(logger.WARN, "[%s] Sender not in peer 
filter", label)
+               }
+               //--------------------------------------------------------------
+               // check if route is recorded (9.3.2.6)
+               /*
+                       withPath := msg.Flags&enums.DHT_RO_RECORD_ROUTE != 0
+                       if withPath {
+                               spe := message.NewPathElement(msg.Key, sender, 
p)
+                               m.core.Sign(spe)
+                               msg.AppendPutPath(spe)
+                       }
+               */
+               //--------------------------------------------------------------
+               // verify PUT path (9.3.2.7)
+               if msg.PathL > 0 {
+
+               }
+
+               logger.Printf(logger.INFO, "[%s] Handling DHT-P2P-PUT message 
done", label)
+
+       case *message.DHTP2PResultMsg:
+               //----------------------------------------------------------
+               // DHT-P2P RESULT
+               //----------------------------------------------------------
+               logger.Printf(logger.INFO, "[%s] Handling DHT-P2P-RESULT 
message", label)
+
+               // check task list for handler
+               key := msg.Query.String()
+               handled := false
+               if list, ok := m.reshdlrs.Get(key); ok {
+                       for _, rh := range list {
+                               logger.Printf(logger.DBG, "[%s] Task #%d for 
DHT-P2P-RESULT found", label, rh.ID())
+                               //  handle the message
+                               go rh.Handle(ctx, msg)
+                       }
+                       return true
+               }
+               if !handled {
+                       logger.Printf(logger.WARN, "[%s] DHT-P2P-RESULT not 
processed (no handler)", label)
+               }
+               return handled
+
+       case *message.DHTP2PHelloMsg:
+               //----------------------------------------------------------
+               // DHT-P2P HELLO
+               //----------------------------------------------------------
+               logger.Printf(logger.INFO, "[%s] Handling DHT-P2P-HELLO 
message", label)
+
+               // verify integrity of message
+               if ok, err := msg.Verify(sender); !ok || err != nil {
+                       logger.Println(logger.WARN, "[dht] Received invalid 
DHT_P2P_HELLO message")
+                       if err != nil {
+                               logger.Println(logger.ERROR, "[dht] --> 
"+err.Error())
+                       }
+                       return false
+               }
+               // keep peer addresses in core for transport
+               aList, err := msg.Addresses()
+               if err != nil {
+                       logger.Println(logger.ERROR, "[dht] Failed to parse 
addresses from DHT_P2P_HELLO message")
+                       return false
+               }
+               if newPeer := m.core.Learn(ctx, sender, aList); newPeer {
+                       // we added a previously unknown peer: send a HELLO
+                       var msgOut *message.DHTP2PHelloMsg
+                       if msgOut, err = m.getHello(); err != nil {
+                               return false
+                       }
+                       logger.Printf(logger.INFO, "[dht] Sending HELLO to %s: 
%s", sender, msgOut)
+                       err = m.core.Send(ctx, sender, msgOut)
+                       // no error if the message might have been sent
+                       if err == transport.ErrEndpMaybeSent {
+                               err = nil
+                       }
+               }
+
+               // cache HELLO block if applicable
+               k := sender.String()
+               isNew := true
+               if hb, ok := m.rtable.GetHello(k); ok {
+                       // cache entry exists: is the HELLO message more recent?
+                       _, isNew = hb.Expires.Diff(msg.Expires)
+               }
+               // we need to cache a new(er) HELLO
+               if isNew {
+                       m.rtable.CacheHello(&blocks.HelloBlock{
+                               PeerID:    sender,
+                               Signature: msg.Signature,
+                               Expires:   msg.Expires,
+                               AddrBin:   util.Clone(msg.AddrList),
+                       })
+               }
+
+       //--------------------------------------------------------------
+       // Legacy message types (not implemented)
+       //--------------------------------------------------------------
+
+       case *message.DHTClientPutMsg:
+               //----------------------------------------------------------
+               // DHT PUT
+               //----------------------------------------------------------
+               logger.Printf(logger.INFO, "[%s] Handling DHTClientPut 
message", label)
+
+       case *message.DHTClientGetMsg:
+               //----------------------------------------------------------
+               // DHT GET
+               //----------------------------------------------------------
+               logger.Printf(logger.INFO, "[%s] Handling DHTClientGet 
message", label)
+
+       case *message.DHTClientGetResultsKnownMsg:
+               //----------------------------------------------------------
+               // DHT GET-RESULTS-KNOWN
+               //----------------------------------------------------------
+               logger.Printf(logger.INFO, "[%s] Handling 
DHTClientGetResultsKnown message", label)
+
+       case *message.DHTClientGetStopMsg:
+               //----------------------------------------------------------
+               // DHT GET-STOP
+               //----------------------------------------------------------
+               logger.Printf(logger.INFO, "[%s] Handling DHTClientGetStop 
message", label)
+
+       case *message.DHTClientResultMsg:
+               //----------------------------------------------------------
+               // DHT RESULT
+               //----------------------------------------------------------
+               logger.Printf(logger.INFO, "[%s] Handling DHTClientResult 
message", label)
+
+       default:
+               //----------------------------------------------------------
+               // UNKNOWN message type received
+               //----------------------------------------------------------
+               logger.Printf(logger.ERROR, "[%s] Unhandled message of type 
(%d)\n", label, msgIn.Header().MsgType)
+               return false
+       }
+       return true
+}
+
+// send a result back to caller
+func (m *Module) sendResult(ctx context.Context, query blocks.Query, blk 
blocks.Block, back transport.Responder) error {
+       // assemble result message
+       out := message.NewDHTP2PResultMsg()
+       out.BType = uint32(query.Type())
+       out.Expires = blk.Expire()
+       out.Query = query.Key()
+       out.Block = blk.Data()
+       out.MsgSize += uint16(len(out.Block))
+       // send message
+       return back.Send(ctx, out)
+}
diff --git a/src/gnunet/service/dht/module.go b/src/gnunet/service/dht/module.go
index 612d0ec..ed28827 100644
--- a/src/gnunet/service/dht/module.go
+++ b/src/gnunet/service/dht/module.go
@@ -20,14 +20,20 @@ package dht
 
 import (
        "context"
+       "errors"
        "gnunet/config"
        "gnunet/core"
        "gnunet/message"
        "gnunet/service"
        "gnunet/service/dht/blocks"
+       "gnunet/service/store"
+       "gnunet/transport"
+       "gnunet/util"
+       gmath "math"
        "time"
 
        "github.com/bfix/gospel/logger"
+       "github.com/bfix/gospel/math"
 )
 
 //======================================================================
@@ -42,34 +48,36 @@ import (
 type Module struct {
        service.ModuleImpl
 
-       store service.DHTStore // reference to the block storage mechanism
-       cache service.DHTStore // transient block cache
-       core  *core.Core       // reference to core services
+       store store.DHTStore // reference to the block storage mechanism
+       core  *core.Core     // reference to core services
 
-       rtable *RoutingTable // routing table
+       rtable    *RoutingTable           // routing table
+       lastHello *message.DHTP2PHelloMsg // last own HELLO message used; 
re-create if expired
+       reshdlrs  *ResultHandlerList      // list of open tasks
 }
 
 // NewModule returns a new module instance. It initializes the storage
 // mechanism for persistence.
-func NewModule(ctx context.Context, c *core.Core) (m *Module, err error) {
+func NewModule(ctx context.Context, c *core.Core, cfg *config.DHTConfig) (m 
*Module, err error) {
        // create permanent storage handler
-       var store, cache service.DHTStore
-       if store, err = service.NewDHTStore(config.Cfg.DHT.Storage); err != nil 
{
+       var storage store.DHTStore
+       if storage, err = store.NewDHTStore(cfg.Storage); err != nil {
                return
        }
        // create routing table
-       rt := NewRoutingTable(NewPeerAddress(c.PeerID()))
+       rt := NewRoutingTable(NewPeerAddress(c.PeerID()), cfg.Routing)
 
        // return module instance
        m = &Module{
                ModuleImpl: *service.NewModuleImpl(),
-               store:      store,
-               cache:      cache,
+               store:      storage,
                core:       c,
                rtable:     rt,
+               reshdlrs:   NewResultHandlerList(),
        }
        // register as listener for core events
-       listener := m.Run(ctx, m.event, m.Filter(), 15*time.Minute, m.heartbeat)
+       pulse := time.Duration(cfg.Heartbeat) * time.Second
+       listener := m.Run(ctx, m.event, m.Filter(), pulse, m.heartbeat)
        c.Register("dht", listener)
        return
 }
@@ -78,26 +86,20 @@ func NewModule(ctx context.Context, c *core.Core) (m 
*Module, err error) {
 
 // Get a block from the DHT ["dht:get"]
 func (m *Module) Get(ctx context.Context, query blocks.Query) (block 
blocks.Block, err error) {
+       return m.store.Get(query)
+}
 
-       // check if we have the requested block in cache or permanent storage.
-       block, err = m.cache.Get(query)
-       if err == nil {
-               // yes: we are done
-               return
-       }
-       block, err = m.store.Get(query)
-       if err == nil {
-               // yes: we are done
-               return
-       }
-       // retrieve the block from the DHT
-
-       return nil, nil
+// GetApprox returns the first block not excluded ["dht:getapprox"]
+func (m *Module) GetApprox(ctx context.Context, query blocks.Query, excl 
func(blocks.Block) bool) (block blocks.Block, dist *math.Int, err error) {
+       var d any
+       block, d, err = m.store.GetApprox(query, excl)
+       dist, _ = d.(*math.Int)
+       return
 }
 
 // Put a block into the DHT ["dht:put"]
 func (m *Module) Put(ctx context.Context, key blocks.Query, block 
blocks.Block) error {
-       return nil
+       return m.store.Put(key, block)
 }
 
 //----------------------------------------------------------------------
@@ -110,17 +112,17 @@ func (m *Module) Filter() *core.EventFilter {
        f.AddEvent(core.EV_DISCONNECT)
 
        // messages we are interested in:
-       // (1) DHT messages
+       // (1) DHT_P2P messages
+       f.AddMsgType(message.DHT_P2P_PUT)
+       f.AddMsgType(message.DHT_P2P_GET)
+       f.AddMsgType(message.DHT_P2P_RESULT)
+       f.AddMsgType(message.DHT_P2P_HELLO)
+       // (2) DHT messages (legacy, not implemented)
        f.AddMsgType(message.DHT_CLIENT_GET)
        f.AddMsgType(message.DHT_CLIENT_GET_RESULTS_KNOWN)
        f.AddMsgType(message.DHT_CLIENT_GET_STOP)
        f.AddMsgType(message.DHT_CLIENT_PUT)
        f.AddMsgType(message.DHT_CLIENT_RESULT)
-       // (2) DHT_P2P messages
-       f.AddMsgType(message.DHT_P2P_PUT)
-       f.AddMsgType(message.DHT_P2P_GET)
-       f.AddMsgType(message.DHT_P2P_RESULT)
-       f.AddMsgType(message.DHT_P2P_HELLO)
 
        return f
 }
@@ -143,32 +145,105 @@ func (m *Module) event(ctx context.Context, ev 
*core.Event) {
        // Message received.
        case core.EV_MESSAGE:
                logger.Printf(logger.INFO, "[dht] Message received: %s", 
ev.Msg.String())
-               // process message (if applicable)
-               if m.ProcessFcn != nil {
-                       m.ProcessFcn(ctx, ev.Msg, ev.Resp)
+
+               // check if peer is in routing table (connected peer)
+               if !m.rtable.Contains(NewPeerAddress(ev.Peer)) {
+                       logger.Printf(logger.WARN, "[dht] message %d from 
unregistered peer -- discarded", ev.Msg.Header().MsgType)
+                       return
+               }
+               // process message
+               if !m.HandleMessage(ctx, ev.Peer, ev.Msg, ev.Resp) {
+                       logger.Println(logger.WARN, "[dht] Message NOT 
handled!")
                }
        }
 }
 
 // Heartbeat handler for periodic tasks
 func (m *Module) heartbeat(ctx context.Context) {
-       // update the estimated network size
-       m.rtable.l2nse = m.core.L2NSE()
-
        // run heartbeat for routing table
        m.rtable.heartbeat(ctx)
+
+       // clean-up task list
+       m.reshdlrs.Cleanup()
 }
 
+// Send the currently active HELLO to given network address
+func (m *Module) SendHello(ctx context.Context, addr *util.Address) (err 
error) {
+       // get (buffered) HELLO
+       var msg *message.DHTP2PHelloMsg
+       if msg, err = m.getHello(); err != nil {
+               return
+       }
+       logger.Printf(logger.INFO, "[core] Sending HELLO to %s: %s", 
addr.URI(), msg)
+       return m.core.SendToAddr(ctx, addr, msg)
+}
+
+// get the recent HELLO if it is defined and not expired;
+// create a new HELLO otherwise.
+func (m *Module) getHello() (msg *message.DHTP2PHelloMsg, err error) {
+       if m.lastHello == nil || m.lastHello.Expires.Expired() {
+               // assemble new (signed) HELLO block
+               var addrList []*util.Address
+               if addrList, err = m.core.Addresses(); err != nil {
+                       return
+               }
+               // assemble HELLO data
+               hb := new(blocks.HelloBlock)
+               hb.PeerID = m.core.PeerID()
+               hb.Expires = 
util.NewAbsoluteTime(time.Now().Add(message.HelloAddressExpiration))
+               hb.SetAddresses(addrList)
+
+               // sign HELLO block
+               if err = m.core.Sign(hb); err != nil {
+                       return
+               }
+               // assemble HELLO message
+               msg = message.NewDHTP2PHelloMsg()
+               msg.Expires = hb.Expires
+               msg.SetAddresses(hb.Addresses())
+               if err = m.core.Sign(msg); err != nil {
+                       return
+               }
+
+               // save for later use
+               m.lastHello = msg
+
+               // DEBUG
+               var ok bool
+               if ok, err = msg.Verify(m.core.PeerID()); !ok || err != nil {
+                       if !ok {
+                               err = errors.New("failed to verify own HELLO")
+                       }
+                       logger.Println(logger.ERROR, err.Error())
+                       return
+               }
+               logger.Println(logger.DBG, "[dht] New HELLO: 
"+transport.Dump(msg, "hex"))
+               return
+       }
+       // we have a valid HELLO for re-use.
+       return m.lastHello, nil
+}
+
+//----------------------------------------------------------------------
+// Inter-module linkage helpers
 //----------------------------------------------------------------------
 
 // Export functions
 func (m *Module) Export(fcn map[string]any) {
        // add exported functions from module
        fcn["dht:get"] = m.Get
+       fcn["dht:getapprox"] = m.GetApprox
        fcn["dht:put"] = m.Put
 }
 
 // Import functions
-func (m *Module) Import(fcm map[string]any) {
-       // nothing to import now.
+func (m *Module) Import(fcn map[string]any) {
+       // nothing to import for now.
+}
+
+//----------------------------------------------------------------------
+
+// SetNetworkSize sets a fixed number of peers in the network
+func (m *Module) SetNetworkSize(numPeers int) {
+       m.rtable.l2nse = gmath.Log2(float64(numPeers))
 }
diff --git a/src/gnunet/service/dht/resulthandler.go 
b/src/gnunet/service/dht/resulthandler.go
new file mode 100644
index 0000000..6564abd
--- /dev/null
+++ b/src/gnunet/service/dht/resulthandler.go
@@ -0,0 +1,351 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix  >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package dht
+
+import (
+       "bytes"
+       "context"
+       "gnunet/crypto"
+       "gnunet/message"
+       "gnunet/service/dht/blocks"
+       "gnunet/transport"
+       "gnunet/util"
+       "time"
+
+       "github.com/bfix/gospel/logger"
+)
+
+//======================================================================
+// DHT GET requests send to neighbours result in DHT RESULT messages
+// being returned that need to be handled. The sequence of incoming
+// results is undetermined and usually not terminated (that is, there
+// is no mechanism to determine the end of results).
+// ResultHandlers handle DHT RESULT messages. The appropriate handler
+// is selected by the DHT query/store key associated with a GET/RESULT
+// message; there can be multiple handlers for the same key (serving
+// different GET requests and/or differnent originators).
+//======================================================================
+
+// ResultHandler interface
+type ResultHandler interface {
+
+       // ID returna the handler id
+       ID() int
+
+       // Done returns true if handler can be removed
+       Done() bool
+
+       // Key returns the query/store key as string
+       Key() string
+
+       // Compare two result handlers
+       Compare(ResultHandler) int
+
+       // Merge two result handlers that are the same except for result filter
+       Merge(ResultHandler) bool
+
+       // Handle result message
+       Handle(context.Context, *message.DHTP2PResultMsg) bool
+}
+
+// Compare return values
+//nolint:stylecheck // allow non-camel-case in constants
+const (
+       RHC_SAME   = blocks.CMP_SAME   // the two result handlers are the same
+       RHC_MERGE  = blocks.CMP_MERGE  // the two result handlers can be merged
+       RHC_DIFFER = blocks.CMP_DIFFER // the two result handlers are different
+       RHC_SIBL   = blocks.CMP_1      // the two result handlers are siblings
+)
+
+//----------------------------------------------------------------------
+
+// Generic (shared) result handler data structure
+type GenericResultHandler struct {
+       id        int                 // task identifier
+       key       *crypto.HashCode    // GET query key
+       btype     uint32              // content type of the payload
+       flags     uint16              // processing flags
+       resFilter blocks.ResultFilter // result filter
+       xQuery    []byte              // extended query
+       started   util.AbsoluteTime   // Timestamp of session start
+       active    bool                // is the task active?
+}
+
+// NewGenericResultHandler creates an instance from a DHT-GET message and a
+// result filter instance.
+func NewGenericResultHandler(msg *message.DHTP2PGetMsg, rf 
blocks.ResultFilter) *GenericResultHandler {
+       return &GenericResultHandler{
+               id:        util.NextID(),
+               key:       msg.Query.Clone(),
+               btype:     msg.BType,
+               flags:     msg.Flags,
+               resFilter: rf,
+               xQuery:    util.Clone(msg.XQuery),
+               started:   util.AbsoluteTimeNow(),
+               active:    true,
+       }
+}
+
+// ID returns the result handler identifier
+func (t *GenericResultHandler) ID() int {
+       return t.id
+}
+
+// Key returns the key string
+func (t *GenericResultHandler) Key() string {
+       return t.key.String()
+}
+
+// Done returns true if the result handler is no longer active.
+func (t *GenericResultHandler) Done() bool {
+       return !t.active || t.started.Add(time.Hour).Expired()
+}
+
+// Compare two handlers
+func (t *GenericResultHandler) Compare(h *GenericResultHandler) int {
+       if t.key.Equals(h.key) ||
+               t.btype != h.btype ||
+               t.flags != h.flags ||
+               !bytes.Equal(t.xQuery, h.xQuery) {
+               return RHC_DIFFER
+       }
+       return t.resFilter.Compare(h.resFilter)
+}
+
+// Merge two result handlers that are the same except for result filter
+func (t *GenericResultHandler) Merge(a *GenericResultHandler) bool {
+       return t.resFilter.Merge(a.resFilter)
+}
+
+//----------------------------------------------------------------------
+// Result handler for forwarded GET requests
+//----------------------------------------------------------------------
+
+// ForwardResultHandler data structure
+type ForwardResultHandler struct {
+       GenericResultHandler
+
+       resp transport.Responder // responder for communicating back to 
originator
+}
+
+// NewForwardResultHandler derived from DHT-GET message
+func NewForwardResultHandler(msgIn message.Message, rf blocks.ResultFilter, 
back transport.Responder) *ForwardResultHandler {
+       // check for correct message type and handler function
+       msg, ok := msgIn.(*message.DHTP2PGetMsg)
+       if ok {
+               return &ForwardResultHandler{
+                       GenericResultHandler: *NewGenericResultHandler(msg, rf),
+                       resp:                 back,
+               }
+       }
+       return nil
+}
+
+// Handle incoming DHT-P2P-RESULT message
+func (t *ForwardResultHandler) Handle(ctx context.Context, msg 
*message.DHTP2PResultMsg) bool {
+       // send result message back to originator (result forwarding).
+       logger.Printf(logger.INFO, "[dht-task-%d] sending result back to 
originator", t.id)
+       if err := t.resp.Send(ctx, msg); err != nil && err != 
transport.ErrEndpMaybeSent {
+               logger.Printf(logger.ERROR, "[dht-task-%d] sending result back 
to originator failed: %s", t.id, err.Error())
+               return false
+       }
+       return true
+}
+
+// Compare two forward result filters
+func (t *ForwardResultHandler) Compare(h ResultHandler) int {
+       // check for correct handler type
+       ht, ok := h.(*ForwardResultHandler)
+       if !ok {
+               return RHC_DIFFER
+       }
+       // check for same recipient
+       if ht.resp.Receiver() != t.resp.Receiver() {
+               return RHC_DIFFER
+       }
+       // check generic handler data
+       return t.GenericResultHandler.Compare(&ht.GenericResultHandler)
+}
+
+// Merge two forward result handlers
+func (t *ForwardResultHandler) Merge(h ResultHandler) bool {
+       // check for correct handler type
+       ht, ok := h.(*ForwardResultHandler)
+       if !ok {
+               return false
+       }
+       return t.GenericResultHandler.Merge(&ht.GenericResultHandler)
+}
+
+//----------------------------------------------------------------------
+// Result handler for locally-initiated GET requests:
+//
+// Before sending the GET request a handler is added for the request:
+//
+//    rc := make(chan any)
+//    myRH := NewDirectResultHandler(msg, rf, MyCustomHandler, rc)
+//    m.reshdlrs.Add(myRH)
+//
+// If a matching response is received, the custom handler is executed
+// in a separate go-routine. A custom handler returns a result (or error) on
+// a back channel and should be context-sensitive (termination).
+//
+// If an asynchronous behaviour is required, use 'ret := <-rc' to wait for
+// completion; synchronous execution does not require 'rc' (which can be set
+// to nil).
+//----------------------------------------------------------------------
+
+// ResultHandlerFcn is the function prototype for custom handlers:
+type ResultHandlerFcn func(context.Context, *message.DHTP2PResultMsg, chan<- 
any) bool
+
+// DirectResultHandler for local DHT-P2P-GET requests
+type DirectResultHandler struct {
+       GenericResultHandler
+
+       hdlr ResultHandlerFcn // Hdlr is a custom message handler
+       rc   chan any         // handler result channel
+}
+
+// NewDirectResultHandler create a new GET handler instance
+func NewDirectResultHandler(msgIn message.Message, rf blocks.ResultFilter, 
hdlr ResultHandlerFcn, rc chan any) *DirectResultHandler {
+       // check for correct message type and handler function
+       msg, ok := msgIn.(*message.DHTP2PGetMsg)
+       if ok {
+               return &DirectResultHandler{
+                       GenericResultHandler: *NewGenericResultHandler(msg, rf),
+                       hdlr:                 hdlr,
+                       rc:                   rc,
+               }
+       }
+       return nil
+}
+
+// Handle incoming DHT-P2P-RESULT message
+func (t *DirectResultHandler) Handle(ctx context.Context, msg 
*message.DHTP2PResultMsg) bool {
+       // check for correct message type and handler function
+       if t.hdlr != nil {
+               logger.Printf(logger.INFO, "[dht-task-%d] handling result 
message", t.id)
+               return t.hdlr(ctx, msg, t.rc)
+       }
+       return false
+}
+
+// Compare two direct result handlers
+func (t *DirectResultHandler) Compare(h ResultHandler) int {
+       // check for correct handler type
+       ht, ok := h.(*DirectResultHandler)
+       if !ok {
+               return RHC_DIFFER
+       }
+       // check generic handler data
+       return t.GenericResultHandler.Compare(&ht.GenericResultHandler)
+}
+
+// Merge two direct result handlers
+func (t *DirectResultHandler) Merge(h ResultHandler) bool {
+       // check for correct handler type
+       ht, ok := h.(*DirectResultHandler)
+       if !ok {
+               return false
+       }
+       // check generic handler data
+       return t.GenericResultHandler.Merge(&ht.GenericResultHandler)
+}
+
+//----------------------------------------------------------------------
+// Handler list for book-keeping:
+// * For each query/store key there can be multiple result handlers.
+//----------------------------------------------------------------------
+
+// ResultHandlerList holds the currently active tasks
+type ResultHandlerList struct {
+       list *util.Map[string, []ResultHandler] // map of handlers
+}
+
+// NewResultHandlerList creates a new task list
+func NewResultHandlerList() *ResultHandlerList {
+       return &ResultHandlerList{
+               list: util.NewMap[string, []ResultHandler](),
+       }
+}
+
+// Add handler to list
+func (t *ResultHandlerList) Add(hdlr ResultHandler) bool {
+       // get current list of handlers for key
+       key := hdlr.Key()
+       list, ok := t.list.Get(key)
+       modified := false
+       if !ok {
+               list = make([]ResultHandler, 0)
+       } else {
+               // check if handler is already available
+       loop:
+               for i, h := range list {
+                       switch h.Compare(hdlr) {
+                       case RHC_SAME:
+                               // already in list; no need to add again
+                               return false
+                       case RHC_MERGE:
+                               // merge the two result handlers
+                               modified = h.Merge(hdlr) || modified
+                               break loop
+                       case RHC_SIBL:
+                               // replace the old handler with the new one
+                               list[i] = hdlr
+                               modified = true
+                               break loop
+                       case RHC_DIFFER:
+                               // try next
+                       }
+               }
+       }
+       if !modified {
+               // append new handler to list
+               list = append(list, hdlr)
+       }
+       t.list.Put(key, list)
+       return true
+}
+
+// Get handler list for given key
+func (t *ResultHandlerList) Get(key string) ([]ResultHandler, bool) {
+       return t.list.Get(key)
+}
+
+// Cleanup removes expired tasks from list
+func (t *ResultHandlerList) Cleanup() {
+       err := t.list.ProcessRange(func(key string, list []ResultHandler) error 
{
+               var newList []ResultHandler
+               changed := false
+               for _, rh := range list {
+                       if !rh.Done() {
+                               newList = append(newList, rh)
+                       } else {
+                               changed = true
+                       }
+               }
+               if changed {
+                       t.list.Put(key, newList)
+               }
+               return nil
+       }, false)
+       if err != nil {
+               logger.Printf(logger.ERROR, "[ResultHandlerList] clean-up 
error: %s", err.Error())
+       }
+}
diff --git a/src/gnunet/service/dht/routingtable.go 
b/src/gnunet/service/dht/routingtable.go
index 2933e6a..fe9f956 100644
--- a/src/gnunet/service/dht/routingtable.go
+++ b/src/gnunet/service/dht/routingtable.go
@@ -21,10 +21,11 @@ package dht
 import (
        "bytes"
        "context"
-       "crypto/sha512"
        "encoding/hex"
+       "gnunet/config"
+       "gnunet/crypto"
+       "gnunet/service/dht/blocks"
        "gnunet/util"
-       "math/rand"
        "sync"
        "time"
 
@@ -32,61 +33,63 @@ import (
        "github.com/bfix/gospel/math"
 )
 
-var (
-       // routing table hash function: defines number of
-       // buckets and size of peer addresses
-       rtHash = sha512.New
-)
-
-// Routing table contants (adjust with changing hash function)
+// Routing table constants
 const (
-       numBuckets = 512 // number of bits of hash function result
-       numK       = 20  // number of entries per k-bucket
-       sizeAddr   = 64  // size of peer address in bytes
+       numK = 20 // number of entries per k-bucket
 )
 
 //======================================================================
+// Peer address
 //======================================================================
 
 // PeerAddress is the identifier for a peer in the DHT network.
 // It is the SHA-512 hash of the PeerID (public Ed25519 key).
 type PeerAddress struct {
-       addr      [sizeAddr]byte    // hash value as bytes
-       connected bool              // is peer connected?
-       lastSeen  util.AbsoluteTime // time the peer was last seen
-       lastUsed  util.AbsoluteTime // time the peer was last used
+       Peer     *util.PeerID      // peer identifier
+       Key      *crypto.HashCode  // address key is a sha512 hash
+       lastSeen util.AbsoluteTime // time the peer was last seen
+       lastUsed util.AbsoluteTime // time the peer was last used
 }
 
 // NewPeerAddress returns the DHT address of a peer.
 func NewPeerAddress(peer *util.PeerID) *PeerAddress {
-       r := new(PeerAddress)
-       h := rtHash()
-       h.Write(peer.Key)
-       copy(r.addr[:], h.Sum(nil))
-       r.lastSeen = util.AbsoluteTimeNow()
-       r.lastUsed = util.AbsoluteTimeNow()
-       return r
+       return &PeerAddress{
+               Peer:     peer,
+               Key:      crypto.Hash(peer.Data),
+               lastSeen: util.AbsoluteTimeNow(),
+               lastUsed: util.AbsoluteTimeNow(),
+       }
+}
+
+// NewQueryAddress returns a wrapped peer address for a query key
+func NewQueryAddress(key *crypto.HashCode) *PeerAddress {
+       return &PeerAddress{
+               Peer:     nil,
+               Key:      crypto.NewHashCode(key.Bits),
+               lastSeen: util.AbsoluteTimeNow(),
+               lastUsed: util.AbsoluteTimeNow(),
+       }
 }
 
 // String returns a human-readble representation of an address.
 func (addr *PeerAddress) String() string {
-       return hex.EncodeToString(addr.addr[:])
+       return hex.EncodeToString(addr.Key.Bits)
 }
 
 // Equals returns true if two peer addresses are the same.
 func (addr *PeerAddress) Equals(p *PeerAddress) bool {
-       return bytes.Equal(addr.addr[:], p.addr[:])
+       return bytes.Equal(addr.Key.Bits, p.Key.Bits)
 }
 
 // Distance between two addresses: returns a distance value and a
 // bucket index (smaller index = less distant).
 func (addr *PeerAddress) Distance(p *PeerAddress) (*math.Int, int) {
-       var d PeerAddress
-       for i := range d.addr {
-               d.addr[i] = addr.addr[i] ^ p.addr[i]
+       d := make([]byte, 64)
+       for i := range d {
+               d[i] = addr.Key.Bits[i] ^ p.Key.Bits[i]
        }
-       r := math.NewIntFromBytes(d.addr[:])
-       return r, numBuckets - r.BitLen()
+       r := math.NewIntFromBytes(d)
+       return r, 512 - r.BitLen()
 }
 
 //======================================================================
@@ -98,23 +101,28 @@ func (addr *PeerAddress) Distance(p *PeerAddress) 
(*math.Int, int) {
 // distance to the reference address, so smaller index means
 // "nearer" to the reference address.
 type RoutingTable struct {
-       ref       *PeerAddress              // reference address for distance
-       buckets   []*Bucket                 // list of buckets
-       list      map[*PeerAddress]struct{} // keep list of peers
-       mtx       sync.RWMutex              // lock for write operations
-       l2nse     float64                   // log2 of estimated network size
-       inProcess bool                      // flag if Process() is running
+       sync.RWMutex
+
+       ref        *PeerAddress                          // reference address 
for distance
+       buckets    []*Bucket                             // list of buckets
+       list       *util.Map[string, *PeerAddress]       // keep list of peers
+       l2nse      float64                               // log2 of estimated 
network size
+       inProcess  bool                                  // flag if Process() 
is running
+       cfg        *config.RoutingConfig                 // routing parameters
+       helloCache *util.Map[string, *blocks.HelloBlock] // HELLO block cache
 }
 
 // NewRoutingTable creates a new routing table for the reference address.
-func NewRoutingTable(ref *PeerAddress) *RoutingTable {
+func NewRoutingTable(ref *PeerAddress, cfg *config.RoutingConfig) 
*RoutingTable {
        // create routing table
        rt := &RoutingTable{
-               ref:       ref,
-               list:      make(map[*PeerAddress]struct{}),
-               buckets:   make([]*Bucket, numBuckets),
-               l2nse:     0.,
-               inProcess: false,
+               ref:        ref,
+               list:       util.NewMap[string, *PeerAddress](),
+               buckets:    make([]*Bucket, 512),
+               l2nse:      -1,
+               inProcess:  false,
+               cfg:        cfg,
+               helloCache: util.NewMap[string, *blocks.HelloBlock](),
        }
        // fill buckets
        for i := range rt.buckets {
@@ -130,41 +138,69 @@ func NewRoutingTable(ref *PeerAddress) *RoutingTable {
 // Add new peer address to routing table.
 // Returns true if the entry was added, false otherwise.
 func (rt *RoutingTable) Add(p *PeerAddress) bool {
-       // ensure one write and no readers
-       rt.lock(false)
-       defer rt.unlock(false)
+       k := p.String()
+       logger.Printf(logger.DBG, "[RT] Add(%s)", k)
 
        // check if peer is already known
-       if _, ok := rt.list[p]; ok {
+       if px, ok := rt.list.Get(k); ok {
+               logger.Println(logger.DBG, "[RT] --> already known")
+               px.lastSeen = util.AbsoluteTimeNow()
                return false
        }
 
        // compute distance (bucket index) and insert address.
        _, idx := p.Distance(rt.ref)
        if rt.buckets[idx].Add(p) {
-               rt.list[p] = struct{}{}
+               logger.Println(logger.DBG, "[RT] --> entry added")
+               p.lastUsed = util.AbsoluteTimeNow()
+               rt.list.Put(k, p)
                return true
        }
        // Full bucket: we did not add the address to the routing table.
+       logger.Println(logger.DBG, "[RT] --> bucket full -- discarded")
        return false
 }
 
 // Remove peer address from routing table.
 // Returns true if the entry was removed, false otherwise.
 func (rt *RoutingTable) Remove(p *PeerAddress) bool {
-       // ensure one write and no readers
-       rt.lock(false)
-       defer rt.unlock(false)
+       k := p.String()
+       logger.Printf(logger.DBG, "[RT] Remove(%s)", k)
 
        // compute distance (bucket index) and remove entry from bucket
+       rc := false
        _, idx := p.Distance(rt.ref)
        if rt.buckets[idx].Remove(p) {
-               delete(rt.list, p)
-               return true
+               logger.Println(logger.DBG, "[RT] --> entry removed from bucket 
and internal lists")
+               rc = true
+       } else {
+               // remove from internal list
+               logger.Println(logger.DBG, "[RT] --> entry removed from 
internal lists only")
        }
-       // remove from internal list
-       delete(rt.list, p)
-       return false
+       rt.list.Delete(k)
+       // delete from HELLO cache
+       rt.helloCache.Delete(p.Peer.String())
+       return rc
+}
+
+// Contains checks if a peer is available in the routing table
+func (rt *RoutingTable) Contains(p *PeerAddress) bool {
+       k := p.String()
+       logger.Printf(logger.DBG, "[RT] Contains(%s)?", k)
+
+       // check for peer in internal list
+       px, ok := rt.list.Get(k)
+       if !ok {
+               logger.Println(logger.DBG, "[RT] --> NOT found in current 
list:")
+               _ = rt.list.ProcessRange(func(key string, val *PeerAddress) 
error {
+                       logger.Printf(logger.DBG, "[RT]    * %s", val)
+                       return nil
+               }, true)
+       } else {
+               logger.Println(logger.DBG, "[RT] --> found in current list")
+               px.lastSeen = util.AbsoluteTimeNow()
+       }
+       return ok
 }
 
 //----------------------------------------------------------------------
@@ -186,51 +222,53 @@ func (rt *RoutingTable) Process(f func() error, readonly 
bool) error {
 // Routing functions
 //----------------------------------------------------------------------
 
-// SelectClosestPeer for a given peer address and bloomfilter.
-func (rt *RoutingTable) SelectClosestPeer(p *PeerAddress, bf *PeerBloomFilter) 
(n *PeerAddress) {
+// SelectClosestPeer for a given peer address and peer filter.
+func (rt *RoutingTable) SelectClosestPeer(p *PeerAddress, pf 
*blocks.PeerFilter) (n *PeerAddress) {
        // no writer allowed
-       rt.mtx.RLock()
-       defer rt.mtx.RUnlock()
+       rt.RLock()
+       defer rt.RUnlock()
 
-       // find closest address
+       // find closest peer in routing table
        var dist *math.Int
        for _, b := range rt.buckets {
-               if k, d := b.SelectClosestPeer(p, bf); n == nil || (d != nil && 
d.Cmp(dist) < 0) {
+               if k, d := b.SelectClosestPeer(p, pf); n == nil || (d != nil && 
d.Cmp(dist) < 0) {
                        dist = d
                        n = k
                }
        }
        // mark peer as used
-       n.lastUsed = util.AbsoluteTimeNow()
+       if n != nil {
+               n.lastUsed = util.AbsoluteTimeNow()
+       }
        return
 }
 
 // SelectRandomPeer returns a random address from table (that is not
 // included in the bloomfilter)
-func (rt *RoutingTable) SelectRandomPeer(bf *PeerBloomFilter) *PeerAddress {
+func (rt *RoutingTable) SelectRandomPeer(pf *blocks.PeerFilter) (p 
*PeerAddress) {
        // no writer allowed
-       rt.mtx.RLock()
-       defer rt.mtx.RUnlock()
+       rt.RLock()
+       defer rt.RUnlock()
 
        // select random entry from list
-       if size := len(rt.list); size > 0 {
-               idx := rand.Intn(size)
-               for k := range rt.list {
-                       if idx == 0 {
-                               // mark peer as used
-                               k.lastUsed = util.AbsoluteTimeNow()
-                               return k
-                       }
-                       idx--
+       var ok bool
+       for {
+               if _, p, ok = rt.list.GetRandom(); !ok {
+                       return nil
+               }
+               if !pf.Contains(p.Peer) {
+                       break
                }
        }
-       return nil
+       // mark peer as used
+       p.lastUsed = util.AbsoluteTimeNow()
+       return
 }
 
 // SelectPeer selects a neighbor depending on the number of hops parameter.
 // If hops < NSE this function MUST return SelectRandomPeer() and
 // SelectClosestpeer() otherwise.
-func (rt *RoutingTable) SelectPeer(p *PeerAddress, hops int, bf 
*PeerBloomFilter) *PeerAddress {
+func (rt *RoutingTable) SelectPeer(p *PeerAddress, hops int, bf 
*blocks.PeerFilter) *PeerAddress {
        if float64(hops) < rt.l2nse {
                return rt.SelectRandomPeer(bf)
        }
@@ -238,9 +276,24 @@ func (rt *RoutingTable) SelectPeer(p *PeerAddress, hops 
int, bf *PeerBloomFilter
 }
 
 // IsClosestPeer returns true if p is the closest peer for k. Peers with a
-// positive test in the Bloom filter  are not considered.
-func (rt *RoutingTable) IsClosestPeer(p, k *PeerAddress, bf *PeerBloomFilter) 
bool {
-       n := rt.SelectClosestPeer(k, bf)
+// positive test in the Bloom filter are not considered. If p is nil, our
+// reference address is used.
+func (rt *RoutingTable) IsClosestPeer(p, k *PeerAddress, pf 
*blocks.PeerFilter) bool {
+       // get closest peer in routing table
+       n := rt.SelectClosestPeer(k, pf)
+       // check SELF?
+       if p == nil {
+               // if no peer in routing table found
+               if n == nil {
+                       // local peer is closest
+                       return true
+               }
+               // check if local distance is smaller than for best peer in 
routing table
+               d0, _ := n.Distance(k)
+               d1, _ := rt.ref.Distance(k)
+               return d1.Cmp(d0) < 0
+       }
+       // check if p is closest peer
        return n.Equals(p)
 }
 
@@ -248,7 +301,7 @@ func (rt *RoutingTable) IsClosestPeer(p, k *PeerAddress, bf 
*PeerBloomFilter) bo
 // The arguments are the desired replication level, the hop count of the 
message so far,
 // and the base-2 logarithm of the current network size estimate (L2NSE) as 
provided by the
 // underlay. The result is the non-negative number of next hops to select.
-func (rt *RoutingTable) ComputeOutDegree(repl, hop int) int {
+func (rt *RoutingTable) ComputeOutDegree(repl, hop uint16) int {
        hf := float64(hop)
        if hf > 4*rt.l2nse {
                return 0
@@ -271,22 +324,62 @@ func (rt *RoutingTable) ComputeOutDegree(repl, hop int) 
int {
 func (rt *RoutingTable) heartbeat(ctx context.Context) {
 
        // check for dead or expired peers
-       timeout := util.NewRelativeTime(3 * time.Hour)
+       logger.Println(logger.DBG, "[dht] RT heartbeat...")
+       timeout := util.NewRelativeTime(time.Duration(rt.cfg.PeerTTL) * 
time.Second)
        if err := rt.Process(func() error {
-               for addr := range rt.list {
-                       if addr.connected {
-                               continue
-                       }
+               return rt.list.ProcessRange(func(k string, p *PeerAddress) 
error {
                        // check if we can/need to drop a peer
-                       drop := timeout.Compare(addr.lastSeen.Elapsed()) < 0
-                       if drop || timeout.Compare(addr.lastUsed.Elapsed()) < 0 
{
-                               rt.Remove(addr)
+                       drop := timeout.Compare(p.lastSeen.Elapsed()) < 0
+                       if drop || timeout.Compare(p.lastUsed.Elapsed()) < 0 {
+                               logger.Printf(logger.DBG, "[RT] removing %v: 
%v, %v", p, p.lastSeen.Elapsed(), p.lastUsed.Elapsed())
+                               rt.Remove(p)
                        }
-               }
-               return nil
+                       return nil
+               }, false)
        }, false); err != nil {
-               logger.Println(logger.ERROR, "[dht] RT heartbeat: "+err.Error())
+               logger.Println(logger.ERROR, "[dht] RT heartbeat failed: 
"+err.Error())
        }
+
+       // drop expired entries from the HELLO cache
+       _ = rt.helloCache.ProcessRange(func(key string, val *blocks.HelloBlock) 
error {
+               if val.Expires.Expired() {
+                       rt.helloCache.Delete(key)
+               }
+               return nil
+       }, false)
+
+       // update the estimated network size
+       // rt.l2nse = ...
+}
+
+//----------------------------------------------------------------------
+
+func (rt *RoutingTable) BestHello(addr *PeerAddress, rf blocks.ResultFilter) 
(hb *blocks.HelloBlock, dist *math.Int) {
+       // iterate over cached HELLOs to find (best) match first
+       _ = rt.helloCache.ProcessRange(func(key string, val *blocks.HelloBlock) 
error {
+               // check if block is excluded by result filter
+               if !rf.Contains(val) {
+                       // check for better match
+                       p := NewPeerAddress(val.PeerID)
+                       d, _ := addr.Distance(p)
+                       if hb == nil || d.Cmp(dist) < 0 {
+                               hb = val
+                               dist = d
+                       }
+               }
+               return nil
+       }, true)
+       return
+}
+
+// CacheHello adds a HELLO block to the list of cached entries.
+func (rt *RoutingTable) CacheHello(hb *blocks.HelloBlock) {
+       rt.helloCache.Put(hb.PeerID.String(), hb)
+}
+
+// GetHello returns a HELLO block for key k (if available)
+func (rt *RoutingTable) GetHello(k string) (*blocks.HelloBlock, bool) {
+       return rt.helloCache.Get(k)
 }
 
 //----------------------------------------------------------------------
@@ -295,9 +388,9 @@ func (rt *RoutingTable) heartbeat(ctx context.Context) {
 func (rt *RoutingTable) lock(readonly bool) {
        if !rt.inProcess {
                if readonly {
-                       rt.mtx.RLock()
+                       rt.RLock()
                } else {
-                       rt.mtx.Lock()
+                       rt.Lock()
                }
        }
 }
@@ -306,9 +399,9 @@ func (rt *RoutingTable) lock(readonly bool) {
 func (rt *RoutingTable) unlock(readonly bool) {
        if !rt.inProcess {
                if readonly {
-                       rt.mtx.RUnlock()
+                       rt.RUnlock()
                } else {
-                       rt.mtx.Unlock()
+                       rt.Unlock()
                }
        }
 }
@@ -319,8 +412,9 @@ func (rt *RoutingTable) unlock(readonly bool) {
 
 // Bucket holds peer entries with approx. same distance from node
 type Bucket struct {
-       list   []*PeerAddress
-       rwlock sync.RWMutex
+       sync.RWMutex
+
+       list []*PeerAddress // list of peer addresses in bucket.
 }
 
 // NewBucket creates a new entry list of given size
@@ -334,8 +428,8 @@ func NewBucket(n int) *Bucket {
 // Returns true if entry is added, false otherwise.
 func (b *Bucket) Add(p *PeerAddress) bool {
        // only one writer and no readers
-       b.rwlock.Lock()
-       defer b.rwlock.Unlock()
+       b.Lock()
+       defer b.Unlock()
 
        // check for free space in bucket
        if len(b.list) < numK {
@@ -343,6 +437,7 @@ func (b *Bucket) Add(p *PeerAddress) bool {
                b.list = append(b.list, p)
                return true
        }
+       // full bucket: no further additions
        return false
 }
 
@@ -350,8 +445,8 @@ func (b *Bucket) Add(p *PeerAddress) bool {
 // Returns true if entry is removed (found), false otherwise.
 func (b *Bucket) Remove(p *PeerAddress) bool {
        // only one writer and no readers
-       b.rwlock.Lock()
-       defer b.rwlock.Unlock()
+       b.Lock()
+       defer b.Unlock()
 
        for i, pe := range b.list {
                if pe.Equals(p) {
@@ -365,14 +460,14 @@ func (b *Bucket) Remove(p *PeerAddress) bool {
 
 // SelectClosestPeer returns the entry with minimal distance to the given
 // peer address; entries included in the bloom flter are ignored.
-func (b *Bucket) SelectClosestPeer(p *PeerAddress, bf *PeerBloomFilter) (n 
*PeerAddress, dist *math.Int) {
+func (b *Bucket) SelectClosestPeer(p *PeerAddress, pf *blocks.PeerFilter) (n 
*PeerAddress, dist *math.Int) {
        // no writer allowed
-       b.rwlock.RLock()
-       defer b.rwlock.RUnlock()
+       b.RLock()
+       defer b.RUnlock()
 
        for _, addr := range b.list {
                // skip addresses in bloomfilter
-               if bf.Contains(addr) {
+               if pf.Contains(addr.Peer) {
                        continue
                }
                // check for shorter distance
diff --git a/src/gnunet/service/dht/routingtable_test.go 
b/src/gnunet/service/dht/routingtable_test.go
index 33c4b7f..16f39de 100644
--- a/src/gnunet/service/dht/routingtable_test.go
+++ b/src/gnunet/service/dht/routingtable_test.go
@@ -19,8 +19,11 @@
 package dht
 
 import (
+       "crypto/sha512"
+       "encoding/hex"
        "gnunet/config"
        "gnunet/core"
+       "gnunet/service/dht/blocks"
        "gnunet/util"
        "math/rand"
        "testing"
@@ -43,7 +46,7 @@ type Entry struct {
 
 // test data
 var (
-       cfg = &config.NodeConfig{
+       nodeCfg = &config.NodeConfig{
                PrivateSeed: "YGoe6XFH3XdvFRl+agx9gIzPTvxA229WFdkazEMdcOs=",
                Endpoints: []*config.EndpointConfig{
                        {
@@ -53,6 +56,9 @@ var (
                        },
                },
        }
+       rtCfg = &config.RoutingConfig{
+               PeerTTL: 10800,
+       }
 )
 
 // TestRT connects and disconnects random peers to test the base
@@ -64,18 +70,16 @@ func TestRT(t *testing.T) {
        // helper functions
        genRemotePeer := func() *PeerAddress {
                d := make([]byte, 32)
-               if _, err := rand.Read(d); err != nil {
-                       panic(err)
-               }
+               _, _ = rand.Read(d)
                return NewPeerAddress(util.NewPeerID(d))
        }
 
        // create routing table and start command handler
-       local, err := core.NewLocalPeer(cfg)
+       local, err := core.NewLocalPeer(nodeCfg)
        if err != nil {
                t.Fatal(err)
        }
-       rt := NewRoutingTable(NewPeerAddress(local.GetID()))
+       rt := NewRoutingTable(NewPeerAddress(local.GetID()), rtCfg)
 
        // create a task list
        tasks := make([]*Entry, NUMP)
@@ -91,7 +95,6 @@ func TestRT(t *testing.T) {
 
        // actions:
        connected := func(task *Entry, e int64, msg string) {
-               task.addr.connected = true
                rt.Add(task.addr)
                task.online = true
                task.last = e
@@ -136,10 +139,29 @@ func TestRT(t *testing.T) {
 
        // execute some routing functions on remaining table
        k := genRemotePeer()
-       bf := NewPeerBloomFilter()
-       n := rt.SelectClosestPeer(k, bf)
+       pf := blocks.NewPeerFilter()
+       n := rt.SelectClosestPeer(k, pf)
        t.Logf("Closest: %s -> %s\n", k, n)
 
-       n = rt.SelectRandomPeer(bf)
+       n = rt.SelectRandomPeer(pf)
        t.Logf("Random: %s\n", n)
 }
+
+func TestDistance(t *testing.T) {
+       pid1 := "4ER9C0GV4QC25GGQMXBBGXYFEB3ZVAYMXZVSRKDVEGCDTAS34E30"
+       pid2 := "V61ESQ96AFXZWDSA509HP11K5HJXXJ9ECM4NAMCQRX5YW4KN8XPG"
+
+       p1, _ := util.DecodeStringToBinary(pid1, 32)
+       p2, _ := util.DecodeStringToBinary(pid2, 32)
+
+       h1 := sha512.Sum512(p1)
+       h2 := sha512.Sum512(p2)
+       t.Logf("h1=%s\n", hex.EncodeToString(h1[:]))
+       t.Logf("h2=%s\n", hex.EncodeToString(h2[:]))
+
+       pa1 := NewPeerAddress(util.NewPeerID(p1))
+       pa2 := NewPeerAddress(util.NewPeerID(p2))
+
+       dist, idx := pa1.Distance(pa2)
+       t.Logf("dist=%v, idx=%d\n", dist, idx)
+}
diff --git a/src/gnunet/service/dht/rpc.go b/src/gnunet/service/dht/rpc.go
index 3ec2b73..96ae26b 100644
--- a/src/gnunet/service/dht/rpc.go
+++ b/src/gnunet/service/dht/rpc.go
@@ -19,21 +19,49 @@
 package dht
 
 import (
-       "net/rpc"
-       "time"
+       "gnunet/service"
+       "net/http"
+
+       "github.com/bfix/gospel/logger"
 )
 
 //----------------------------------------------------------------------
 
-type DHTCommand struct{}
+// RPCService is a type for DHT-related JSON-RPC requests
+type RPCService struct{}
+
+// local instance of service
+var dhtRPC = &RPCService{}
+
+//----------------------------------------------------------------------
+// Command "DHT.Status"
+//----------------------------------------------------------------------
 
-type DHTStats struct {
-       Started time.Time
+// StatusRequest is a status request for specific information addressed
+// by topic(s)
+type StatusRequest struct {
+       Topics []string `json:"topics"`
 }
 
-func (c *DHTCommand) Status(mode int, stats *DHTStats) error {
-       *stats = DHTStats{
-               Started: time.Now(),
+// StatusResponse is a response to a status request. It returns information
+// on each topic requested.
+type StatusResponse struct {
+       Messages map[string]string `json:"messages"`
+}
+
+// Status requests information by topic(s).
+func (s *RPCService) Status(r *http.Request, req *StatusRequest, reply 
*StatusResponse) error {
+       // assemble information on topic(s)
+       out := make(map[string]string)
+       for _, topic := range req.Topics {
+               switch topic {
+               case "echo":
+                       out[topic] = "echo test"
+               }
+       }
+       // set reply
+       *reply = StatusResponse{
+               Messages: out,
        }
        return nil
 }
@@ -41,6 +69,8 @@ func (c *DHTCommand) Status(mode int, stats *DHTStats) error {
 //----------------------------------------------------------------------
 
 // InitRPC registers RPC commands for the module
-func (m *Module) InitRPC(srv *rpc.Server) {
-       srv.Register(new(DHTCommand))
+func (m *Module) InitRPC(srv *service.JRPCServer) {
+       if err := srv.RegisterService(dhtRPC, "DHT"); err != nil {
+               logger.Printf(logger.ERROR, "[dht] Failed to init RPC: %s", 
err.Error())
+       }
 }
diff --git a/src/gnunet/service/dht/service.go 
b/src/gnunet/service/dht/service.go
index 3cf216f..29e3804 100644
--- a/src/gnunet/service/dht/service.go
+++ b/src/gnunet/service/dht/service.go
@@ -23,10 +23,9 @@ import (
        "fmt"
        "io"
 
+       "gnunet/config"
        "gnunet/core"
-       "gnunet/message"
        "gnunet/service"
-       "gnunet/transport"
 
        "github.com/bfix/gospel/logger"
 )
@@ -48,15 +47,14 @@ type Service struct {
 }
 
 // NewService creates a new DHT service instance
-func NewService(ctx context.Context, c *core.Core) (service.Service, error) {
-       mod, err := NewModule(ctx, c)
+func NewService(ctx context.Context, c *core.Core, cfg *config.DHTConfig) 
(*Service, error) {
+       mod, err := NewModule(ctx, c, cfg)
        if err != nil {
                return nil, err
        }
        srv := &Service{
                Module: *mod,
        }
-       srv.ProcessFcn = srv.HandleMessage
        return srv, nil
 }
 
@@ -85,7 +83,8 @@ loop:
                logger.Printf(logger.INFO, "[dht:%d:%d] Received request: 
%v\n", id, reqID, msg)
 
                // handle message
-               s.HandleMessage(context.WithValue(ctx, "label", 
fmt.Sprintf(":%d:%d", id, reqID)), msg, mc)
+               valueCtx := context.WithValue(ctx, service.CtxKey("label"), 
fmt.Sprintf(":%d:%d", id, reqID))
+               s.HandleMessage(valueCtx, nil, msg, mc)
        }
        // close client connection
        mc.Close()
@@ -94,48 +93,3 @@ loop:
        logger.Printf(logger.INFO, "[dht:%d] Start closing session...\n", id)
        cancel()
 }
-
-// HandleMessage handles a DHT request/response message. If the transport 
channel
-// is nil, responses are send directly via the transport layer.
-func (s *Service) HandleMessage(ctx context.Context, msg message.Message, back 
transport.Responder) bool {
-       // assemble log label
-       label := ""
-       if v := ctx.Value("label"); v != nil {
-               label = v.(string)
-       }
-       // process message
-       switch msg.(type) {
-       case *message.DHTClientPutMsg:
-               //----------------------------------------------------------
-               // DHT PUT
-               //----------------------------------------------------------
-
-       case *message.DHTClientGetMsg:
-               //----------------------------------------------------------
-               // DHT GET
-               //----------------------------------------------------------
-
-       case *message.DHTClientGetResultsKnownMsg:
-               //----------------------------------------------------------
-               // DHT GET-RESULTS-KNOWN
-               //----------------------------------------------------------
-
-       case *message.DHTClientGetStopMsg:
-               //----------------------------------------------------------
-               // DHT GET-STOP
-               //----------------------------------------------------------
-
-       case *message.DHTClientResultMsg:
-               //----------------------------------------------------------
-               // DHT RESULT
-               //----------------------------------------------------------
-
-       default:
-               //----------------------------------------------------------
-               // UNKNOWN message type received
-               //----------------------------------------------------------
-               logger.Printf(logger.ERROR, "[dht-%s] Unhandled message of type 
(%d)\n", label, msg.Header().MsgType)
-               return false
-       }
-       return true
-}
diff --git a/src/gnunet/service/gns/block_handler.go 
b/src/gnunet/service/gns/block_handler.go
index 4c49c99..ee87065 100644
--- a/src/gnunet/service/gns/block_handler.go
+++ b/src/gnunet/service/gns/block_handler.go
@@ -30,7 +30,7 @@ import (
        "github.com/bfix/gospel/logger"
 )
 
-// HdlrInst is the type for functions that instanciate custom block handlers.
+// HdlrInst is the type for functions that instantiate custom block handlers.
 type HdlrInst func(*message.ResourceRecord, []string) (BlockHandler, error)
 
 // Error codes
@@ -157,7 +157,7 @@ func NewBlockHandlerList(records []*message.ResourceRecord, 
labels []string) (*B
                hl.counts.Add(rrType)
 
                // check for custom handler type
-               if creat, ok := customHandler[enums.GNSType(rrType)]; ok {
+               if creat, ok := customHandler[rrType]; ok {
                        // check if a handler for given type already exists
                        var (
                                hdlr BlockHandler
@@ -192,7 +192,7 @@ func NewBlockHandlerList(records []*message.ResourceRecord, 
labels []string) (*B
 }
 
 // GetHandler returns a BlockHandler for the given GNS block type.
-// If more than one type is given, the first matching hanlder is
+// If more than one type is given, the first matching handler is
 // returned.
 func (hl *BlockHandlerList) GetHandler(types ...enums.GNSType) BlockHandler {
        for _, t := range types {
diff --git a/src/gnunet/service/gns/dns.go b/src/gnunet/service/gns/dns.go
index 4422c44..7996460 100644
--- a/src/gnunet/service/gns/dns.go
+++ b/src/gnunet/service/gns/dns.go
@@ -204,7 +204,7 @@ func QueryDNS(id int, name string, server net.IP, kind 
RRTypeList) *message.Reco
 // ResolveDNS resolves a name in DNS. Multiple DNS servers are queried in
 // parallel; the first result delivered by any of the servers is returned
 // as the result list of matching resource records.
-func (gns *Module) ResolveDNS(
+func (m *Module) ResolveDNS(
        ctx context.Context,
        name string,
        servers []string,
@@ -223,7 +223,7 @@ func (gns *Module) ResolveDNS(
                if addr == nil {
                        // no, it is a name... try to resolve an IP address 
from the name
                        query := NewRRTypeList(enums.GNS_TYPE_DNS_A, 
enums.GNS_TYPE_DNS_AAAA)
-                       if set, err = gns.ResolveUnknown(ctx, srv, nil, zkey, 
query, depth+1); err != nil {
+                       if set, err = m.ResolveUnknown(ctx, srv, nil, zkey, 
query, depth+1); err != nil {
                                logger.Printf(logger.ERROR, "[dns] Can't 
resolve NS server '%s': %s\n", srv, err.Error())
                                continue
                        }
diff --git a/src/gnunet/service/gns/module.go b/src/gnunet/service/gns/module.go
index 1129273..c431d48 100644
--- a/src/gnunet/service/gns/module.go
+++ b/src/gnunet/service/gns/module.go
@@ -103,7 +103,7 @@ func NewModule(ctx context.Context, c *core.Core) (m 
*Module) {
                ModuleImpl: *service.NewModuleImpl(),
        }
        // register as listener for core events
-       listener := m.Run(ctx, m.event, m.Filter(), 0, nil)
+       listener := m.ModuleImpl.Run(ctx, m.event, m.Filter(), 0, nil)
        c.Register("gns", listener)
        return
 }
@@ -135,11 +135,11 @@ func (m *Module) Export(fcn map[string]any) {
 // Import functions
 func (m *Module) Import(fcn map[string]any) {
        // resolve imports from other modules
-       m.LookupLocal = fcn["namecache:get"].(func(ctx context.Context, query 
*blocks.GNSQuery) (*blocks.GNSBlock, error))
-       m.StoreLocal = fcn["namecache:put"].(func(ctx context.Context, query 
*blocks.GNSQuery, block *blocks.GNSBlock) error)
-       m.LookupRemote = fcn["dht:get"].(func(ctx context.Context, query 
blocks.Query) (blocks.Block, error))
-       m.RevocationQuery = fcn["rev:query"].(func(ctx context.Context, zkey 
*crypto.ZoneKey) (valid bool, err error))
-       m.RevocationRevoke = fcn["rev:revoke"].(func(ctx context.Context, rd 
*revocation.RevData) (success bool, err error))
+       m.LookupLocal, _ = fcn["namecache:get"].(func(ctx context.Context, 
query *blocks.GNSQuery) (*blocks.GNSBlock, error))
+       m.StoreLocal, _ = fcn["namecache:put"].(func(ctx context.Context, query 
*blocks.GNSQuery, block *blocks.GNSBlock) error)
+       m.LookupRemote, _ = fcn["dht:get"].(func(ctx context.Context, query 
blocks.Query) (blocks.Block, error))
+       m.RevocationQuery, _ = fcn["rev:query"].(func(ctx context.Context, zkey 
*crypto.ZoneKey) (valid bool, err error))
+       m.RevocationRevoke, _ = fcn["rev:revoke"].(func(ctx context.Context, rd 
*revocation.RevData) (success bool, err error))
 }
 
 //----------------------------------------------------------------------
@@ -257,7 +257,7 @@ func (m *Module) ResolveRelative(
 
                if hdlr := hdlrs.GetHandler(crypto.ZoneTypes...); hdlr != nil {
                        // (1) zone key record:
-                       inst := hdlr.(*ZoneKeyHandler)
+                       inst, _ := hdlr.(*ZoneKeyHandler)
                        // if labels are pending, set new zone and continue 
resolution;
                        // otherwise resolve "@" label for the zone if no zone 
key record
                        // was requested.
@@ -265,14 +265,15 @@ func (m *Module) ResolveRelative(
                                labels = append(labels, "@")
                        }
                        // check if zone key has been revoked
-                       if valid, err := m.RevocationQuery(ctx, inst.zkey); err 
!= nil || !valid {
+                       var valid bool
+                       if valid, err = m.RevocationQuery(ctx, inst.zkey); err 
!= nil || !valid {
                                // revoked key -> no results!
                                records = make([]*message.ResourceRecord, 0)
                                break
                        }
                } else if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_GNS2DNS); 
hdlr != nil {
                        // (2) GNS2DNS records
-                       inst := hdlr.(*Gns2DnsHandler)
+                       inst, _ := hdlr.(*Gns2DnsHandler)
                        // if we are at the end of the path and the requested 
type
                        // includes GNS_TYPE_GNS2DNS, the GNS2DNS records are 
returned...
                        if len(labels) == 1 && 
kind.HasType(enums.GNS_TYPE_GNS2DNS) && !kind.IsAny() {
@@ -308,7 +309,7 @@ func (m *Module) ResolveRelative(
                        break
                } else if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_BOX); hdlr != 
nil {
                        // (3) BOX records:
-                       inst := hdlr.(*BoxHandler)
+                       inst, _ := hdlr.(*BoxHandler)
                        newRecords := inst.Records(kind).Records
                        if len(newRecords) > 0 {
                                records = newRecords
@@ -316,7 +317,7 @@ func (m *Module) ResolveRelative(
                        }
                } else if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_DNS_CNAME); 
hdlr != nil {
                        // (4) CNAME records:
-                       inst := hdlr.(*CnameHandler)
+                       inst, _ := hdlr.(*CnameHandler)
                        // if we are at the end of the path and the requested 
type
                        // includes GNS_TYPE_DNS_CNAME, the records are 
returned...
                        if len(labels) == 1 && 
kind.HasType(enums.GNS_TYPE_DNS_CNAME) && !kind.IsAny() {
@@ -352,7 +353,7 @@ func (m *Module) ResolveRelative(
                // check for VPN record
                if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_VPN); hdlr != nil {
                        // add VPN record to result set
-                       inst := hdlr.(*VpnHandler)
+                       inst, _ := hdlr.(*VpnHandler)
                        set.AddRecord(inst.rec)
                }
        }
@@ -458,7 +459,10 @@ func (m *Module) Lookup(
                                return
                        }
                        // store RRs from remote locally.
-                       m.StoreLocal(ctx, query, block)
+                       if err = m.StoreLocal(ctx, query, block); err != nil {
+                               logger.Printf(logger.DBG, "[gns] store local 
failed: %s", err.Error())
+                               return
+                       }
                }
        }
        return
diff --git a/src/gnunet/service/gns/rpc.go b/src/gnunet/service/gns/rpc.go
index 33682d3..042d72a 100644
--- a/src/gnunet/service/gns/rpc.go
+++ b/src/gnunet/service/gns/rpc.go
@@ -18,10 +18,10 @@
 
 package gns
 
-import "net/rpc"
+import "gnunet/service"
 
 //----------------------------------------------------------------------
 
 // InitRPC registers RPC commands for the module
-func (m *Module) InitRPC(srv *rpc.Server) {
+func (m *Module) InitRPC(srv *service.JRPCServer) {
 }
diff --git a/src/gnunet/service/gns/service.go 
b/src/gnunet/service/gns/service.go
index 19ddc14..45eb700 100644
--- a/src/gnunet/service/gns/service.go
+++ b/src/gnunet/service/gns/service.go
@@ -62,8 +62,6 @@ func NewService(ctx context.Context, c *core.Core) 
service.Service {
        srv := &Service{
                Module: *mod,
        }
-       srv.ProcessFcn = srv.HandleMessage
-
        // set external function references (external services)
        srv.LookupLocal = srv.LookupNamecache
        srv.StoreLocal = srv.StoreNamecache
@@ -98,7 +96,8 @@ func (s *Service) ServeClient(ctx context.Context, id int, mc 
*service.Connectio
                logger.Printf(logger.INFO, "[gns:%d:%d] Received request: 
%v\n", id, reqID, msg)
 
                // handle message
-               s.HandleMessage(context.WithValue(ctx, "label", 
fmt.Sprintf(":%d:%d", id, reqID)), msg, mc)
+               valueCtx := context.WithValue(ctx, service.CtxKey("label"), 
fmt.Sprintf(":%d:%d", id, reqID))
+               s.HandleMessage(valueCtx, nil, msg, mc)
        }
        // close client connection
        mc.Close()
@@ -109,11 +108,11 @@ func (s *Service) ServeClient(ctx context.Context, id 
int, mc *service.Connectio
 }
 
 // Handle a single incoming message
-func (s *Service) HandleMessage(ctx context.Context, msg message.Message, back 
transport.Responder) bool {
+func (s *Service) HandleMessage(ctx context.Context, sender *util.PeerID, msg 
message.Message, back transport.Responder) bool {
        // assemble log label
        label := ""
        if v := ctx.Value("label"); v != nil {
-               label = v.(string)
+               label, _ = v.(string)
        }
        // perform lookup
        switch m := msg.(type) {
@@ -123,7 +122,7 @@ func (s *Service) HandleMessage(ctx context.Context, msg 
message.Message, back t
                //----------------------------------------------------------
 
                // perform lookup on block (locally and remote)
-               go func(m *message.LookupMsg) {
+               go func(m *message.LookupMsg, label string) {
                        logger.Printf(logger.INFO, "[gns%s] Lookup request 
received.\n", label)
                        resp := message.NewGNSLookupResultMsg(m.ID)
                        defer func() {
@@ -137,7 +136,6 @@ func (s *Service) HandleMessage(ctx context.Context, msg 
message.Message, back t
                                logger.Printf(logger.DBG, "[gns%s] Lookup 
request finished.\n", label)
                        }()
 
-                       label := m.GetName()
                        kind := NewRRTypeList(enums.GNSType(m.Type))
                        recset, err := s.Resolve(ctx, label, m.Zone, kind, 
int(m.Options), 0)
                        if err != nil {
@@ -163,11 +161,13 @@ func (s *Service) HandleMessage(ctx context.Context, msg 
message.Message, back t
                                        // is this the record type we are 
looking for?
                                        if rec.Type == m.Type || 
enums.GNSType(m.Type) == enums.GNS_TYPE_ANY {
                                                // add it to the response 
message
-                                               resp.AddRecord(rec)
+                                               if err := resp.AddRecord(rec); 
err != nil {
+                                                       
logger.Printf(logger.ERROR, "[gns%s] failed: %sv", label, err.Error())
+                                               }
                                        }
                                }
                        }
-               }(m)
+               }(m, label)
 
        default:
                //----------------------------------------------------------
@@ -374,7 +374,7 @@ func (s *Service) LookupDHT(ctx context.Context, query 
blocks.Query) (block bloc
        // send DHT GET request and wait for response
        reqGet := message.NewDHTClientGetMsg(query.Key())
        reqGet.ID = uint64(util.NextID())
-       reqGet.ReplLevel = uint32(enums.DHT_GNS_REPLICATION_LEVEL)
+       reqGet.ReplLevel = uint32(enums.GNS_REPLICATION_LEVEL)
        reqGet.Type = uint32(enums.BLOCK_TYPE_GNS_NAMERECORD)
        reqGet.Options = uint32(enums.DHT_RO_DEMULTIPLEX_EVERYWHERE)
 
@@ -419,7 +419,7 @@ func (s *Service) LookupDHT(ctx context.Context, query 
blocks.Query) (block bloc
                }
 
                // get GNSBlock from message
-               qGNS := query.(*blocks.GNSQuery)
+               qGNS, _ := query.(*blocks.GNSQuery)
                block = new(blocks.GNSBlock)
                if err = data.Unmarshal(block, m.Data); err != nil {
                        logger.Printf(logger.ERROR, "[gns] can't read GNS 
block: %s\n", err.Error())
diff --git a/src/gnunet/service/module.go b/src/gnunet/service/module.go
index 65b49d8..7311c4f 100644
--- a/src/gnunet/service/module.go
+++ b/src/gnunet/service/module.go
@@ -21,9 +21,6 @@ package service
 import (
        "context"
        "gnunet/core"
-       "gnunet/message"
-       "gnunet/transport"
-       "net/rpc"
        "time"
 )
 
@@ -66,7 +63,7 @@ type Module interface {
        Import(map[string]any)
 
        // InitRPC registers RPC commands for the module
-       InitRPC(*rpc.Server)
+       InitRPC(*JRPCServer)
 
        // Filter returns the event filter for the module
        Filter() *core.EventFilter
@@ -78,20 +75,19 @@ type EventHandler func(context.Context, *core.Event)
 // Heartbeat is a function prototype for periodic tasks
 type Heartbeat func(context.Context)
 
+// CtxKey is a value-context key
+type CtxKey string
+
 // ModuleImpl is an event-handling type used by Module implementations.
 type ModuleImpl struct {
        // channel for core events.
        ch chan *core.Event
-
-       // ProcessFcn message: function reference (implemented by service)
-       ProcessFcn func(ctx context.Context, msg message.Message, back 
transport.Responder) bool
 }
 
 // NewModuleImplementation returns a new base module and starts
 func NewModuleImpl() (m *ModuleImpl) {
        return &ModuleImpl{
-               ch:         make(chan *core.Event),
-               ProcessFcn: nil,
+               ch: make(chan *core.Event),
        }
 }
 
@@ -108,14 +104,14 @@ func (m *ModuleImpl) Run(
        if heartbeat == nil {
                pulse = 365 * 24 * time.Hour // once a year
        }
-       tick := time.Tick(pulse)
+       tick := time.NewTicker(pulse)
        // run event loop
        go func() {
                for {
                        select {
                        // Handle events
                        case event := <-m.ch:
-                               hCtx := context.WithValue(ctx, "label", 
event.Label)
+                               hCtx := context.WithValue(ctx, CtxKey("label"), 
event.Label)
                                hdlr(hCtx, event)
 
                        // wait for terminate signal
@@ -123,7 +119,7 @@ func (m *ModuleImpl) Run(
                                return
 
                        // handle heartbeat
-                       case <-tick:
+                       case <-tick.C:
                                // check for defined heartbeat handler
                                if heartbeat != nil {
                                        heartbeat(ctx)
diff --git a/src/gnunet/service/namecache/module.go 
b/src/gnunet/service/namecache/module.go
index 9251a58..42d38a2 100644
--- a/src/gnunet/service/namecache/module.go
+++ b/src/gnunet/service/namecache/module.go
@@ -24,6 +24,7 @@ import (
        "gnunet/core"
        "gnunet/service"
        "gnunet/service/dht/blocks"
+       "gnunet/service/store"
 )
 
 //======================================================================
@@ -38,7 +39,7 @@ import (
 type Module struct {
        service.ModuleImpl
 
-       cache service.DHTStore // transient block cache
+       cache store.DHTStore // transient block cache
 }
 
 // NewModule creates a new module instance.
@@ -46,7 +47,7 @@ func NewModule(ctx context.Context, c *core.Core) (m *Module) 
{
        m = &Module{
                ModuleImpl: *service.NewModuleImpl(),
        }
-       m.cache, _ = service.NewDHTStore(config.Cfg.Namecache.Storage)
+       m.cache, _ = store.NewDHTStore(config.Cfg.Namecache.Storage)
        return
 }
 
@@ -69,7 +70,9 @@ func (m *Module) Import(fcm map[string]any) {
 // Get an entry from the cache if available.
 func (m *Module) Get(ctx context.Context, query *blocks.GNSQuery) (block 
*blocks.GNSBlock, err error) {
        var b blocks.Block
-       b, err = m.cache.Get(query)
+       if b, err = m.cache.Get(query); err != nil {
+               return
+       }
        err = blocks.Unwrap(b, block)
        return
 }
diff --git a/src/gnunet/service/revocation/module.go 
b/src/gnunet/service/revocation/module.go
index 6997060..e435dd4 100644
--- a/src/gnunet/service/revocation/module.go
+++ b/src/gnunet/service/revocation/module.go
@@ -25,6 +25,7 @@ import (
        "gnunet/crypto"
        "gnunet/message"
        "gnunet/service"
+       "gnunet/service/store"
        "gnunet/util"
        "net/http"
 
@@ -44,7 +45,7 @@ type Module struct {
        service.ModuleImpl
 
        bloomf *data.BloomFilter // bloomfilter for fast revocation check
-       kvs    service.KVStore   // storage for known revocations
+       kvs    store.KVStore     // storage for known revocations
 }
 
 // NewModule returns an initialized revocation module
@@ -55,7 +56,7 @@ func NewModule(ctx context.Context, c *core.Core) (m *Module) 
{
        }
        init := func() (err error) {
                // Initialize access to revocation data storage
-               if m.kvs, err = 
service.NewKVStore(config.Cfg.Revocation.Storage); err != nil {
+               if m.kvs, err = 
store.NewKVStore(config.Cfg.Revocation.Storage); err != nil {
                        return
                }
                // traverse the storage and build bloomfilter for all keys
@@ -170,6 +171,6 @@ func (m *Module) Revoke(ctx context.Context, rd *RevData) 
(success bool, err err
 // RPC returns the route and handler function for a JSON-RPC request
 func (m *Module) RPC() (string, func(http.ResponseWriter, *http.Request)) {
        return "/revocation/", func(wrt http.ResponseWriter, req *http.Request) 
{
-               wrt.Write([]byte(`{"msg": "This is REVOCATION" }`))
+               _, _ = wrt.Write([]byte(`{"msg": "This is REVOCATION" }`))
        }
 }
diff --git a/src/gnunet/service/revocation/pow.go 
b/src/gnunet/service/revocation/pow.go
index cb35532..5518749 100644
--- a/src/gnunet/service/revocation/pow.go
+++ b/src/gnunet/service/revocation/pow.go
@@ -22,7 +22,6 @@ import (
        "bytes"
        "context"
        "encoding/binary"
-       "fmt"
        "sort"
        "time"
 
@@ -62,27 +61,21 @@ func NewPoWData(pow uint64, ts util.AbsoluteTime, zoneKey 
*crypto.ZoneKey) *PoWD
                Timestamp: ts,
                ZoneKey:   zoneKey,
        }
-       if rd.SetPoW(pow) != nil {
-               return nil
-       }
+       rd.SetPoW(pow)
        return rd
 }
 
 // SetPoW sets a new PoW value in the data structure
-func (p *PoWData) SetPoW(pow uint64) error {
+func (p *PoWData) SetPoW(pow uint64) {
        p.PoW = pow
        p.blob = p.Blob()
-       if p.blob == nil {
-               return fmt.Errorf("invalid PoW work unit")
-       }
-       return nil
 }
 
 // GetPoW returns the last checked PoW value
 func (p *PoWData) GetPoW() uint64 {
        if p.blob != nil {
                var val uint64
-               binary.Read(bytes.NewReader(p.blob[:8]), binary.BigEndian, &val)
+               _ = binary.Read(bytes.NewReader(p.blob[:8]), binary.BigEndian, 
&val)
                p.PoW = val
        }
        return p.PoW
@@ -138,14 +131,11 @@ type SignedRevData struct {
 
 // NewRevDataFromMsg initializes a new RevData instance from a GNUnet message
 func NewRevDataFromMsg(m *message.RevocationRevokeMsg) *RevData {
-       rd := &RevData{
+       return &RevData{
                Timestamp:  m.Timestamp,
                ZoneKeySig: m.ZoneKeySig,
+               PoWs:       util.Clone(m.PoWs),
        }
-       for i, pow := range m.PoWs {
-               rd.PoWs[i] = pow
-       }
-       return rd
 }
 
 // Size of a serialized RevData object.
@@ -174,7 +164,6 @@ func (rd *RevData) Sign(skey *crypto.ZonePrivate) (err 
error) {
 // in this revocation and a verification status (-1=failed signature, -2=
 // expired revocation, -3="out-of-order" PoW sequence).
 func (rd *RevData) Verify(withSig bool) (zbits float64, rc int) {
-
        // (1) check signature
        if withSig {
                sigBlock := &SignedRevData{
@@ -196,7 +185,7 @@ func (rd *RevData) Verify(withSig bool) (zbits float64, rc 
int) {
        }
 
        // (2) check PoWs
-       var last uint64 = 0
+       var last uint64
        for _, pow := range rd.PoWs {
                // check sequence order
                if pow <= last {
@@ -252,7 +241,7 @@ func (rdc *RevDataCalc) Size() int {
 
 // Average number of leading zero-bits in current list
 func (rdc *RevDataCalc) Average() float64 {
-       var sum uint16 = 0
+       var sum uint16
        for _, num := range rdc.Bits {
                sum += num
        }
@@ -290,7 +279,7 @@ func (rdc *RevDataCalc) sortBits() {
 func (rdc *RevDataCalc) Compute(ctx context.Context, bits int, last uint64, cb 
func(float64, uint64)) (float64, uint64) {
        // find the largest PoW value in current work unit
        work := NewPoWData(0, rdc.Timestamp, &rdc.ZoneKeySig.ZoneKey)
-       var max uint64 = 0
+       var max uint64
        for i, pow := range rdc.PoWs {
                if pow == 0 {
                        max++
diff --git a/src/gnunet/service/revocation/pow_test.go 
b/src/gnunet/service/revocation/pow_test.go
index 17eb695..5d31b2f 100644
--- a/src/gnunet/service/revocation/pow_test.go
+++ b/src/gnunet/service/revocation/pow_test.go
@@ -12,7 +12,6 @@ import (
 
 // Test revocation with test vector defined in the RFC draft.
 func TestRevocationRFC(t *testing.T) {
-
        var (
                D     = 
"6fea32c05af58bfa979553d188605fd57d8bf9cc263b78d5f7478c07b998ed70"
                ZKEY  = 
"000100002ca223e879ecc4bbdeb5da17319281d63b2e3b6955f1c3775c804a98d5f8ddaa"
diff --git a/src/gnunet/service/revocation/rpc.go 
b/src/gnunet/service/revocation/rpc.go
index 1b8ea12..7473def 100644
--- a/src/gnunet/service/revocation/rpc.go
+++ b/src/gnunet/service/revocation/rpc.go
@@ -18,10 +18,10 @@
 
 package revocation
 
-import "net/rpc"
+import "gnunet/service"
 
 //----------------------------------------------------------------------
 
 // InitRPC registers RPC commands for the module
-func (m *Module) InitRPC(srv *rpc.Server) {
+func (m *Module) InitRPC(srv *service.JRPCServer) {
 }
diff --git a/src/gnunet/service/revocation/service.go 
b/src/gnunet/service/revocation/service.go
index 3d579e8..99d9b4a 100644
--- a/src/gnunet/service/revocation/service.go
+++ b/src/gnunet/service/revocation/service.go
@@ -27,6 +27,7 @@ import (
        "gnunet/message"
        "gnunet/service"
        "gnunet/transport"
+       "gnunet/util"
 
        "github.com/bfix/gospel/logger"
 )
@@ -47,7 +48,6 @@ func NewService(ctx context.Context, c *core.Core) 
service.Service {
        srv := &Service{
                Module: *mod,
        }
-       srv.ProcessFcn = srv.HandleMessage
        return srv
 }
 
@@ -75,7 +75,8 @@ func (s *Service) ServeClient(ctx context.Context, id int, mc 
*service.Connectio
                logger.Printf(logger.INFO, "[revocation:%d:%d] Received 
request: %v\n", id, reqID, msg)
 
                // handle message
-               s.HandleMessage(context.WithValue(ctx, "label", 
fmt.Sprintf(":%d:%d", id, reqID)), msg, mc)
+               valueCtx := context.WithValue(ctx, service.CtxKey("label"), 
fmt.Sprintf(":%d:%d", id, reqID))
+               s.HandleMessage(valueCtx, nil, msg, mc)
        }
        // close client connection
        mc.Close()
@@ -86,11 +87,11 @@ func (s *Service) ServeClient(ctx context.Context, id int, 
mc *service.Connectio
 }
 
 // Handle a single incoming message
-func (s *Service) HandleMessage(ctx context.Context, msg message.Message, back 
transport.Responder) bool {
+func (s *Service) HandleMessage(ctx context.Context, sender *util.PeerID, msg 
message.Message, back transport.Responder) bool {
        // assemble log label
        label := ""
        if v := ctx.Value("label"); v != nil {
-               label = v.(string)
+               label, _ = v.(string)
        }
        switch m := msg.(type) {
        case *message.RevocationQueryMsg:
diff --git a/src/gnunet/service/rpc.go b/src/gnunet/service/rpc.go
index de3a2c1..d5740fb 100644
--- a/src/gnunet/service/rpc.go
+++ b/src/gnunet/service/rpc.go
@@ -21,24 +21,35 @@ package service
 import (
        "context"
        "net/http"
-       "net/rpc"
        "time"
 
        "github.com/bfix/gospel/logger"
        "github.com/gorilla/mux"
+       "github.com/gorilla/rpc/v2"
+       "github.com/gorilla/rpc/v2/json2"
 )
 
+//----------------------------------------------------------------------
+//----------------------------------------------------------------------
+
+// JRPCServer for JSON-RPC handling (wrapper to keep type in our package)
+type JRPCServer struct {
+       *rpc.Server
+}
+
 //----------------------------------------------------------------------
 // JSON-RPC interface for services to be used as the primary client API
 // for perform, manage and monitor GNUnet activities.
 //----------------------------------------------------------------------
 
-// StartRPC the JSON-RPC server. It can be terminated by context
-func StartRPC(ctx context.Context, endpoint string) (srvRPC *rpc.Server, err 
error) {
+// RunRPCServer runs the JSON-RPC server. It can be terminated by context only.
+func RunRPCServer(ctx context.Context, endpoint string) (srvRPC *JRPCServer, 
err error) {
+       // instantiate RPC service
+       srvRPC = &JRPCServer{rpc.NewServer()}
+       srvRPC.RegisterCodec(json2.NewCodec(), "application/json")
 
        // setup RPC request handler
        router := mux.NewRouter()
-       srvRPC = rpc.NewServer()
        router.HandleFunc("/", srvRPC.ServeHTTP)
 
        // instantiate a server and run it
@@ -51,16 +62,14 @@ func StartRPC(ctx context.Context, endpoint string) (srvRPC 
*rpc.Server, err err
        // start listening
        go func() {
                if err := srv.ListenAndServe(); err != http.ErrServerClosed {
-                       logger.Printf(logger.WARN, "[RPC] Server listen failed: 
%s", err.Error())
+                       logger.Printf(logger.WARN, "[rpc] server listen failed: 
%s", err.Error())
                }
        }()
        // wait for shutdown
        go func() {
-               select {
-               case <-ctx.Done():
-                       if err := srv.Shutdown(context.Background()); err != 
nil {
-                               logger.Printf(logger.WARN, "[RPC] Server 
shutdownn failed: %s", err.Error())
-                       }
+               <-ctx.Done()
+               if err := srv.Shutdown(context.Background()); err != nil {
+                       logger.Printf(logger.WARN, "[rpc] server shutdownn 
failed: %s", err.Error())
                }
        }()
        return
diff --git a/src/gnunet/service/service.go b/src/gnunet/service/service.go
index c47ff5c..dd08282 100644
--- a/src/gnunet/service/service.go
+++ b/src/gnunet/service/service.go
@@ -42,7 +42,7 @@ type Service interface {
        // Handle a single incoming message (either locally from a socket
        // connection or from Transport). Response messages can be send
        // via a Responder. Returns true if message was processed.
-       HandleMessage(ctx context.Context, msg message.Message, resp 
transport.Responder) bool
+       HandleMessage(ctx context.Context, sender *util.PeerID, msg 
message.Message, resp transport.Responder) bool
 }
 
 // SocketHandler handles incoming connections on the local service socket.
@@ -85,7 +85,6 @@ func (h *SocketHandler) Start(ctx context.Context, path 
string, params map[strin
        loop:
                for {
                        select {
-
                        // handle incoming connection
                        case conn := <-h.hdlr:
                                // run a new session with context
diff --git a/src/gnunet/service/store.go b/src/gnunet/service/store.go
deleted file mode 100644
index 5de5415..0000000
--- a/src/gnunet/service/store.go
+++ /dev/null
@@ -1,502 +0,0 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019-2022 Bernd Fix  >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package service
-
-import (
-       "context"
-       "database/sql"
-       "encoding/binary"
-       "encoding/gob"
-       "encoding/hex"
-       "errors"
-       "fmt"
-       "gnunet/config"
-       "gnunet/crypto"
-       "gnunet/service/dht/blocks"
-       "gnunet/util"
-       "io"
-       "io/ioutil"
-       "os"
-       "sort"
-       "sync"
-
-       "github.com/bfix/gospel/logger"
-       redis "github.com/go-redis/redis/v8"
-)
-
-// Error messages related to the key/value-store implementations
-var (
-       ErrStoreInvalidSpec  = fmt.Errorf("Invalid Store specification")
-       ErrStoreUnknown      = fmt.Errorf("Unknown Store type")
-       ErrStoreNotAvailable = fmt.Errorf("Store not available")
-)
-
-//------------------------------------------------------------
-// Generic storage interface. Can be used for persistent or
-// transient (caching) storage of key/value data.
-//------------------------------------------------------------
-
-// Store is a key/value storage where the type of the key is either
-// a SHA512 hash value or a string and the value is either a DHT
-// block or a string. It is possiblle to mix any key/value types,
-// but not used in this implementation.
-type Store[K, V any] interface {
-       // Put value into storage under given key
-       Put(key K, val V) error
-
-       // Get value with given key from storage
-       Get(key K) (V, error)
-
-       // List all store keys
-       List() ([]K, error)
-}
-
-//------------------------------------------------------------
-// Types for custom store requirements
-//------------------------------------------------------------
-
-// DHTStore for DHT queries and blocks
-type DHTStore Store[blocks.Query, blocks.Block]
-
-// KVStore for key/value string pairs
-type KVStore Store[string, string]
-
-//------------------------------------------------------------
-// NewDHTStore creates a new storage handler with given spec
-// for use with DHT queries and blocks
-func NewDHTStore(spec config.ParameterConfig) (DHTStore, error) {
-       // get the mode parameter
-       mode, ok := config.GetParam[string](spec, "mode")
-       if !ok {
-               return nil, ErrStoreInvalidSpec
-       }
-       switch mode {
-       //------------------------------------------------------------------
-       // File-base storage
-       //------------------------------------------------------------------
-       case "file":
-               return NewFileStore(spec)
-       }
-       return nil, ErrStoreUnknown
-}
-
-//------------------------------------------------------------
-// NewKVStore creates a new storage handler with given spec
-// for use with key/value string pairs.
-func NewKVStore(spec config.ParameterConfig) (KVStore, error) {
-       // get the mode parameter
-       mode, ok := config.GetParam[string](spec, "mode")
-       if !ok {
-               return nil, ErrStoreInvalidSpec
-       }
-       switch mode {
-       //--------------------------------------------------------------
-       // Redis service
-       //--------------------------------------------------------------
-       case "redis":
-               return NewRedisStore(spec)
-
-       //--------------------------------------------------------------
-       // SQL database service
-       //--------------------------------------------------------------
-       case "sql":
-               return NewSQLStore(spec)
-       }
-       return nil, errors.New("unknown storage mechanism")
-}
-
-//------------------------------------------------------------
-// Filesystem-based storage
-//------------------------------------------------------------
-
-// FileHeader is the layout of a file managed by the storage handler.
-// On start-up the file store recreates the list of file entries from
-// traversing the actual filesystem. This is done in the background.
-type FileHeader struct {
-       key       string            // storage key
-       size      uint64            // size of file
-       btype     uint16            // block type
-       stored    util.AbsoluteTime // time added to store
-       expires   util.AbsoluteTime // expiration time
-       lastUsed  util.AbsoluteTime // time last used
-       usedCount uint64            // usage count
-}
-
-// FileStore implements a filesystem-based storage mechanism for
-// DHT queries and blocks.
-type FileStore struct {
-       path  string                 // storage path
-       cache bool                   // storage works as cache
-       args  config.ParameterConfig // arguments / settings
-
-       totalSize uint64                 // total storage size (logical, not 
physical)
-       files     map[string]*FileHeader // list of file headers
-       wrPos     int                    // write position in cyclic list
-       mtx       sync.Mutex             // serialize operations (prune)
-}
-
-// NewFileStore instantiates a new file storage.
-func NewFileStore(spec config.ParameterConfig) (DHTStore, error) {
-       // get path parameter
-       path, ok := config.GetParam[string](spec, "path")
-       if !ok {
-               return nil, ErrStoreInvalidSpec
-       }
-       isCache, ok := config.GetParam[bool](spec, "cache")
-       if !ok {
-               isCache = false
-       }
-       // remove old cache content
-       if isCache {
-               os.RemoveAll(path)
-       }
-       // create file store handler
-       fs := &FileStore{
-               path:  path,
-               args:  spec,
-               cache: isCache,
-               files: make(map[string]*FileHeader),
-       }
-       // load file header list
-       if !isCache {
-               if fp, err := os.Open(path + "/files.db"); err == nil {
-                       dec := gob.NewDecoder(fp)
-                       for {
-                               hdr := new(FileHeader)
-                               if dec.Decode(hdr) != nil {
-                                       if err != io.EOF {
-                                               return nil, err
-                                       }
-                                       break
-                               }
-                               fs.files[hdr.key] = hdr
-                               fs.totalSize += hdr.size
-                       }
-                       fp.Close()
-               }
-       }
-       return fs, nil
-}
-
-// Close file storage. write metadata to file
-func (s *FileStore) Close() (err error) {
-       if !s.cache {
-               if fp, err := os.Create(s.path + "/files.db"); err == nil {
-                       defer fp.Close()
-                       enc := gob.NewEncoder(fp)
-                       for _, hdr := range s.files {
-                               if err = enc.Encode(hdr); err != nil {
-                                       break
-                               }
-                       }
-               }
-       }
-       return
-}
-
-// Put block into storage under given key
-func (s *FileStore) Put(query blocks.Query, block blocks.Block) (err error) {
-       // check for free space
-       if s.cache {
-               // caching is limited by explicit number of files
-               num, ok := config.GetParam[int](s.args, "num")
-               if !ok {
-                       num = 100
-               }
-               if len(s.files) >= num {
-                       // make space for at least one new entry
-                       s.prune(1)
-               }
-       } else {
-               // normal storage is limited by quota (default: 10GB)
-               max, ok := config.GetParam[int](s.args, "maxGB")
-               if !ok {
-                       max = 10
-               }
-               if int(s.totalSize>>30) > max {
-                       // drop a significant number of blocks
-                       s.prune(20)
-               }
-       }
-       // get query parameters for entry
-       var btype uint16 // block type
-       query.Get("blkType", &btype)
-       var expire util.AbsoluteTime // block expiration
-       query.Get("expire", &expire)
-
-       // get path and filename from key
-       path, fname := s.expandPath(query.Key())
-       // make sure the path exists
-       if err = os.MkdirAll(path, 0755); err != nil {
-               return
-       }
-       // write to file for storage
-       var fp *os.File
-       var fpSize int
-       if fp, err = os.Create(path + "/" + fname); err == nil {
-               defer fp.Close()
-               // write block data
-               if err = binary.Write(fp, binary.BigEndian, btype); err == nil {
-                       if err = binary.Write(fp, binary.BigEndian, expire); 
err == nil {
-                               _, err = fp.Write(block.Data())
-                       }
-               }
-       }
-       // add header to internal list
-       now := util.AbsoluteTimeNow()
-       hdr := &FileHeader{
-               key:       hex.EncodeToString(query.Key().Bits),
-               size:      uint64(fpSize),
-               btype:     btype,
-               expires:   expire,
-               stored:    now,
-               lastUsed:  now,
-               usedCount: 1,
-       }
-       s.files[hdr.key] = hdr
-       return
-}
-
-// Get block with given key from storage
-func (s *FileStore) Get(query blocks.Query) (block blocks.Block, err error) {
-       // get requested block type
-       var (
-               btype  uint16            = blocks.DHT_BLOCK_ANY
-               blkt   uint16            // actual block type
-               expire util.AbsoluteTime // expiration date
-               data   []byte            // block data
-       )
-       query.Get("blkType", &btype)
-
-       // get path and filename from key
-       path, fname := s.expandPath(query.Key())
-       // read file content (block data)
-       var file *os.File
-       if file, err = os.Open(path + "/" + fname); err != nil {
-               return
-       }
-       // read block data
-       if err = binary.Read(file, binary.BigEndian, &blkt); err == nil {
-               if btype != blocks.DHT_BLOCK_ANY && btype != blkt {
-                       // block types not matching
-                       return
-               }
-               if err = binary.Read(file, binary.BigEndian, &expire); err == 
nil {
-                       if data, err = ioutil.ReadAll(file); err == nil {
-                               block = blocks.NewGenericBlock(data)
-                       }
-               }
-       }
-       return
-}
-
-// Get a list of all stored block keys (generic query).
-func (s *FileStore) List() ([]blocks.Query, error) {
-       return make([]blocks.Query, 0), nil
-}
-
-// expandPath returns the full path to the file for given key.
-func (s *FileStore) expandPath(key *crypto.HashCode) (string, string) {
-       h := hex.EncodeToString(key.Bits)
-       return fmt.Sprintf("%s/%s/%s", s.path, h[:2], h[2:4]), h[4:]
-}
-
-// Prune list of file headers so we drop at least n entries.
-// returns number of removed entries.
-func (s *FileStore) prune(n int) (del int) {
-       // get list of headers; remove expired entries on the fly
-       list := make([]*FileHeader, 0)
-       for key, hdr := range s.files {
-               // remove expired entry
-               if hdr.expires.Expired() {
-                       s.dropFile(key)
-                       del++
-               }
-               // append to list
-               list = append(list, hdr)
-       }
-       // check if we are already done.
-       if del >= n {
-               return
-       }
-       // sort list by decending rate "(lifetime * size) / usedCount"
-       sort.Slice(list, func(i, j int) bool {
-               ri := (list[i].stored.Elapsed().Val * list[i].size) / 
list[i].usedCount
-               rj := (list[j].stored.Elapsed().Val * list[j].size) / 
list[j].usedCount
-               return ri > rj
-       })
-       // remove from start of list until prune limit is reached
-       for _, hdr := range list {
-               s.dropFile(hdr.key)
-               del++
-               if del == n {
-                       break
-               }
-       }
-       return
-}
-
-// drop file removes a file from the internal list and the physical storage.
-func (s *FileStore) dropFile(key string) {
-       // remove for internal list
-       delete(s.files, key)
-       // remove from filesystem
-       path := fmt.Sprintf("%s/%s/%s/%s", s.path, key[:2], key[2:4], key[4:])
-       if err := os.Remove(path); err != nil {
-               logger.Printf(logger.ERROR, "[store] can't remove file %s: %s", 
path, err.Error())
-               return
-       }
-}
-
-//------------------------------------------------------------
-// Redis: only use for caching purposes on key/value strings
-//------------------------------------------------------------
-
-// RedisStore uses a (local) Redis server for key/value storage
-type RedisStore struct {
-       client *redis.Client // client connection
-       db     int           // index to database
-}
-
-// NewRedisStore creates a Redis service client instance.
-func NewRedisStore(spec config.ParameterConfig) (s KVStore, err error) {
-       // get connection parameters
-       addr, ok := config.GetParam[string](spec, "addr")
-       if !ok {
-               return nil, ErrStoreInvalidSpec
-       }
-       passwd, ok := config.GetParam[string](spec, "passwd")
-       if !ok {
-               passwd = ""
-       }
-       db, ok := config.GetParam[int](spec, "db")
-       if !ok {
-               return nil, ErrStoreInvalidSpec
-       }
-
-       // create new Redis store
-       kvs := new(RedisStore)
-       kvs.db = db
-       kvs.client = redis.NewClient(&redis.Options{
-               Addr:     addr,
-               Password: passwd,
-               DB:       db,
-       })
-       if kvs.client == nil {
-               err = ErrStoreNotAvailable
-       }
-       s = kvs
-       return
-}
-
-// Put block into storage under given key
-func (s *RedisStore) Put(key string, value string) (err error) {
-       return s.client.Set(context.TODO(), key, value, 0).Err()
-}
-
-// Get block with given key from storage
-func (s *RedisStore) Get(key string) (value string, err error) {
-       return s.client.Get(context.TODO(), key).Result()
-}
-
-// List all keys in store
-func (s *RedisStore) List() (keys []string, err error) {
-       var (
-               crs  uint64
-               segm []string
-               ctx  = context.TODO()
-       )
-       keys = make([]string, 0)
-       for {
-               segm, crs, err = s.client.Scan(ctx, crs, "*", 10).Result()
-               if err != nil {
-                       return
-               }
-               if crs == 0 {
-                       break
-               }
-               keys = append(keys, segm...)
-       }
-       return
-}
-
-//------------------------------------------------------------
-// SQL-based key-value-store
-//------------------------------------------------------------
-
-// SQLStore for generic SQL database handling
-type SQLStore struct {
-       db *util.DbConn
-}
-
-// NewSQLStore creates a new SQL-based key/value store.
-func NewSQLStore(spec config.ParameterConfig) (s KVStore, err error) {
-       // get connection parameters
-       connect, ok := config.GetParam[string](spec, "connect")
-       if !ok {
-               return nil, ErrStoreInvalidSpec
-       }
-       // create SQL store
-       kvs := new(SQLStore)
-
-       // connect to SQL database
-       kvs.db, err = util.DbPool.Connect(connect)
-       if err != nil {
-               return nil, err
-       }
-       // get number of key/value pairs (as a check for existing table)
-       row := kvs.db.QueryRow("select count(*) from store")
-       var num int
-       if row.Scan(&num) != nil {
-               return nil, ErrStoreNotAvailable
-       }
-       return kvs, nil
-}
-
-// Put a key/value pair into the store
-func (s *SQLStore) Put(key string, value string) error {
-       _, err := s.db.Exec("insert into store(key,value) values(?,?)", key, 
value)
-       return err
-}
-
-// Get a value for a given key from store
-func (s *SQLStore) Get(key string) (value string, err error) {
-       row := s.db.QueryRow("select value from store where key=?", key)
-       err = row.Scan(&value)
-       return
-}
-
-// List all keys in store
-func (s *SQLStore) List() (keys []string, err error) {
-       var (
-               rows *sql.Rows
-               key  string
-       )
-       keys = make([]string, 0)
-       rows, err = s.db.Query("select key from store")
-       if err == nil {
-               for rows.Next() {
-                       if err = rows.Scan(&key); err != nil {
-                               break
-                       }
-                       keys = append(keys, key)
-               }
-       }
-       return
-}
diff --git a/src/gnunet/util/database.go b/src/gnunet/service/store/database.go
similarity index 75%
rename from src/gnunet/util/database.go
rename to src/gnunet/service/store/database.go
index 852862b..2b94122 100644
--- a/src/gnunet/util/database.go
+++ b/src/gnunet/service/store/database.go
@@ -16,12 +16,13 @@
 //
 // SPDX-License-Identifier: AGPL3.0-or-later
 
-package util
+package store
 
 import (
        "context"
        "database/sql"
        "fmt"
+       "gnunet/util"
        "os"
        "strings"
 
@@ -31,8 +32,8 @@ import (
 
 // Error messages related to databases
 var (
-       ErrSQLInvalidDatabaseSpec = fmt.Errorf("Invalid database specification")
-       ErrSQLNoDatabase          = fmt.Errorf("Database not found")
+       ErrSQLInvalidDatabaseSpec = fmt.Errorf("invalid database specification")
+       ErrSQLNoDatabase          = fmt.Errorf("database not found")
 )
 
 //----------------------------------------------------------------------
@@ -40,35 +41,35 @@ var (
 // on the same instance, managed by the database pool.
 //----------------------------------------------------------------------
 
-// DbConn is a database connection suitable for executing SQL commands.
-type DbConn struct {
-       conn *sql.Conn // connection to database instance
-       pool *dbPool   // reference to managng pool
-       key  string    // database identifier (connect string)
+// DBConn is a database connection suitable for executing SQL commands.
+type DBConn struct {
+       conn   *sql.Conn // connection to database instance
+       key    string    // database connect string (identifier for pool)
+       engine string    // database engine
 }
 
 // Close database connection.
-func (db *DbConn) Close() (err error) {
+func (db *DBConn) Close() (err error) {
        if err = db.conn.Close(); err != nil {
                return
        }
-       err = db.pool.remove(db.key)
+       err = DBPool.remove(db.key)
        return
 }
 
 // QueryRow returns a single record for a query
-func (db *DbConn) QueryRow(query string, args ...any) *sql.Row {
-       return db.conn.QueryRowContext(db.pool.ctx, query, args...)
+func (db *DBConn) QueryRow(query string, args ...any) *sql.Row {
+       return db.conn.QueryRowContext(DBPool.ctx, query, args...)
 }
 
 // Query returns all matching records for a query
-func (db *DbConn) Query(query string, args ...any) (*sql.Rows, error) {
-       return db.conn.QueryContext(db.pool.ctx, query, args...)
+func (db *DBConn) Query(query string, args ...any) (*sql.Rows, error) {
+       return db.conn.QueryContext(DBPool.ctx, query, args...)
 }
 
 // Exec a SQL statement
-func (db *DbConn) Exec(query string, args ...any) (sql.Result, error) {
-       return db.conn.ExecContext(db.pool.ctx, query, args...)
+func (db *DBConn) Exec(query string, args ...any) (sql.Result, error) {
+       return db.conn.ExecContext(DBPool.ctx, query, args...)
 }
 
 // TODO: add more SQL methods
@@ -80,11 +81,11 @@ func (db *DbConn) Exec(query string, args ...any) 
(sql.Result, error) {
 
 // global instance for the database pool (singleton)
 var (
-       DbPool *dbPool
+       DBPool *dbPool
 )
 
-// DbPoolEntry holds information about a database instance.
-type DbPoolEntry struct {
+// DBPoolEntry holds information about a database instance.
+type DBPoolEntry struct {
        db      *sql.DB // reference to the database engine
        refs    int     // number of open connections (reference count)
        connect string  // SQL connect string
@@ -93,16 +94,16 @@ type DbPoolEntry struct {
 // package initialization
 func init() {
        // construct database pool
-       DbPool = new(dbPool)
-       DbPool.insts = NewMap[string, *DbPoolEntry]()
-       DbPool.ctx, DbPool.cancel = context.WithCancel(context.Background())
+       DBPool = new(dbPool)
+       DBPool.insts = util.NewMap[string, *DBPoolEntry]()
+       DBPool.ctx, DBPool.cancel = context.WithCancel(context.Background())
 }
 
 // dbPool keeps a mapping between connect string and database instance
 type dbPool struct {
-       ctx    context.Context            // connection context
-       cancel context.CancelFunc         // cancel function
-       insts  *Map[string, *DbPoolEntry] // map of database instances
+       ctx    context.Context                 // connection context
+       cancel context.CancelFunc              // cancel function
+       insts  *util.Map[string, *DBPoolEntry] // map of database instances
 }
 
 // remove a database instance from the pool based on its connect string.
@@ -127,7 +128,7 @@ func (p *dbPool) remove(key string) error {
 // Connect to a SQL database (various types and flavors):
 // The 'spec' option defines the arguments required to connect to a database;
 // the meaning and format of the arguments depends on the specific SQL 
database.
-// The arguments are seperated by the '+' character; the first (and mandatory)
+// The arguments are separated by the '+' character; the first (and mandatory)
 // argument defines the SQL database type. Other arguments depend on the value
 // of this first argument.
 // The following SQL types are implemented:
@@ -136,12 +137,13 @@ func (p *dbPool) remove(key string) error {
 // * 'mysql':   A MySQL-compatible database; the second argument specifies the
 //              information required to log into the database (e.g.
 //              "[user[:passwd]@][proto[(addr)]]/dbname[?param1=value1&...]").
-func (p *dbPool) Connect(spec string) (db *DbConn, err error) {
+func (p *dbPool) Connect(spec string) (db *DBConn, err error) {
        err = p.insts.Process(func() error {
                // check if we have a connection to this database.
+               db = new(DBConn)
                inst, ok := p.insts.Get(spec)
                if !ok {
-                       inst = new(DbPoolEntry)
+                       inst = new(DBPoolEntry)
                        inst.refs = 0
                        inst.connect = spec
 
@@ -152,7 +154,8 @@ func (p *dbPool) Connect(spec string) (db *DbConn, err 
error) {
                                return ErrSQLInvalidDatabaseSpec
                        }
                        // create database object
-                       switch specs[0] {
+                       db.engine = specs[0]
+                       switch db.engine {
                        case "sqlite3":
                                // check if the database file exists
                                var fi os.FileInfo
@@ -172,12 +175,10 @@ func (p *dbPool) Connect(spec string) (db *DbConn, err 
error) {
                        }
                        // save database in pool
                        p.insts.Put(spec, inst)
-                       ok = true
                }
                // increment reference count
                inst.refs++
                // return a new connection to the database.
-               db = new(DbConn)
                db.conn, err = inst.db.Conn(p.ctx)
                return err
        }, false)
diff --git a/src/gnunet/service/dht/dhtstore_test.go 
b/src/gnunet/service/store/dhtstore_test.go
similarity index 92%
rename from src/gnunet/service/dht/dhtstore_test.go
rename to src/gnunet/service/store/dhtstore_test.go
index d9fc1d0..14da6ff 100644
--- a/src/gnunet/service/dht/dhtstore_test.go
+++ b/src/gnunet/service/store/dhtstore_test.go
@@ -16,37 +16,36 @@
 //
 // SPDX-License-Identifier: AGPL3.0-or-later
 
-package dht
+package store
 
 import (
        "encoding/hex"
-       "gnunet/config"
        "gnunet/crypto"
-       "gnunet/service"
+       "gnunet/enums"
        "gnunet/service/dht/blocks"
+       "gnunet/util"
        "math/rand"
        "testing"
 )
 
 // test constants
 const (
-       fsNumBlocks = 5
+       fsNumBlocks = 10
 )
 
 // TestDHTFileStore generates 'fsNumBlocks' fully-random blocks
 // and stores them under their SHA512 key. It than retrieves
 // each block from storage and checks for matching hash.
 func TestDHTFilesStore(t *testing.T) {
-
        // test configuration
-       cfg := make(config.ParameterConfig)
+       cfg := make(util.ParameterSet)
        cfg["mode"] = "file"
        cfg["cache"] = false
        cfg["path"] = "/var/lib/gnunet/dht/store"
        cfg["maxGB"] = 10
 
        // create file store
-       fs, err := service.NewFileStore(cfg)
+       fs, err := NewFileStore(cfg)
        if err != nil {
                t.Fatal(err)
        }
@@ -62,7 +61,7 @@ func TestDHTFilesStore(t *testing.T) {
                val := blocks.NewGenericBlock(buf)
                // generate associated key
                k := crypto.Hash(buf).Bits
-               key := blocks.NewGenericQuery(k)
+               key := blocks.NewGenericQuery(k, enums.BLOCK_TYPE_ANY, 0)
 
                // store block
                if err := fs.Put(key, val); err != nil {
diff --git a/src/gnunet/service/store/store.go 
b/src/gnunet/service/store/store.go
new file mode 100644
index 0000000..d5ef05d
--- /dev/null
+++ b/src/gnunet/service/store/store.go
@@ -0,0 +1,278 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix  >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package store
+
+import (
+       "context"
+       "database/sql"
+       _ "embed" // use embedded filesystem
+       "errors"
+       "fmt"
+       "gnunet/service/dht/blocks"
+       "gnunet/util"
+
+       redis "github.com/go-redis/redis/v8"
+)
+
+// Error messages related to the key/value-store implementations
+var (
+       ErrStoreInvalidSpec  = fmt.Errorf("invalid Store specification")
+       ErrStoreUnknown      = fmt.Errorf("unknown Store type")
+       ErrStoreNotAvailable = fmt.Errorf("store not available")
+       ErrStoreNoApprox     = fmt.Errorf("no approx search for store defined")
+       ErrStoreNoList       = fmt.Errorf("no key listing for store defined")
+)
+
+//------------------------------------------------------------
+// Generic storage interface. Can be used for persistent or
+// transient (caching) storage of key/value data.
+//------------------------------------------------------------
+
+// Store is a key/value storage where the type of the key is either
+// a SHA512 hash value or a string and the value is either a DHT
+// block or a string. It is possiblle to mix any key/value types,
+// but not used in this implementation.
+type Store[K, V any] interface {
+       // Put value into storage under given key
+       Put(key K, val V) error
+
+       // Get value with given key from storage
+       Get(key K) (V, error)
+
+       // GetApprox returns the best-matching value with given key from storage
+       // that is not excluded.
+       GetApprox(key K, excl func(V) bool) (V, any, error)
+
+       // List all store keys
+       List() ([]K, error)
+
+       // Close store
+       Close() error
+}
+
+//------------------------------------------------------------
+// Types for custom store requirements
+//------------------------------------------------------------
+
+// DHTStore for DHT queries and blocks
+type DHTStore Store[blocks.Query, blocks.Block]
+
+// KVStore for key/value string pairs
+type KVStore Store[string, string]
+
+//------------------------------------------------------------
+// NewDHTStore creates a new storage handler with given spec
+// for use with DHT queries and blocks
+func NewDHTStore(spec util.ParameterSet) (DHTStore, error) {
+       // get the mode parameter
+       mode, ok := util.GetParam[string](spec, "mode")
+       if !ok {
+               return nil, ErrStoreInvalidSpec
+       }
+       switch mode {
+       //------------------------------------------------------------------
+       // File-base storage
+       //------------------------------------------------------------------
+       case "file":
+               return NewFileStore(spec)
+       }
+       return nil, ErrStoreUnknown
+}
+
+//------------------------------------------------------------
+// NewKVStore creates a new storage handler with given spec
+// for use with key/value string pairs.
+func NewKVStore(spec util.ParameterSet) (KVStore, error) {
+       // get the mode parameter
+       mode, ok := util.GetParam[string](spec, "mode")
+       if !ok {
+               return nil, ErrStoreInvalidSpec
+       }
+       switch mode {
+       //--------------------------------------------------------------
+       // Redis service
+       //--------------------------------------------------------------
+       case "redis":
+               return NewRedisStore(spec)
+
+       //--------------------------------------------------------------
+       // SQL database service
+       //--------------------------------------------------------------
+       case "sql":
+               return NewSQLStore(spec)
+       }
+       return nil, errors.New("unknown storage mechanism")
+}
+
+//------------------------------------------------------------
+// Redis: only use for caching purposes on key/value strings
+//------------------------------------------------------------
+
+// RedisStore uses a (local) Redis server for key/value storage
+type RedisStore struct {
+       client *redis.Client // client connection
+       db     int           // index to database
+}
+
+// NewRedisStore creates a Redis service client instance.
+func NewRedisStore(spec util.ParameterSet) (s KVStore, err error) {
+       // get connection parameters
+       addr, ok := util.GetParam[string](spec, "addr")
+       if !ok {
+               return nil, ErrStoreInvalidSpec
+       }
+       passwd, ok := util.GetParam[string](spec, "passwd")
+       if !ok {
+               passwd = ""
+       }
+       db, ok := util.GetParam[int](spec, "db")
+       if !ok {
+               return nil, ErrStoreInvalidSpec
+       }
+
+       // create new Redis store
+       kvs := new(RedisStore)
+       kvs.db = db
+       kvs.client = redis.NewClient(&redis.Options{
+               Addr:     addr,
+               Password: passwd,
+               DB:       db,
+       })
+       if kvs.client == nil {
+               err = ErrStoreNotAvailable
+       }
+       s = kvs
+       return
+}
+
+// Put value into storage under given key
+func (s *RedisStore) Put(key string, value string) (err error) {
+       return s.client.Set(context.TODO(), key, value, 0).Err()
+}
+
+// Get value with given key from storage
+func (s *RedisStore) Get(key string) (value string, err error) {
+       return s.client.Get(context.TODO(), key).Result()
+}
+
+// GetApprox returns the best-matching value for given key from storage
+func (s *RedisStore) GetApprox(key string, crit func(string) bool) (value 
string, vkey any, err error) {
+       return "", "", ErrStoreNoApprox
+}
+
+// List all keys in store
+func (s *RedisStore) List() (keys []string, err error) {
+       var (
+               crs  uint64
+               segm []string
+               ctx  = context.TODO()
+       )
+       keys = make([]string, 0)
+       for {
+               segm, crs, err = s.client.Scan(ctx, crs, "*", 10).Result()
+               if err != nil {
+                       return
+               }
+               if crs == 0 {
+                       break
+               }
+               keys = append(keys, segm...)
+       }
+       return
+}
+
+// Close redis connection
+func (s *RedisStore) Close() error {
+       return s.client.Close()
+}
+
+//------------------------------------------------------------
+// SQL-based key-value-store
+//------------------------------------------------------------
+
+// SQLStore for generic SQL database handling
+type SQLStore struct {
+       db *DBConn
+}
+
+// NewSQLStore creates a new SQL-based key/value store.
+func NewSQLStore(spec util.ParameterSet) (s KVStore, err error) {
+       // get connection parameters
+       connect, ok := util.GetParam[string](spec, "connect")
+       if !ok {
+               return nil, ErrStoreInvalidSpec
+       }
+       // create SQL store
+       kvs := new(SQLStore)
+
+       // connect to SQL database
+       kvs.db, err = DBPool.Connect(connect)
+       if err != nil {
+               return nil, err
+       }
+       // get number of key/value pairs (as a check for existing table)
+       row := kvs.db.QueryRow("select count(*) from store")
+       var num int
+       if row.Scan(&num) != nil {
+               return nil, ErrStoreNotAvailable
+       }
+       return kvs, nil
+}
+
+// Put a key/value pair into the store
+func (s *SQLStore) Put(key string, value string) error {
+       _, err := s.db.Exec("insert into store(key,value) values(?,?)", key, 
value)
+       return err
+}
+
+// Get a value for a given key from store
+func (s *SQLStore) Get(key string) (value string, err error) {
+       row := s.db.QueryRow("select value from store where key=?", key)
+       err = row.Scan(&value)
+       return
+}
+
+// GetApprox returns the best-matching value for given key from storage
+func (s *SQLStore) GetApprox(key string, crit func(string) bool) (value 
string, vkey any, err error) {
+       return "", "", ErrStoreNoApprox
+}
+
+// List all keys in store
+func (s *SQLStore) List() (keys []string, err error) {
+       var (
+               rows *sql.Rows
+               key  string
+       )
+       keys = make([]string, 0)
+       rows, err = s.db.Query("select key from store")
+       if err == nil {
+               for rows.Next() {
+                       if err = rows.Scan(&key); err != nil {
+                               break
+                       }
+                       keys = append(keys, key)
+               }
+       }
+       return
+}
+
+// Close redis connection
+func (s *SQLStore) Close() error {
+       return s.db.Close()
+}
diff --git a/src/gnunet/service/store/store_fs.go 
b/src/gnunet/service/store/store_fs.go
new file mode 100644
index 0000000..a33c317
--- /dev/null
+++ b/src/gnunet/service/store/store_fs.go
@@ -0,0 +1,287 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix  >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package store
+
+import (
+       "encoding/hex"
+       "fmt"
+       "gnunet/service/dht/blocks"
+       "gnunet/util"
+       "io/ioutil"
+       "os"
+
+       "github.com/bfix/gospel/logger"
+       "github.com/bfix/gospel/math"
+)
+
+//============================================================
+// Filesystem-based storage
+//============================================================
+
+// FileStore implements a filesystem-based storage mechanism for
+// DHT queries and blocks.
+type FileStore struct {
+       path      string            // storage path
+       cache     bool              // storage works as cache
+       args      util.ParameterSet // arguments / settings
+       totalSize uint64            // total storage size (logical, not 
physical)
+
+       // storage-mode metadata
+       meta     *FileMetaDB // database for metadata
+       maxSpace int         // max. storage space in GB
+
+       // cache-mode metadata
+       cacheMeta []*FileMetadata // cached metadata
+       wrPos     int             // write position in cyclic list
+       size      int             // size of cache (number of entries)
+}
+
+// NewFileStore instantiates a new file storage.
+func NewFileStore(spec util.ParameterSet) (DHTStore, error) {
+       // create file store handler
+       fs := new(FileStore)
+       fs.args = spec
+
+       // get parameter
+       var ok bool
+       if fs.path, ok = util.GetParam[string](spec, "path"); !ok {
+               return nil, ErrStoreInvalidSpec
+       }
+       if fs.cache, ok = util.GetParam[bool](spec, "cache"); !ok {
+               fs.cache = false
+       }
+
+       // setup file store depending on mode (storage/cache)
+       if fs.cache {
+               // remove old cache content
+               os.RemoveAll(fs.path)
+               // get number of cache entries
+               if fs.size, ok = util.GetParam[int](spec, "num"); !ok {
+                       // defaults to 1000 entries
+                       fs.size = 1000
+               }
+               fs.cacheMeta = make([]*FileMetadata, fs.size)
+       } else {
+               // connect to metadata database
+               var err error
+               if fs.meta, err = OpenMetaDB(fs.path); err != nil {
+                       return nil, err
+               }
+               // normal storage is limited by quota (default: 10GB)
+               if fs.maxSpace, ok = util.GetParam[int](spec, "maxGB"); !ok {
+                       fs.maxSpace = 10
+               }
+       }
+       return fs, nil
+}
+
+// Close file storage.
+func (s *FileStore) Close() (err error) {
+       if !s.cache {
+               // close database connection
+               err = s.meta.Close()
+       }
+       return
+}
+
+// Put block into storage under given key
+func (s *FileStore) Put(query blocks.Query, block blocks.Block) (err error) {
+       // check for free space
+       if !s.cache {
+               if int(s.totalSize>>30) > s.maxSpace {
+                       // drop a significant number of blocks
+                       s.prune(20)
+               }
+       }
+       // get parameters
+       btype := query.Type()
+       expire := block.Expire()
+
+       // get path and filename from key
+       path, fname := s.expandPath(query.Key().Bits)
+       // make sure the path exists
+       if err = os.MkdirAll(path, 0755); err != nil {
+               return
+       }
+       // write to file for storage
+       var fp *os.File
+       bd := block.Data()
+       if fp, err = os.Create(path + "/" + fname); err == nil {
+               defer fp.Close()
+               // write block data
+               if _, err = fp.Write(bd); err != nil {
+                       return
+               }
+       }
+       // compile metadata
+       now := util.AbsoluteTimeNow()
+       meta := &FileMetadata{
+               key:       query.Key().Bits,
+               size:      uint64(len(bd)),
+               btype:     btype,
+               expires:   expire,
+               stored:    now,
+               lastUsed:  now,
+               usedCount: 1,
+       }
+       if s.cache {
+               // store in cyclic list
+               s.cacheMeta[s.wrPos] = meta
+               s.wrPos = (s.wrPos + 1) % s.size
+       } else {
+               // store metadata in database
+               if err = s.meta.Store(meta); err != nil {
+                       return
+               }
+               // add to total storage size
+               s.totalSize += meta.size
+       }
+       return
+}
+
+// Get block with given key from storage
+func (s *FileStore) Get(query blocks.Query) (block blocks.Block, err error) {
+       // check if we have metadata for the query
+       key := query.Key().Bits
+       btype := query.Type()
+       var md *FileMetadata
+       if md, err = s.meta.Get(key, btype); err != nil || md == nil {
+               return
+       }
+       // check for expired entry
+       if md.expires.Expired() {
+               err = s.dropFile(md)
+               return
+       }
+       // mark the block as newly used
+       if err = s.meta.Used(key, btype); err != nil {
+               return
+       }
+       return s.readBlock(query.Key().Bits)
+}
+
+// GetApprox returns the best-matching value with given key from storage
+// that is not excluded
+func (s *FileStore) GetApprox(query blocks.Query, excl func(blocks.Block) 
bool) (block blocks.Block, key any, err error) {
+       var bestKey []byte
+       var bestBlk blocks.Block
+       var bestDist *math.Int
+       // distance function
+       dist := func(a, b []byte) *math.Int {
+               c := make([]byte, len(a))
+               for i := range a {
+                       c[i] = a[i] ^ b[i]
+               }
+               return math.NewIntFromBytes(c)
+       }
+       // iterate over all keys
+       check := func(md *FileMetadata) {
+               // check for better match
+               d := dist(md.key, query.Key().Bits)
+               if bestKey == nil || d.Cmp(bestDist) < 0 {
+                       // we might have a match. check block for exclusion
+                       block, err = s.readBlock(md.key)
+                       if err != nil {
+                               logger.Printf(logger.ERROR, "[dhtstore] failed 
to retrieve blok for %s", hex.EncodeToString(md.key))
+                               return
+                       }
+                       if excl(block) {
+                               return
+                       }
+                       // remember best match
+                       bestKey = md.key
+                       bestBlk = block
+                       bestDist = d
+               }
+       }
+       if err = s.meta.Traverse(check); err != nil {
+               return
+       }
+       if bestBlk != nil {
+               // mark the block as newly used
+               if err = s.meta.Used(bestKey, bestBlk.Type()); err != nil {
+                       return
+               }
+       }
+       return bestBlk, bestDist, nil
+}
+
+// Get a list of all stored block keys (generic query).
+func (s *FileStore) List() ([]blocks.Query, error) {
+       return nil, ErrStoreNoList
+}
+
+// read block from storage for given key
+func (s *FileStore) readBlock(key []byte) (block blocks.Block, err error) {
+       // get path and filename from key
+       path, fname := s.expandPath(key)
+       // read file content (block data)
+       var file *os.File
+       if file, err = os.Open(path + "/" + fname); err != nil {
+               return
+       }
+       defer file.Close()
+       // read block data
+       var data []byte
+       if data, err = ioutil.ReadAll(file); err == nil {
+               block = blocks.NewGenericBlock(data)
+       }
+       return
+}
+
+// expandPath returns the full path to the file for given key.
+func (s *FileStore) expandPath(key []byte) (string, string) {
+       h := hex.EncodeToString(key)
+       return fmt.Sprintf("%s/%s/%s", s.path, h[:2], h[2:4]), h[4:]
+}
+
+// Prune list of file headers so we drop at least n entries.
+// returns number of removed entries.
+func (s *FileStore) prune(n int) (del int) {
+       // collect obsolete records
+       obsolete, err := s.meta.Obsolete(n)
+       if err != nil {
+               logger.Println(logger.ERROR, "[FileStore] failed to collect 
obsolete records: "+err.Error())
+               return
+       }
+       for _, md := range obsolete {
+               if err := s.dropFile(md); err != nil {
+                       return
+               }
+               del++
+       }
+       return
+}
+
+// drop file removes a file from metadatabase and the physical storage.
+func (s *FileStore) dropFile(md *FileMetadata) (err error) {
+       // adjust total size
+       s.totalSize -= md.size
+       // remove from database
+       if err = s.meta.Drop(md.key, md.btype); err != nil {
+               logger.Printf(logger.ERROR, "[store] can't remove metadata 
(%s,%d): %s", md.key, md.btype, err.Error())
+               return
+       }
+       // remove from filesystem
+       path := fmt.Sprintf("%s/%s/%s/%s", s.path, md.key[:2], md.key[2:4], 
md.key[4:])
+       if err = os.Remove(path); err != nil {
+               logger.Printf(logger.ERROR, "[store] can't remove file %s: %s", 
path, err.Error())
+       }
+       return
+}
diff --git a/src/gnunet/service/store/store_fs_meta.go 
b/src/gnunet/service/store/store_fs_meta.go
new file mode 100644
index 0000000..414921c
--- /dev/null
+++ b/src/gnunet/service/store/store_fs_meta.go
@@ -0,0 +1,174 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix  >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package store
+
+import (
+       "database/sql"
+       _ "embed"
+       "gnunet/util"
+       "os"
+)
+
+//============================================================
+// Metadata handling for file storage
+//============================================================
+
+// FileMetadata holds information about a file (raw block data)
+// and is stored in a SQL database for faster access.
+type FileMetadata struct {
+       key       []byte            // storage key
+       size      uint64            // size of file
+       btype     uint16            // block type
+       stored    util.AbsoluteTime // time added to store
+       expires   util.AbsoluteTime // expiration time
+       lastUsed  util.AbsoluteTime // time last used
+       usedCount uint64            // usage count
+}
+
+//------------------------------------------------------------
+// Metadata database: A SQLite3 database to hold metadata about
+// blocks in file storage
+//------------------------------------------------------------
+
+//go:embed store_fs_meta.sql
+var initScript []byte
+
+// FileMetaDB is a SQLite3 database for block metadata
+type FileMetaDB struct {
+       conn *DBConn // database connection
+}
+
+// OpenMetaDB opens a metadata database in the given path. The name of the
+// database is "access.db".
+func OpenMetaDB(path string) (db *FileMetaDB, err error) {
+       // connect to database
+       dbFile := path + "/acccess.db"
+       if _, err = os.Stat(path + "/acccess.db"); err != nil {
+               var file *os.File
+               if file, err = os.Create(dbFile); err != nil {
+                       return
+               }
+               file.Close()
+       }
+       db = new(FileMetaDB)
+       if db.conn, err = DBPool.Connect("sqlite3:" + dbFile); err != nil {
+               return
+       }
+       // check for initialized database
+       res := db.conn.QueryRow("select name from sqlite_master where 
type='table' and name='meta'")
+       var s string
+       if res.Scan(&s) != nil {
+               // initialize database
+               if _, err = db.conn.Exec(string(initScript)); err != nil {
+                       return
+               }
+       }
+       return
+}
+
+// Store metadata in database: creates or updates a record for the metadata
+// in the database; primary key is the query key
+func (db *FileMetaDB) Store(md *FileMetadata) (err error) {
+       sql := "replace into 
meta(qkey,btype,size,stored,expires,lastUsed,usedCount) values(?,?,?,?,?,?,?)"
+       _, err = db.conn.Exec(sql, md.key, md.btype, md.size, 
md.stored.Epoch(), md.expires.Epoch(), md.lastUsed.Epoch(), md.usedCount)
+       return
+}
+
+// Get block metadata from database
+func (db *FileMetaDB) Get(key []byte, btype uint16) (md *FileMetadata, err 
error) {
+       md = new(FileMetadata)
+       md.key = util.Clone(key)
+       md.btype = btype
+       stmt := "select size,stored,expires,lastUsed,usedCount from meta where 
qkey=? and btype=?"
+       row := db.conn.QueryRow(stmt, key, btype)
+       var st, exp, lu uint64
+       if err = row.Scan(&md.size, &st, &exp, &lu, &md.usedCount); err == 
sql.ErrNoRows {
+               md = nil
+               err = nil
+       } else {
+               md.stored.Val = st * 1000000
+               md.expires.Val = exp * 1000000
+               md.lastUsed.Val = lu * 1000000
+       }
+       return
+}
+
+// Drop metadata for block from database
+func (db *FileMetaDB) Drop(key []byte, btype uint16) error {
+       _, err := db.conn.Exec("delete from meta where qkey=? and btype=?", 
key, btype)
+       return err
+}
+
+// Used a block from store: increment usage count and lastUsed time.
+func (db *FileMetaDB) Used(key []byte, btype uint16) error {
+       _, err := db.conn.Exec("update meta set 
usedCount=usedCount+1,lastUsed=unixepoch() where qkey=? and btype=?", key, 
btype)
+       return err
+}
+
+// Obsolete collects records from the meta database that are considered
+// "removable". Entries are rated by the value of "(lifetime * size) / 
usedCount"
+func (db *FileMetaDB) Obsolete(n int) (removable []*FileMetadata, err error) {
+       // get obsolete records from database
+       rate := "(unixepoch()-unixepoch(stored))*size/usedCount"
+       stmt := "select qkey,btype from meta order by " + rate + " limit ?"
+       var rows *sql.Rows
+       if rows, err = db.conn.Query(stmt, n); err != nil {
+               return
+       }
+       var md *FileMetadata
+       for rows.Next() {
+               var st, exp, lu uint64
+               if err = rows.Scan(&md.key, &md.btype, &md.size, &st, &exp, 
&lu, &md.usedCount); err != nil {
+                       return
+               }
+               md.stored.Val = st * 1000000
+               md.expires.Val = exp * 1000000
+               md.lastUsed.Val = lu * 1000000
+               removable = append(removable, md)
+       }
+       return
+}
+
+// Traverse metadata records and call function on each record
+func (db *FileMetaDB) Traverse(f func(*FileMetadata)) error {
+       sql := "select qkey,btype,size,stored,expires,lastUsed,usedCount from 
meta"
+       rows, err := db.conn.Query(sql)
+       if err != nil {
+               return err
+       }
+       md := new(FileMetadata)
+       for rows.Next() {
+               var st, exp, lu uint64
+               err = rows.Scan(&md.key, &md.btype, &md.size, &st, &exp, &lu, 
&md.usedCount)
+               if err != nil {
+                       return err
+               }
+               md.stored.Val = st * 1000000
+               md.expires.Val = exp * 1000000
+               md.lastUsed.Val = lu * 1000000
+               // call process function
+               f(md)
+       }
+       return nil
+}
+
+// Close metadata database
+func (db *FileMetaDB) Close() error {
+       return db.conn.Close()
+}
diff --git a/src/gnunet/service/store/store_fs_meta.sql 
b/src/gnunet/service/store/store_fs_meta.sql
new file mode 100644
index 0000000..a2692ab
--- /dev/null
+++ b/src/gnunet/service/store/store_fs_meta.sql
@@ -0,0 +1,29 @@
+-- This file is part of gnunet-go, a GNUnet-implementation in Golang.
+-- Copyright (C) 2019-2022 Bernd Fix  >Y<
+--
+-- gnunet-go is free software: you can redistribute it and/or modify it
+-- under the terms of the GNU Affero General Public License as published
+-- by the Free Software Foundation, either version 3 of the License,
+-- or (at your option) any later version.
+--
+-- gnunet-go is distributed in the hope that it will be useful, but
+-- WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+-- Affero General Public License for more details.
+--
+-- You should have received a copy of the GNU Affero General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- SPDX-License-Identifier: AGPL3.0-or-later
+
+create table meta (
+    qkey      blob,         -- key (SHA512 hash)
+       btype     integer,      -- block type
+    size      integer,      -- size of file
+       stored    integer,      -- time added to store
+       expires   integer,      -- expiration time
+       lastUsed  integer,      -- time last used
+       usedCount integer,      -- usage count
+
+       unique(qkey,btype)      -- unique key in database
+);
diff --git a/src/gnunet/test/gnunet-dhtu/main.go 
b/src/gnunet/test/gnunet-dhtu/main.go
deleted file mode 100644
index 2a49b9c..0000000
--- a/src/gnunet/test/gnunet-dhtu/main.go
+++ /dev/null
@@ -1,206 +0,0 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2022 Bernd Fix  >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package main
-
-import (
-       "context"
-       "flag"
-       "fmt"
-       "gnunet/config"
-       "gnunet/core"
-       "gnunet/message"
-       "gnunet/service"
-       "gnunet/service/dht"
-       "gnunet/transport"
-       "gnunet/util"
-       "log"
-       "net/rpc"
-       "time"
-
-       "github.com/bfix/gospel/logger"
-)
-
-//----------------------------------------------------------------------
-// Test Go node with DHTU GNUnet nodes
-//
-// N.B.: THIS TEST ONLY COVERS THE BASIC MESSAGE EXCHANGE LEVEL; NO
-// MESSAGE PROCESSING EXCEPT FOR HELLO MESSAGES WILL TAKE PLACE.
-//----------------------------------------------------------------------
-
-func main() {
-       // handle command-line arguments
-       var remoteAddr string
-       var cfgFile string
-       flag.StringVar(&cfgFile, "c", "gnunet-config.json", "configuration 
file")
-       flag.StringVar(&remoteAddr, "a", "", "address of remote node")
-       flag.Parse()
-
-       // read configuration file and set missing arguments.
-       if err := config.ParseConfig(cfgFile); err != nil {
-               logger.Printf(logger.ERROR, "[node] Invalid configuration file: 
%s\n", err.Error())
-               return
-       }
-
-       // convert arguments
-       var rAddr *util.Address
-       var err error
-       if rAddr, err = util.ParseAddress(remoteAddr); err != nil {
-               logger.Println(logger.ERROR, err.Error())
-               return
-       }
-
-       // setup execution context
-       ctx, cancel := context.WithCancel(context.Background())
-       defer func() {
-               cancel()
-               time.Sleep(time.Second)
-       }()
-
-       // create and run node
-       node, err := NewTestNode(ctx)
-       if err != nil {
-               logger.Println(logger.ERROR, err.Error())
-               return
-       }
-       defer node.Shutdown()
-
-       // show our HELLO URL
-       ep := config.Cfg.Local.Endpoints[0]
-       as := fmt.Sprintf("%s://%s:%d", ep.Network, ep.Address, ep.Port)
-       listen, err := util.ParseAddress(as)
-       if err != nil {
-               logger.Println(logger.ERROR, err.Error())
-               return
-       }
-       aList := []*util.Address{listen}
-       logger.Println(logger.INFO, "[node] --> "+node.HelloURL(aList))
-
-       // send HELLO to bootstrap address
-       if err = node.SendHello(ctx, rAddr); err != nil && err != 
transport.ErrEndpMaybeSent {
-               logger.Println(logger.ERROR, "[node] failed to send HELLO: 
"+err.Error())
-               return
-       }
-
-       // run forever
-       var ch chan struct{}
-       <-ch
-}
-
-//----------------------------------------------------------------------
-// create and run a node with given spec
-//----------------------------------------------------------------------
-
-type TestNode struct {
-       id   int
-       peer *core.Peer
-       core *core.Core
-       addr *util.Address
-}
-
-func (n *TestNode) Shutdown() {
-       n.core.Shutdown()
-}
-func (n *TestNode) HelloURL(a []*util.Address) string {
-       hd, err := n.peer.HelloData(message.HelloAddressExpiration, a)
-       if err != nil {
-               return ""
-       }
-       return hd.URL()
-}
-
-func (n *TestNode) SendHello(ctx context.Context, addr *util.Address) error {
-       return n.core.SendHello(ctx, addr)
-}
-
-func NewTestNode(ctx context.Context) (node *TestNode, err error) {
-
-       // create test node
-       node = new(TestNode)
-       node.id = util.NextID()
-
-       // create core service
-       if node.core, err = core.NewCore(ctx, config.Cfg.Local); err != nil {
-               return
-       }
-       node.peer = node.core.Peer()
-       logger.Printf(logger.INFO, "[node] Node %s starting", node.peer.GetID())
-
-       // start a new DHT service
-       dht, err := dht.NewService(ctx, node.core)
-       if err != nil {
-               log.Fatal(err)
-       }
-
-       // start JSON-RPC server on request
-       var rpc *rpc.Server
-       if rpc, err = service.StartRPC(ctx, config.Cfg.RPC.Endpoint); err != 
nil {
-               logger.Printf(logger.ERROR, "[node] RPC failed to start: %s", 
err.Error())
-               return
-       }
-       dht.InitRPC(rpc)
-
-       // start listening on the network
-       list, err := node.core.Addresses()
-       if err != nil {
-               log.Fatal(err)
-       }
-       for _, addr := range list {
-               s := addr.Network() + "://" + addr.String()
-               if node.addr, err = util.ParseAddress(s); err != nil {
-                       continue
-               }
-               logger.Printf(logger.INFO, "[node] Listening on %s", s)
-       }
-
-       // register as event listener
-       incoming := make(chan *core.Event)
-       node.core.Register(config.Cfg.Local.Name, core.NewListener(incoming, 
nil))
-
-       // heart beat
-       tick := time.NewTicker(5 * time.Minute)
-
-       // run event handler
-       go func() {
-               for {
-                       select {
-                       // show incoming event
-                       case ev := <-incoming:
-                               switch ev.ID {
-                               case core.EV_CONNECT:
-                                       logger.Printf(logger.INFO, "[node] <<< 
Peer %s connected", ev.Peer)
-                               case core.EV_DISCONNECT:
-                                       logger.Printf(logger.INFO, "[node] <<< 
Peer %s diconnected", ev.Peer)
-                               case core.EV_MESSAGE:
-                                       logger.Printf(logger.INFO, "[node] <<< 
Msg from %s of type %d", ev.Peer, ev.Msg.Header().MsgType)
-                                       logger.Printf(logger.INFO, "[node] <<<  
  --> %s", ev.Msg.String())
-                               }
-
-                       // handle termination signal
-                       case <-ctx.Done():
-                               logger.Println(logger.INFO, "[node] Shutting 
down node")
-                               return
-
-                       // handle heart beat
-                       case now := <-tick.C:
-                               logger.Printf(logger.INFO, "[node] Heart beat 
at %s", now.String())
-                       }
-               }
-       }()
-       return
-}
diff --git a/src/gnunet/transport/endpoint.go b/src/gnunet/transport/endpoint.go
index d98776a..a2f54d7 100644
--- a/src/gnunet/transport/endpoint.go
+++ b/src/gnunet/transport/endpoint.go
@@ -39,7 +39,7 @@ var (
        ErrEndpExists           = errors.New("endpoint exists")
        ErrEndpNoAddress        = errors.New("no address for endpoint")
        ErrEndpNoConnection     = errors.New("no connection on endpoint")
-       ErrEndpMaybeSent        = errors.New("message may have been sent - cant 
know")
+       ErrEndpMaybeSent        = errors.New("message may have been sent - 
can't know")
        ErrEndpWriteShort       = errors.New("write too short")
 )
 
@@ -48,10 +48,10 @@ var (
 // remote endpoints for TCP and UDP traffic.
 type Endpoint interface {
        // Run the endpoint and send received messages to channel
-       Run(context.Context, chan *TransportMessage) error
+       Run(context.Context, chan *Message) error
 
        // Send message on endpoint
-       Send(context.Context, net.Addr, *TransportMessage) error
+       Send(context.Context, net.Addr, *Message) error
 
        // Address returns the listening address for the endpoint
        Address() net.Addr
@@ -84,16 +84,17 @@ func NewEndpoint(addr net.Addr) (ep Endpoint, err error) {
 
 // PacketEndpoint for packet-oriented network protocols
 type PaketEndpoint struct {
+       sync.Mutex
+
        id   int            // endpoint identifier
        netw string         // network identifier ("udp", "udp4", "udp6", ...)
        addr net.Addr       // endpoint address
        conn net.PacketConn // packet connection
        buf  []byte         // buffer for read/write operations
-       mtx  sync.Mutex     // mutex for send operations
 }
 
 // Run packet endpoint: send incoming messages to the handler.
-func (ep *PaketEndpoint) Run(ctx context.Context, hdlr chan *TransportMessage) 
(err error) {
+func (ep *PaketEndpoint) Run(ctx context.Context, hdlr chan *Message) (err 
error) {
        // create listener
        var lc net.ListenConfig
        xproto := ep.addr.Network()
@@ -144,7 +145,7 @@ func (ep *PaketEndpoint) Run(ctx context.Context, hdlr chan 
*TransportMessage) (
 }
 
 // Read a transport message from endpoint based on extended protocol
-func (ep *PaketEndpoint) read() (tm *TransportMessage, err error) {
+func (ep *PaketEndpoint) read() (tm *Message, err error) {
        // read next packet (assuming that it contains one complete message)
        var n int
        if n, _, err = ep.conn.ReadFrom(ep.buf); err != nil {
@@ -167,7 +168,7 @@ func (ep *PaketEndpoint) read() (tm *TransportMessage, err 
error) {
                panic(ErrEndpProtocolUnknown)
        }
        // return transport message
-       return &TransportMessage{
+       return &Message{
                Peer:  peer,
                Msg:   msg,
                Resp:  nil,
@@ -176,10 +177,10 @@ func (ep *PaketEndpoint) read() (tm *TransportMessage, 
err error) {
 }
 
 // Send message to address from endpoint
-func (ep *PaketEndpoint) Send(ctx context.Context, addr net.Addr, msg 
*TransportMessage) (err error) {
+func (ep *PaketEndpoint) Send(ctx context.Context, addr net.Addr, msg 
*Message) (err error) {
        // only one sender at a time
-       ep.mtx.Lock()
-       defer ep.mtx.Unlock()
+       ep.Lock()
+       defer ep.Unlock()
 
        // check for valid connection
        if ep.conn == nil {
@@ -284,7 +285,7 @@ type StreamEndpoint struct {
 }
 
 // Run packet endpoint: send incoming messages to the handler.
-func (ep *StreamEndpoint) Run(ctx context.Context, hdlr chan 
*TransportMessage) (err error) {
+func (ep *StreamEndpoint) Run(ctx context.Context, hdlr chan *Message) (err 
error) {
        // create listener
        var lc net.ListenConfig
        xproto := ep.addr.Network()
@@ -331,7 +332,7 @@ func (ep *StreamEndpoint) Run(ctx context.Context, hdlr 
chan *TransportMessage)
 }
 
 // Read a transport message from endpoint based on extended protocol
-func (ep *StreamEndpoint) read(ctx context.Context, conn net.Conn) (tm 
*TransportMessage, err error) {
+func (ep *StreamEndpoint) read(ctx context.Context, conn net.Conn) (tm 
*Message, err error) {
        // parse transport message based on extended protocol
        var (
                peer *util.PeerID
@@ -341,7 +342,7 @@ func (ep *StreamEndpoint) read(ctx context.Context, conn 
net.Conn) (tm *Transpor
        case "ip+udp":
                // parse peer id
                peer = util.NewPeerID(nil)
-               if _, err = conn.Read(peer.Key); err != nil {
+               if _, err = conn.Read(peer.Data); err != nil {
                        return
                }
                // read next message from connection
@@ -352,14 +353,14 @@ func (ep *StreamEndpoint) read(ctx context.Context, conn 
net.Conn) (tm *Transpor
                panic(ErrEndpProtocolUnknown)
        }
        // return transport message
-       return &TransportMessage{
+       return &Message{
                Peer: peer,
                Msg:  msg,
        }, nil
 }
 
 // Send message to address from endpoint
-func (ep *StreamEndpoint) Send(ctx context.Context, addr net.Addr, msg 
*TransportMessage) error {
+func (ep *StreamEndpoint) Send(ctx context.Context, addr net.Addr, msg 
*Message) error {
        return nil
 }
 
diff --git a/src/gnunet/transport/reader_writer.go 
b/src/gnunet/transport/reader_writer.go
index e99b3df..13028fe 100644
--- a/src/gnunet/transport/reader_writer.go
+++ b/src/gnunet/transport/reader_writer.go
@@ -54,12 +54,8 @@ func WriteMessage(ctx context.Context, wrt io.WriteCloser, 
msg message.Message)
        }
        // watch dog for write operation
        go func() {
-               for {
-                       select {
-                       case <-ctx.Done():
-                               wrt.Close()
-                       }
-               }
+               <-ctx.Done()
+               wrt.Close()
        }()
        // perform write operation
        var n int
@@ -86,46 +82,41 @@ func ReadMessageDirect(rdr io.Reader, buf []byte) (msg 
message.Message, err erro
 func ReadMessage(ctx context.Context, rdr io.ReadCloser, buf []byte) (msg 
message.Message, err error) {
        // watch dog for write operation
        go func() {
-               for {
-                       select {
-                       case <-ctx.Done():
-                               rdr.Close()
-                       }
-               }
+               <-ctx.Done()
+               rdr.Close()
        }()
        // get bytes from reader
        if buf == nil {
                buf = make([]byte, 65536)
        }
-       get := func(pos, count int) error {
-               n, err := rdr.Read(buf[pos : pos+count])
-               if err == nil && n != count {
+       get := func(pos, count int) (err error) {
+               var n int
+               if n, err = rdr.Read(buf[pos : pos+count]); err == nil && n != 
count {
                        err = fmt.Errorf("not enough bytes on reader (%d of 
%d)", n, count)
                }
                return err
        }
        // read header first
-       if err := get(0, 4); err != nil {
-               return nil, err
+       if err = get(0, 4); err != nil {
+               return
        }
        var mh *message.Header
        if mh, err = message.GetMsgHeader(buf[:4]); err != nil {
-               return nil, err
+               return
        }
        // get rest of message
        if err = get(4, int(mh.MsgSize)-4); err != nil {
-               return nil, err
+               return
        }
        if msg, err = message.NewEmptyMessage(mh.MsgType); err != nil {
-               return nil, err
+               return
        }
        if msg == nil {
-               return nil, fmt.Errorf("message{%d} is nil", mh.MsgType)
-       }
-       if err = data.Unmarshal(msg, buf[:mh.MsgSize]); err != nil {
-               return nil, err
+               err = fmt.Errorf("message{%d} is nil", mh.MsgType)
+               return
        }
-       return msg, nil
+       err = data.Unmarshal(msg, buf[:mh.MsgSize])
+       return
 }
 
 //----------------------------------------------------------------------
diff --git a/src/gnunet/transport/responder.go 
b/src/gnunet/transport/responder.go
index f0d9d66..7032c78 100644
--- a/src/gnunet/transport/responder.go
+++ b/src/gnunet/transport/responder.go
@@ -32,6 +32,9 @@ import (
 type Responder interface {
        // Handle outgoing message
        Send(ctx context.Context, msg message.Message) error
+
+       // Receiver returns the receiving peer (string representation)
+       Receiver() string
 }
 
 //----------------------------------------------------------------------
@@ -50,3 +53,8 @@ func (r *TransportResponder) Send(ctx context.Context, msg 
message.Message) erro
        }
        return r.SendFcn(ctx, r.Peer, msg)
 }
+
+// Receiver returns the receiving peer id
+func (r *TransportResponder) Receiver() string {
+       return r.Peer.String()
+}
diff --git a/src/gnunet/transport/transport.go 
b/src/gnunet/transport/transport.go
index 2101849..bc4b632 100644
--- a/src/gnunet/transport/transport.go
+++ b/src/gnunet/transport/transport.go
@@ -32,17 +32,18 @@ import (
 // Trnsport layer error codes
 var (
        ErrTransNoEndpoint = errors.New("no matching endpoint found")
+       ErrTransNoUPNP     = errors.New("no UPnP available")
 )
 
 //======================================================================
 // Network-oriented transport implementation
 //======================================================================
 
-// TransportMessage is the unit processed by the transport mechanism.
+// Message is the unit processed by the transport mechanism.
 // Peer refers to the remote endpoint (sender/receiver) and
 // Msg is the exchanged GNUnet message. The packet itself satisfies the
 // message.Message interface.
-type TransportMessage struct {
+type Message struct {
        // Peer is a identifier for a remote peer
        Peer *util.PeerID
 
@@ -62,10 +63,10 @@ type TransportMessage struct {
 }
 
 // Bytes returns the binary representation of a transport message
-func (msg *TransportMessage) Bytes() ([]byte, error) {
+func (msg *Message) Bytes() ([]byte, error) {
        buf := new(bytes.Buffer)
        // serialize peer id
-       if _, err := buf.Write(msg.Peer.Key); err != nil {
+       if _, err := buf.Write(msg.Peer.Bytes()); err != nil {
                return nil, err
        }
        // serialize message
@@ -74,16 +75,16 @@ func (msg *TransportMessage) Bytes() ([]byte, error) {
 }
 
 // String returns the message in human-readable form
-func (msg *TransportMessage) String() string {
+func (msg *Message) String() string {
        return "TransportMessage{...}"
 }
 
 // NewTransportMessage creates a message suitable for transfer
-func NewTransportMessage(peer *util.PeerID, msg message.Message) (tm 
*TransportMessage) {
+func NewTransportMessage(peer *util.PeerID, msg message.Message) (tm *Message) 
{
        if peer == nil {
                peer = util.NewPeerID(nil)
        }
-       tm = &TransportMessage{
+       tm = &Message{
                Peer:  peer,
                Msg:   msg,
                Resp:  nil,
@@ -97,13 +98,13 @@ func NewTransportMessage(peer *util.PeerID, msg 
message.Message) (tm *TransportM
 // Transport enables network-oriented (like IP, UDP, TCP or UDS)
 // message exchange on multiple endpoints.
 type Transport struct {
-       incoming  chan *TransportMessage   // messages as received from the 
network
+       incoming  chan *Message            // messages as received from the 
network
        endpoints *util.Map[int, Endpoint] // list of available endpoints
        upnp      *network.PortMapper      // UPnP mapper (optional)
 }
 
 // NewTransport creates and runs a new transport layer implementation.
-func NewTransport(ctx context.Context, tag string, ch chan *TransportMessage) 
(t *Transport) {
+func NewTransport(ctx context.Context, tag string, ch chan *Message) (t 
*Transport) {
        // create transport instance
        mngr, err := network.NewPortMapper(tag)
        if err != nil {
@@ -124,7 +125,7 @@ func (t *Transport) Shutdown() {
 }
 
 // Send a message over suitable endpoint
-func (t *Transport) Send(ctx context.Context, addr net.Addr, msg 
*TransportMessage) (err error) {
+func (t *Transport) Send(ctx context.Context, addr net.Addr, msg *Message) 
(err error) {
        // select best endpoint able to handle address
        var bestEp Endpoint
        err = t.endpoints.ProcessRange(func(_ int, ep Endpoint) error {
@@ -174,7 +175,7 @@ func (t *Transport) AddEndpoint(ctx context.Context, addr 
*util.Address) (ep End
        }
        // add endpoint to list and run it
        t.endpoints.Put(ep.ID(), ep)
-       ep.Run(ctx, t.incoming)
+       err = ep.Run(ctx, t.incoming)
        return
 }
 
@@ -185,11 +186,20 @@ func (t *Transport) AddEndpoint(ctx context.Context, addr 
*util.Address) (ep End
 // ForwardOpen returns a local address for listening that will receive traffic
 // from a port forward handled by UPnP on the router.
 func (t *Transport) ForwardOpen(protocol, param string, port int) (id, local, 
remote string, err error) {
+       // check for available UPnP
+       if t.upnp == nil {
+               err = ErrTransNoUPNP
+               return
+       }
        // no parameters currently defined, so just do the assignment.
        return t.upnp.Assign(protocol, port)
 }
 
 // ForwardClose closes a specific port forwarding
 func (t *Transport) ForwardClose(id string) error {
+       // check for available UPnP
+       if t.upnp == nil {
+               return ErrTransNoUPNP
+       }
        return t.upnp.Unassign(id)
 }
diff --git a/src/gnunet/util/address.go b/src/gnunet/util/address.go
index 4cd07da..35e0030 100644
--- a/src/gnunet/util/address.go
+++ b/src/gnunet/util/address.go
@@ -27,10 +27,10 @@ import (
 
 // Address specifies how a peer is reachable on the network.
 type Address struct {
-       Netw    string       ``            // network protocol
-       Options uint32       `order:"big"` // address options
-       Expires AbsoluteTime ``            // expiration date for address
-       Address []byte       `size:"*"`    // address data (protocol-dependent)
+       Netw    string       // network protocol
+       Options uint32       // address options
+       Expires AbsoluteTime // expiration date for address
+       Address []byte       // address data (protocol-dependent)
 }
 
 // NewAddress returns a new Address for the given transport and specs
@@ -43,6 +43,8 @@ func NewAddress(transport string, addr string) *Address {
        }
 }
 
+// NewAddressWrap returns new address from net.Addr with no options
+// or expiry date.
 func NewAddressWrap(addr net.Addr) *Address {
        return &Address{
                Netw:    addr.Network(),
@@ -53,7 +55,7 @@ func NewAddressWrap(addr net.Addr) *Address {
 }
 
 // ParseAddress translates a GNUnet address string like
-// "r5n+ip+udp://1.2.3.4:6789" or "gnunet+tcp://12.3.4.5/".
+// "ip+udp://1.2.3.4:6789" or "gnunet+tcp://12.3.4.5/".
 // It can also handle standard strings like "udp:127.0.0.1:6735".
 func ParseAddress(s string) (addr *Address, err error) {
        p := strings.SplitN(s, ":", 2)
@@ -72,11 +74,6 @@ func (a *Address) Equals(b *Address) bool {
                bytes.Equal(a.Address, b.Address)
 }
 
-// StringAll returns a human-readable representation of an address.
-func (a *Address) StringAll() string {
-       return a.Netw + "://" + string(a.Address)
-}
-
 // implement net.Addr interface methods:
 
 // String returns a human-readable representation of an address.
@@ -91,7 +88,7 @@ func (a *Address) Network() string {
 
 //----------------------------------------------------------------------
 
-// URI returns a string representaion of an address.
+// URI returns a string representation of an address.
 func (a *Address) URI() string {
        return URI(a.Netw, a.Address)
 }
@@ -101,24 +98,6 @@ func URI(network string, addr []byte) string {
 
 //----------------------------------------------------------------------
 
-// IPAddress (can be IPv4 or IPv6 or a DNS name)
-type IPAddress struct {
-       Host []byte `size:"*-2"`
-       Port uint16 `order:"big"`
-}
-
-// NewIPAddress creates a new instance for a given host and port.
-func NewIPAddress(host []byte, port uint16) *IPAddress {
-       ip := &IPAddress{
-               Host: make([]byte, len(host)),
-               Port: port,
-       }
-       copy(ip.Host, host)
-       return ip
-}
-
-//----------------------------------------------------------------------
-
 // PeerAddrList is a list of addresses per peer ID.
 type PeerAddrList struct {
        list *Map[string, []*Address]
@@ -133,12 +112,13 @@ func NewPeerAddrList() *PeerAddrList {
 
 // Add address for peer. The returned mode is 0=not added, 1=new peer,
 // 2=new address
-func (a *PeerAddrList) Add(id string, addr *Address) (mode int) {
+func (a *PeerAddrList) Add(peer *PeerID, addr *Address) (mode int) {
        // check for expired address.
        mode = 0
        if !addr.Expires.Expired() {
                // run add operation
-               a.list.Process(func() error {
+               _ = a.list.Process(func() error {
+                       id := peer.String()
                        list, ok := a.list.Get(id)
                        if !ok {
                                list = make([]*Address, 0)
@@ -160,7 +140,8 @@ func (a *PeerAddrList) Add(id string, addr *Address) (mode 
int) {
 }
 
 // Get address for peer
-func (a *PeerAddrList) Get(id string, transport string) (res []*Address) {
+func (a *PeerAddrList) Get(peer *PeerID, transport string) (res []*Address) {
+       id := peer.String()
        list, ok := a.list.Get(id)
        if ok {
                for _, addr := range list {
@@ -181,6 +162,13 @@ func (a *PeerAddrList) Get(id string, transport string) 
(res []*Address) {
 }
 
 // Delete a list entry by key.
-func (a *PeerAddrList) Delete(id string) {
-       a.list.Delete(id)
+func (a *PeerAddrList) Delete(peer *PeerID) {
+       a.list.Delete(peer.String())
+}
+
+// Contains checks if a peer is contained in the list. Does not check
+// for expired entries.
+func (a *PeerAddrList) Contains(peer *PeerID) (ok bool) {
+       _, ok = a.list.Get(peer.String())
+       return
 }
diff --git a/src/gnunet/util/address_test.go b/src/gnunet/util/address_test.go
index d4936e8..1222124 100644
--- a/src/gnunet/util/address_test.go
+++ b/src/gnunet/util/address_test.go
@@ -37,17 +37,24 @@ func TestAddrList(t *testing.T) {
                        t.Fatal(err)
                }
        }
+       // test peer
+       peer := NewPeerID(nil)
        // allocate AddrList
        addrL := NewPeerAddrList()
        for _, addr := range addrA {
-               rc := 
addrL.Add("2BHV4BN8736W5W3CJNXY2S9WABWTGH35QMFG4BPCWBH7DNBCFC60", addr)
+               rc := addrL.Add(peer, addr)
                t.Logf("added %s (%d)", addr.URI(), rc)
        }
 
        // check list
        t.Log("checking list...")
-       list := 
addrL.Get("2BHV4BN8736W5W3CJNXY2S9WABWTGH35QMFG4BPCWBH7DNBCFC60", "ip+udp")
-       t.Logf("got: %v", list)
+       list := addrL.Get(peer, "ip+udp")
+       for i, addr := range list {
+               t.Logf("got: %s", addr.URI())
+               if addr != addrA[i] {
+                       t.Errorf("address mismatch at index %d", i)
+               }
+       }
        if len(list) != len(addrS) {
                t.Fatal("list size not matching")
        }
diff --git a/src/gnunet/util/array.go b/src/gnunet/util/array.go
index 99c74d9..954521c 100644
--- a/src/gnunet/util/array.go
+++ b/src/gnunet/util/array.go
@@ -33,6 +33,11 @@ var (
 
 // Clone creates a new array of same content as the argument.
 func Clone[T []E, E any](d T) T {
+       // handle nil slices
+       if d == nil {
+               return nil
+       }
+       // create copy
        r := make(T, len(d))
        copy(r, d)
        return r
diff --git a/src/gnunet/util/base32.go b/src/gnunet/util/base32.go
index f0b149a..e9ca494 100644
--- a/src/gnunet/util/base32.go
+++ b/src/gnunet/util/base32.go
@@ -46,9 +46,9 @@ const xlate = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
 
 var (
        // ErrInvalidEncoding signals an invalid encoding
-       ErrInvalidEncoding = errors.New("Invalid encoding")
+       ErrInvalidEncoding = errors.New("invalid encoding")
        // ErrBufferTooSmall signalsa too small buffer for decoding
-       ErrBufferTooSmall = errors.New("Buffer to small")
+       ErrBufferTooSmall = errors.New("buffer to small")
 )
 
 // EncodeBinaryToString encodes a byte array into a string.
diff --git a/src/gnunet/util/base32_test.go b/src/gnunet/util/base32_test.go
index 2ec7231..32ea2de 100644
--- a/src/gnunet/util/base32_test.go
+++ b/src/gnunet/util/base32_test.go
@@ -73,7 +73,7 @@ func TestBase32Preset(t *testing.T) {
                if err != nil {
                        t.Fatal(err)
                }
-               if bytes.Compare(x.bin, e) != 0 {
+               if !bytes.Equal(x.bin, e) {
                        t.Fatalf("Decoding mismatch: '%s' != '%s' for '%s'\n", 
hex.EncodeToString(e), hex.EncodeToString(x.bin), x.str)
                }
        }
diff --git a/src/gnunet/util/fs.go b/src/gnunet/util/fs.go
index 3df641c..00ffa28 100644
--- a/src/gnunet/util/fs.go
+++ b/src/gnunet/util/fs.go
@@ -37,7 +37,7 @@ func EnforceDirExists(path string) error {
                return err
        }
        if !fi.IsDir() {
-               return fmt.Errorf("Not a directory (%s)", path)
+               return fmt.Errorf("not a directory (%s)", path)
        }
        return nil
 }
diff --git a/src/gnunet/util/misc.go b/src/gnunet/util/map.go
similarity index 73%
copy from src/gnunet/util/misc.go
copy to src/gnunet/util/map.go
index 2ee8f1d..adfcc0e 100644
--- a/src/gnunet/util/misc.go
+++ b/src/gnunet/util/map.go
@@ -19,46 +19,19 @@
 package util
 
 import (
-       "strings"
+       "math/rand"
        "sync"
 )
 
-//----------------------------------------------------------------------
-// Count occurence of multiple instance at the same time.
-//----------------------------------------------------------------------
-
-// Counter is a metric with single key
-type Counter[T comparable] map[T]int
-
-// Add one to themetric for a given key and return current value
-func (cm Counter[T]) Add(i T) int {
-       count, ok := cm[i]
-       if !ok {
-               count = 1
-       } else {
-               count++
-       }
-       cm[i] = count
-       return count
-}
-
-// Num returns the metric for a given key
-func (cm Counter[T]) Num(i T) int {
-       count, ok := cm[i]
-       if !ok {
-               count = 0
-       }
-       return count
-}
-
 //----------------------------------------------------------------------
 // Thread-safe map implementation
 //----------------------------------------------------------------------
 
 // Map keys to values
 type Map[K comparable, V any] struct {
+       sync.RWMutex
+
        list      map[K]V
-       mtx       sync.RWMutex
        inProcess bool
 }
 
@@ -107,6 +80,11 @@ func (m *Map[K, V]) ProcessRange(f func(key K, value V) 
error, readonly bool) er
 
 //----------------------------------------------------------------------
 
+// Size returns the number of entries in the map.
+func (m *Map[K, V]) Size() int {
+       return len(m.list)
+}
+
 // Put value into map under given key.
 func (m *Map[K, V]) Put(key K, value V) {
        m.lock(false)
@@ -122,6 +100,25 @@ func (m *Map[K, V]) Get(key K) (value V, ok bool) {
        return
 }
 
+// GetRandom returns a random map entry.
+func (m *Map[K, V]) GetRandom() (key K, value V, ok bool) {
+       m.lock(true)
+       defer m.unlock(true)
+
+       ok = false
+       if size := m.Size(); size > 0 {
+               idx := rand.Intn(size)
+               for key, value = range m.list {
+                       if idx == 0 {
+                               ok = true
+                               return
+                       }
+                       idx--
+               }
+       }
+       return
+}
+
 // Delete key/value pair from map.
 func (m *Map[K, V]) Delete(key K) {
        m.lock(false)
@@ -135,9 +132,9 @@ func (m *Map[K, V]) Delete(key K) {
 func (m *Map[K, V]) lock(readonly bool) {
        if !m.inProcess {
                if readonly {
-                       m.mtx.RLock()
+                       m.RLock()
                } else {
-                       m.mtx.Lock()
+                       m.Lock()
                }
        }
 }
@@ -146,22 +143,9 @@ func (m *Map[K, V]) lock(readonly bool) {
 func (m *Map[K, V]) unlock(readonly bool) {
        if !m.inProcess {
                if readonly {
-                       m.mtx.RUnlock()
+                       m.RUnlock()
                } else {
-                       m.mtx.Unlock()
+                       m.Unlock()
                }
        }
 }
-
-//----------------------------------------------------------------------
-// additional helpers
-//----------------------------------------------------------------------
-
-// StripPathRight returns a dot-separated path without
-// its last (right-most) element.
-func StripPathRight(s string) string {
-       if idx := strings.LastIndex(s, "."); idx != -1 {
-               return s[:idx]
-       }
-       return s
-}
diff --git a/src/gnunet/util/misc.go b/src/gnunet/util/misc.go
index 2ee8f1d..1768a96 100644
--- a/src/gnunet/util/misc.go
+++ b/src/gnunet/util/misc.go
@@ -20,11 +20,10 @@ package util
 
 import (
        "strings"
-       "sync"
 )
 
 //----------------------------------------------------------------------
-// Count occurence of multiple instance at the same time.
+// Count occurrence of multiple instance at the same time.
 //----------------------------------------------------------------------
 
 // Counter is a metric with single key
@@ -52,107 +51,23 @@ func (cm Counter[T]) Num(i T) int {
 }
 
 //----------------------------------------------------------------------
-// Thread-safe map implementation
+// Parameter set with string keys and variable value types
 //----------------------------------------------------------------------
 
-// Map keys to values
-type Map[K comparable, V any] struct {
-       list      map[K]V
-       mtx       sync.RWMutex
-       inProcess bool
-}
-
-// NewMap allocates a new mapping.
-func NewMap[K comparable, V any]() *Map[K, V] {
-       return &Map[K, V]{
-               list:      make(map[K]V),
-               inProcess: false,
-       }
-}
+// ParameterSet with string keys and variable value types
+type ParameterSet map[string]any
 
-//----------------------------------------------------------------------
-
-// Process a function in the locked map context. Calls
-// to other map functions in 'f' will skip their locks.
-func (m *Map[K, V]) Process(f func() error, readonly bool) error {
-       // handle locking
-       m.lock(readonly)
-       m.inProcess = true
-       defer func() {
-               m.inProcess = false
-               m.unlock(readonly)
-       }()
-       // function call in unlocked environment
-       return f()
-}
-
-// Process a ranged function in the locked map context. Calls
-// to other map functions in 'f' will skip their locks.
-func (m *Map[K, V]) ProcessRange(f func(key K, value V) error, readonly bool) 
error {
-       // handle locking
-       m.lock(readonly)
-       m.inProcess = true
-       defer func() {
-               m.inProcess = false
-               m.unlock(readonly)
-       }()
-       // range over map and call function.
-       for key, value := range m.list {
-               if err := f(key, value); err != nil {
-                       return err
+// Get a parameter value with given type 'V'
+func GetParam[V any](params ParameterSet, key string) (i V, ok bool) {
+       var v any
+       if v, ok = params[key]; ok {
+               if i, ok = v.(V); ok {
+                       return
                }
        }
-       return nil
-}
-
-//----------------------------------------------------------------------
-
-// Put value into map under given key.
-func (m *Map[K, V]) Put(key K, value V) {
-       m.lock(false)
-       defer m.unlock(false)
-       m.list[key] = value
-}
-
-// Get value with iven key from map.
-func (m *Map[K, V]) Get(key K) (value V, ok bool) {
-       m.lock(true)
-       defer m.unlock(true)
-       value, ok = m.list[key]
        return
 }
 
-// Delete key/value pair from map.
-func (m *Map[K, V]) Delete(key K) {
-       m.lock(false)
-       defer m.unlock(false)
-       delete(m.list, key)
-}
-
-//----------------------------------------------------------------------
-
-// lock with given mode (if not in processing function)
-func (m *Map[K, V]) lock(readonly bool) {
-       if !m.inProcess {
-               if readonly {
-                       m.mtx.RLock()
-               } else {
-                       m.mtx.Lock()
-               }
-       }
-}
-
-// lock with given mode (if not in processing function)
-func (m *Map[K, V]) unlock(readonly bool) {
-       if !m.inProcess {
-               if readonly {
-                       m.mtx.RUnlock()
-               } else {
-                       m.mtx.Unlock()
-               }
-       }
-}
-
 //----------------------------------------------------------------------
 // additional helpers
 //----------------------------------------------------------------------
diff --git a/src/gnunet/util/peer.go b/src/gnunet/util/peer.go
new file mode 100644
index 0000000..f880040
--- /dev/null
+++ b/src/gnunet/util/peer.go
@@ -0,0 +1,94 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix  >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package util
+
+import (
+       "bytes"
+)
+
+//----------------------------------------------------------------------
+// Peer public key (Ed25519 public key)
+//----------------------------------------------------------------------
+
+// PeerPublicKey is the binary representation of an Ed25519 public key
+type PeerPublicKey struct {
+       Data []byte `size:"32"` // Ed25519 public key data
+}
+
+// NewPeerPublicKey creates a key instance from binary data
+func NewPeerPublicKey(data []byte) *PeerPublicKey {
+       pk := &PeerPublicKey{
+               Data: make([]byte, 32),
+       }
+       if data != nil {
+               if len(data) < 32 {
+                       CopyAlignedBlock(pk.Data, data)
+               } else {
+                       copy(pk.Data, data[:32])
+               }
+       }
+       return pk
+}
+
+//----------------------------------------------------------------------
+// Peer identifier:
+//----------------------------------------------------------------------
+
+// PeerID is a wrpped PeerPublicKey
+type PeerID PeerPublicKey
+
+// NewPeerID creates a new peer id from data.
+func NewPeerID(data []byte) (p *PeerID) {
+       return (*PeerID)(NewPeerPublicKey(data))
+}
+
+// Equals returns true if two peer IDs match.
+func (p *PeerID) Equals(q *PeerID) bool {
+       return bytes.Equal(p.Data, q.Data)
+}
+
+// String returns a human-readable representation of a peer id.
+func (p *PeerID) String() string {
+       return EncodeBinaryToString(p.Data)
+}
+
+// Bytes returns the binary representation of a peer identifier.
+func (p *PeerID) Bytes() []byte {
+       return Clone(p.Data)
+}
+
+//----------------------------------------------------------------------
+
+// PeerSignature is a EdDSA signature from the peer
+type PeerSignature struct {
+       Data []byte `size:"64"`
+}
+
+// NewPeerSignature is a EdDSA signatre with the private peer key
+func NewPeerSignature(data []byte) *PeerSignature {
+       var v []byte
+       if data == nil {
+               v = make([]byte, 64)
+       } else {
+               v = Clone(data)
+       }
+       return &PeerSignature{
+               Data: v,
+       }
+}
diff --git a/src/gnunet/util/peer_id.go b/src/gnunet/util/peer_id.go
deleted file mode 100644
index 384f46c..0000000
--- a/src/gnunet/util/peer_id.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019-2022 Bernd Fix  >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package util
-
-import (
-       "bytes"
-
-       "github.com/bfix/gospel/crypto/ed25519"
-)
-
-// PeerID is the 32-byte binary representation od a Ed25519 key
-type PeerID struct {
-       Key []byte `size:"32"`
-}
-
-// NewPeerID creates a new peer id from data.
-func NewPeerID(data []byte) (p *PeerID) {
-       p = &PeerID{
-               Key: make([]byte, 32),
-       }
-       if data != nil {
-               if len(data) < 32 {
-                       CopyAlignedBlock(p.Key, data)
-               } else {
-                       copy(p.Key, data[:32])
-               }
-       }
-       return
-}
-
-// Equals returns true if two peer IDs match.
-func (p *PeerID) Equals(q *PeerID) bool {
-       return bytes.Equal(p.Key, q.Key)
-}
-
-// String returns a human-readable representation of a peer id.
-func (p *PeerID) String() string {
-       return EncodeBinaryToString(p.Key)
-}
-
-func (p *PeerID) PublicKey() *ed25519.PublicKey {
-       return ed25519.NewPublicKeyFromBytes(p.Key)
-}
diff --git a/src/gnunet/util/rnd.go b/src/gnunet/util/rnd.go
index c01f331..5bf6bd0 100644
--- a/src/gnunet/util/rnd.go
+++ b/src/gnunet/util/rnd.go
@@ -22,17 +22,21 @@ import (
        "bytes"
        "crypto/rand"
        "encoding/binary"
+
+       "github.com/bfix/gospel/logger"
 )
 
 // RndArray fills a buffer with random content
 func RndArray(b []byte) {
-       rand.Read(b)
+       if _, err := rand.Read(b); err != nil {
+               logger.Printf(logger.ERROR, "[RndArray] failed: %s", 
err.Error())
+       }
 }
 
 // NewRndArray creates a new buffer of given size; filled with random content.
 func NewRndArray(size int) []byte {
        b := make([]byte, size)
-       rand.Read(b)
+       RndArray(b)
        return b
 }
 
@@ -42,7 +46,9 @@ func RndUInt64() uint64 {
        RndArray(b)
        var v uint64
        c := bytes.NewBuffer(b)
-       binary.Read(c, binary.BigEndian, &v)
+       if err := binary.Read(c, binary.BigEndian, &v); err != nil {
+               logger.Printf(logger.ERROR, "[RndUInt64] failed: %s", 
err.Error())
+       }
        return v
 }
 
diff --git a/src/gnunet/util/time.go b/src/gnunet/util/time.go
index 9a9d365..6e98514 100644
--- a/src/gnunet/util/time.go
+++ b/src/gnunet/util/time.go
@@ -46,7 +46,7 @@ func NewAbsoluteTime(t time.Time) AbsoluteTime {
 // NewAbsoluteTimeEpoch set the point in time to the given time value
 func NewAbsoluteTimeEpoch(secs uint64) AbsoluteTime {
        return AbsoluteTime{
-               Val: uint64(secs * 1000000),
+               Val: secs * 1000000,
        }
 }
 
@@ -137,8 +137,9 @@ func (t AbsoluteTime) Compare(t2 AbsoluteTime) int {
 // Relative time
 //----------------------------------------------------------------------
 
-// RelativeTime is a timestamp defined relative to the current time.
-// It actually is more like a duration than a time...
+// RelativeTime is a timestamp defined relative to an AbsoluteTime.
+// It is measured in microseconds and is actually more like a duration
+// than a time...
 type RelativeTime struct {
        Val uint64 `order:"big"`
 }
@@ -146,7 +147,7 @@ type RelativeTime struct {
 // NewRelativeTime is initialized with a given duration.
 func NewRelativeTime(d time.Duration) RelativeTime {
        return RelativeTime{
-               Val: uint64(d.Milliseconds()),
+               Val: uint64(d.Microseconds()),
        }
 }
 
@@ -155,12 +156,14 @@ func (t RelativeTime) String() string {
        if t.Val == math.MaxUint64 {
                return "Forever"
        }
-       return time.Duration(t.Val * 1000).String()
+       return time.Duration(t.Val * 1000000).String()
 }
 
 // Add two durations
-func (t RelativeTime) Add(t2 RelativeTime) {
-       t.Val += t2.Val
+func (t RelativeTime) Add(t2 RelativeTime) RelativeTime {
+       return RelativeTime{
+               Val: t.Val + t2.Val,
+       }
 }
 
 // Compare two durations

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]